From 3162233043614eb4d5ed35f5d104106a23404b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bigna=20H=C3=A4rdi?= Date: Wed, 28 Jun 2023 11:02:10 +0200 Subject: [PATCH] Update Metadata, Error and Events (#597) * we're getting there.. * add missing file * finaling compiling! * fix test * remove unused stuff * clean up metadata * move impl metadata * fix build * clean up node-api Error * remove unused errors * update error and event headers * add linker comments * remove clone --- Cargo.lock | 107 ++++- compose-macros/src/lib.rs | 9 +- node-api/Cargo.toml | 7 +- node-api/src/error/dispatch_error.rs | 286 +++++------ node-api/src/error/mod.rs | 72 +-- node-api/src/events/event_details.rs | 208 ++++---- node-api/src/events/mod.rs | 26 +- node-api/src/lib.rs | 4 +- node-api/src/metadata/error.rs | 24 +- node-api/src/metadata/metadata_types.rs | 607 +++++++++++++----------- node-api/src/metadata/mod.rs | 1 + node-api/src/metadata/print_metadata.rs | 146 +++--- node-api/src/metadata/variant_index.rs | 86 ++++ node-api/src/scale_value/decode.rs | 8 +- node-api/src/scale_value/mod.rs | 8 +- node-api/src/test_utils.rs | 6 +- src/api/api_client.rs | 32 +- src/api/mod.rs | 9 +- src/api/rpc_api/author.rs | 6 +- src/api/rpc_api/events.rs | 10 +- src/api/rpc_api/state.rs | 5 +- testing/examples/author_tests.rs | 6 +- testing/examples/events_tests.rs | 4 +- 23 files changed, 935 insertions(+), 742 deletions(-) create mode 100644 node-api/src/metadata/variant_index.rs diff --git a/Cargo.lock b/Cargo.lock index 918aa1b1a..db19d8c7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,7 @@ dependencies = [ name = "ac-node-api" version = "0.4.0" dependencies = [ + "ac-primitives", "bitvec", "derive_more", "either", @@ -56,7 +57,7 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "scale-bits", + "scale-bits 0.3.0", "scale-decode", "scale-encode", "scale-info", @@ -766,6 +767,41 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "der" version = "0.7.6" @@ -1710,6 +1746,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -4424,26 +4466,48 @@ dependencies = [ [[package]] name = "scale-bits" version = "0.3.0" -source = "git+https://github.com/haerdib/scale-bits.git?branch=bh/no-std#41daa270ae62adcae310b801c82647a772f464e4" +source = "git+https://github.com/haerdib/scale-bits.git?branch=bh/no-std#24b4b48821322d0b7337dba1e4e3d11441e8a5dd" dependencies = [ "parity-scale-codec", "scale-info", "serde", ] +[[package]] +name = "scale-bits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + [[package]] name = "scale-decode" version = "0.7.0" -source = "git+https://github.com/scs/scale-decode.git?branch=no-std#37fefeea18ba6c3e4fbc5309898f7e4344dc08e7" +source = "git+https://github.com/scs/scale-decode.git?branch=no-std#d9768dc33ab6d3d064c35fbabfe32e03ec605f07" dependencies = [ - "derive_more", "parity-scale-codec", "primitive-types", - "scale-bits", + "scale-bits 0.4.0", + "scale-decode-derive", "scale-info", "smallvec", ] +[[package]] +name = "scale-decode-derive" +version = "0.7.0" +source = "git+https://github.com/scs/scale-decode.git?branch=no-std#d9768dc33ab6d3d064c35fbabfe32e03ec605f07" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "scale-encode" version = "0.3.0" @@ -4452,16 +4516,29 @@ dependencies = [ "derive_more", "parity-scale-codec", "primitive-types", - "scale-bits", + "scale-bits 0.3.0", + "scale-encode-derive", "scale-info", "smallvec", ] +[[package]] +name = "scale-encode-derive" +version = "0.3.0" +source = "git+https://github.com/scs/scale-encode.git?branch=no-std#1688cf1001530f91c56e5794d072e2b77a81f257" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "scale-info" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b569c32c806ec3abdf3b5869fb8bf1e0d275a7c1c9b0b05603d9464632649edf" +checksum = "ad560913365790f17cbf12479491169f01b9d46d29cfc7422bf8c64bdc61b731" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -4473,9 +4550,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53012eae69e5aa5c14671942a5dd47de59d4cdcff8532a6dd0e081faf1119482" +checksum = "19df9bd9ace6cc2fe19387c96ce677e823e07d017ceed253e7bb3d1d1bd9c73b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5553,6 +5630,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.24.1" @@ -6044,9 +6127,9 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.7", - "rand 0.7.3", + "rand 0.8.5", "static_assertions", ] diff --git a/compose-macros/src/lib.rs b/compose-macros/src/lib.rs index 785897982..8ddd67646 100644 --- a/compose-macros/src/lib.rs +++ b/compose-macros/src/lib.rs @@ -37,11 +37,11 @@ mod rpc; macro_rules! compose_call { ($node_metadata: expr, $pallet: expr, $call_name: expr $(, $args: expr) *) => { { - let pallet = $node_metadata.pallet($pallet).unwrap().to_owned(); + let pallet = $node_metadata.pallet_by_name($pallet).unwrap().to_owned(); - let call_index = pallet.call_indexes.get($call_name).unwrap(); + let call_index = pallet.call_variant_by_name($call_name).unwrap().index; - ([pallet.index, *call_index as u8] $(, ($args)) *) + ([pallet.index(), call_index as u8] $(, ($args)) *) } }; } @@ -93,7 +93,8 @@ macro_rules! compose_extrinsic_with_nonce { debug!("Composing generic extrinsic for module {:?} and call {:?}", $module, $call); - let call = $crate::compose_call!($api.metadata().clone(), $module, $call $(, ($args)) *); + let metadata = $api.metadata(); + let call = $crate::compose_call!(metadata, $module, $call $(, ($args)) *); if let Some(signer) = $api.signer() { $crate::compose_extrinsic_offline!( signer, diff --git a/node-api/Cargo.toml b/node-api/Cargo.toml index d266a7da5..c9e63b122 100644 --- a/node-api/Cargo.toml +++ b/node-api/Cargo.toml @@ -23,8 +23,8 @@ serde_json = { version = "1.0.79", default-features = false, features = ["alloc" # scale scale-bits = { default-features = false, features = ["scale-info", "serde"], git = "https://github.com/haerdib/scale-bits.git", branch = "bh/no-std" } -scale-decode = { default-features = false, features = ["primitive-types"], git = "https://github.com/scs/scale-decode.git", branch = "no-std" } -scale-encode = { default-features = false, features = ["bits", "primitive-types"], git = "https://github.com/scs/scale-encode.git", branch = "no-std" } +scale-decode = { default-features = false, features = ["primitive-types", "derive"], git = "https://github.com/scs/scale-decode.git", branch = "no-std" } +scale-encode = { default-features = false, features = ["bits", "primitive-types", "derive"], git = "https://github.com/scs/scale-encode.git", branch = "no-std" } # substrate sp-core = { default-features = false, features = ["full_crypto", "serde"], git = "https://github.com/paritytech/substrate.git", branch = "master" } @@ -35,6 +35,9 @@ sp-storage = { default-features = false, features = ["serde"], git = "https://gi sp-application-crypto = { default-features = false, git = "https://github.com/paritytech/substrate.git", features = ["full_crypto"], branch = "master" } sp-runtime-interface = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } +# local +ac-primitives = { path = "../primitives", default-features = false } + [dev-dependencies] test-case = "3.1.0" diff --git a/node-api/src/error/dispatch_error.rs b/node-api/src/error/dispatch_error.rs index 22fc07bc5..101757dfc 100644 --- a/node-api/src/error/dispatch_error.rs +++ b/node-api/src/error/dispatch_error.rs @@ -1,16 +1,14 @@ -/* - Copyright 2021 Integritee AG and Supercomputing Systems AG - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// This file was taken from subxt (Parity Technologies (UK)) +// https://github.com/paritytech/subxt/ +// And was adapted by Supercomputing Systems AG. +// +// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG. +// This file is licensed as Apache-2.0 +// see LICENSE for license details. +//! Substrate Dispatch Error representation. + +use super::{Error, MetadataError}; use crate::metadata::Metadata; use alloc::{ borrow::Cow, @@ -21,22 +19,14 @@ use codec::{Decode, Encode}; use core::fmt::Debug; use derive_more::From; use log::*; -use scale_info::TypeDef; - -// Re-expose the errors we use from other crates here: -pub use crate::{ - metadata::{InvalidMetadataError, MetadataError}, - scale_value::{DecodeError, EncodeError}, -}; -pub use sp_core::crypto::SecretStringError; -pub use sp_runtime::transaction_validity::TransactionValidityError; +use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType}; /// An error dispatching a transaction. See Substrate DispatchError //https://github.com/paritytech/substrate/blob/890451221db37176e13cb1a306246f02de80590a/primitives/runtime/src/lib.rs#L524 #[derive(Debug, From)] pub enum DispatchError { /// Some error occurred. - Other(Vec), + Other, /// Failed to lookup some data. CannotLookup, /// A bad origin. @@ -64,154 +54,127 @@ pub enum DispatchError { } impl DispatchError { - /// Attempt to decode a runtime DispatchError - pub fn decode_from<'a>(bytes: impl Into>, metadata: &Metadata) -> Self { + /// Attempt to decode a runtime [`DispatchError`]. + // This function is copied (and adapted) from subxt: + // https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/error/dispatch_error.rs#L208-L321 + pub fn decode_from<'a>( + bytes: impl Into>, + metadata: Metadata, + ) -> Result { let bytes = bytes.into(); - let dispatch_error_ty_id = match metadata.dispatch_error_ty() { - Some(id) => id, - None => { - warn!("Can't decode error: sp_runtime::DispatchError was not found in Metadata"); - return DispatchError::Other(bytes.into_owned()) - }, - }; - - let dispatch_error_ty = match metadata.types().resolve(dispatch_error_ty_id) { - Some(ty) => ty, - None => { - warn!("Can't decode error: sp_runtime::DispatchError type ID doesn't resolve to a known type"); - return DispatchError::Other(bytes.into_owned()) - }, - }; + let dispatch_error_ty_id = + metadata.dispatch_error_ty().ok_or(MetadataError::DispatchErrorNotFound)?; - let variant = match &dispatch_error_ty.type_def { - TypeDef::Variant(var) => var, - _ => { - warn!("Can't decode error: sp_runtime::DispatchError type is not a Variant"); - return DispatchError::Other(bytes.into_owned()) - }, - }; - - let variant_name = - variant.variants.iter().find(|v| v.index == bytes[0]).map(|v| v.name.as_str()); - let name = match variant_name { - Some(name) => name, - None => { - warn!("Can't decode error: sp_runtime::DispatchError does not have a name variant"); - return DispatchError::Other(bytes.into_owned()) - }, - }; - - if bytes.len() < 2 { - warn!( - "Can't decode error: sp_runtime::DispatchError because it contains too few bytes" - ); - return DispatchError::Other(bytes.into_owned()) + // The aim is to decode our bytes into roughly this shape. This is copied from + // `sp_runtime::DispatchError`; we need the variant names and any inner variant + // names/shapes to line up in order for decoding to be successful. + #[derive(DecodeAsType)] + enum DecodedDispatchError { + Other, + CannotLookup, + BadOrigin, + Module(DecodedModuleErrorBytes), + ConsumerRemaining, + NoProviders, + TooManyConsumers, + Token(TokenError), + Arithmetic(ArithmeticError), + Transactional(TransactionalError), + Exhausted, + Corruption, + Unavailable, } - // The remaining bytes are the specific error to decode: - let mut specific_bytes = &bytes[1..]; - match name { - "Module" => Self::decode_module_error(specific_bytes, metadata), // We apply custom logic to transform the module error into the outward facing version - "Token" => { - let token_error = match TokenError::decode(&mut specific_bytes) { - Ok(err) => err, - Err(_) => { - warn!("Can't decode token error: TokenError does not match known formats"); - return DispatchError::Other(bytes.to_vec()) - }, - }; - DispatchError::Token(token_error) - }, - "Arithmetic" => { - let arithmetic_error = match ArithmeticError::decode(&mut specific_bytes) { - Ok(err) => err, - Err(_) => { - warn!("Can't decode arithmetic error: ArithmeticError does not match known formats"); - return DispatchError::Other(bytes.to_vec()) - }, - }; - DispatchError::Arithmetic(arithmetic_error) - }, - "Transactional" => { - let error = match TransactionalError::decode(&mut specific_bytes) { - Ok(err) => err, - Err(_) => { - warn!("Can't decode transactional error: TransactionalError does not match known formats"); - return DispatchError::Other(bytes.to_vec()) - }, - }; - DispatchError::Transactional(error) - }, - "CannotLookup" => DispatchError::CannotLookup, - "BadOrigin" => DispatchError::BadOrigin, - "ConsumerRemaining" => DispatchError::ConsumerRemaining, - "NoProviders" => DispatchError::NoProviders, - "TooManyConsumers" => DispatchError::TooManyConsumers, - "Exhausted" => DispatchError::Exhausted, - "Corruption" => DispatchError::Corruption, - "Unavailable" => DispatchError::Unavailable, - _ => { - warn!("Can't decode runtime dispatch error: sp_runtime::DispatchError does not match known formats"); - DispatchError::Other(bytes.into_owned()) - }, + // ModuleError is a bit special; we want to support being decoded from either + // a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes + // out when decoding to manually work with them. + struct DecodedModuleErrorBytes(Vec); + struct DecodedModuleErrorBytesVisitor; + impl scale_decode::Visitor for DecodedModuleErrorBytesVisitor { + type Error = scale_decode::Error; + type Value<'scale, 'info> = DecodedModuleErrorBytes; + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + _type_id: scale_decode::visitor::TypeId, + _types: &'info scale_info::PortableRegistry, + ) -> DecodeAsTypeResult, Self::Error>> { + DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec()))) + } } - } - - /// ModuleError is a bit special; we want to support being decoded from either - /// a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes - /// out when decoding to manually work with them. - fn decode_module_error(mut bytes: &[u8], metadata: &Metadata) -> Self { - // The oldest and second oldest type of error decode to this shape. - // The old version is 2 bytes; a pallet and error index. - #[derive(Decode)] - struct LegacyModuleError { - index: u8, - error: u8, + impl scale_decode::IntoVisitor for DecodedModuleErrorBytes { + type Visitor = DecodedModuleErrorBytesVisitor; + fn into_visitor() -> Self::Visitor { + DecodedModuleErrorBytesVisitor + } } - // The newer case expands the error for forward compat: - // The new version is 5 bytes; a pallet and error index and then 3 extra bytes. - #[derive(Decode)] - struct CurrentModuleError { - index: u8, - error: [u8; 4], - } + // Decode into our temporary error: + let decoded_dispatch_err = DecodedDispatchError::decode_as_type( + &mut &*bytes, + dispatch_error_ty_id, + metadata.types(), + )?; - // try to decode into the new shape, or the old if that doesn't work - let err = match CurrentModuleError::decode(&mut bytes) { - Ok(e) => e, - Err(_) => { - let old_e = match LegacyModuleError::decode(&mut bytes) { - Ok(err) => err, - Err(_) => { - warn!("Can't decode module error: sp_runtime::DispatchError does not match known formats"); - return DispatchError::Other(bytes.to_vec()) - }, + // Convert into the outward-facing error, mainly by handling the Module variant. + let dispatch_error = match decoded_dispatch_err { + // Mostly we don't change anything from our decoded to our outward-facing error: + DecodedDispatchError::Other => DispatchError::Other, + DecodedDispatchError::CannotLookup => DispatchError::CannotLookup, + DecodedDispatchError::BadOrigin => DispatchError::BadOrigin, + DecodedDispatchError::ConsumerRemaining => DispatchError::ConsumerRemaining, + DecodedDispatchError::NoProviders => DispatchError::NoProviders, + DecodedDispatchError::TooManyConsumers => DispatchError::TooManyConsumers, + DecodedDispatchError::Token(val) => DispatchError::Token(val), + DecodedDispatchError::Arithmetic(val) => DispatchError::Arithmetic(val), + DecodedDispatchError::Transactional(val) => DispatchError::Transactional(val), + DecodedDispatchError::Exhausted => DispatchError::Exhausted, + DecodedDispatchError::Corruption => DispatchError::Corruption, + DecodedDispatchError::Unavailable => DispatchError::Unavailable, + // But we apply custom logic to transform the module error into the outward facing version: + DecodedDispatchError::Module(module_bytes) => { + let module_bytes = module_bytes.0; + + // The old version is 2 bytes; a pallet and error index. + // The new version is 5 bytes; a pallet and error index and then 3 extra bytes. + let raw = if module_bytes.len() == 2 { + RawModuleError { + pallet_index: module_bytes[0], + error: [module_bytes[1], 0, 0, 0], + } + } else if module_bytes.len() == 5 { + RawModuleError { + pallet_index: module_bytes[0], + error: [module_bytes[1], module_bytes[2], module_bytes[3], module_bytes[4]], + } + } else { + warn!("Can't decode error sp_runtime::DispatchError: bytes do not match known shapes"); + // Return _all_ of the bytes; every "unknown" return should be consistent. + return Err(Error::Unknown(bytes.to_vec())) }; - CurrentModuleError { index: old_e.index, error: [old_e.error, 0, 0, 0] } - }, - }; - let error_details = match metadata.error(err.index, err.error[0]) { - Ok(details) => details, - Err(_) => { - warn!("Can't decode error: sp_runtime::DispatchError::Module details do not match known information"); - return DispatchError::Other(bytes.to_vec()) + let pallet_metadata = metadata.pallet_by_index_err(raw.pallet_index)?; + let error_details = pallet_metadata + .error_variant_by_index(raw.error[0]) + .ok_or(MetadataError::ErrorNotFound(raw.pallet_index, raw.error[0]))?; + + // And return our outward-facing version: + DispatchError::Module(ModuleError { + pallet: pallet_metadata.name().to_string(), + error: error_details.name.clone(), + description: error_details.docs.clone(), + raw, + }) }, }; - DispatchError::Module(ModuleError { - pallet: error_details.pallet().to_string(), - error: error_details.error().to_string(), - description: error_details.docs().to_vec(), - error_data: ModuleErrorData { pallet_index: err.index, error: err.error }, - }) + Ok(dispatch_error) } } /// An error relating to tokens when dispatching a transaction. //https://github.com/paritytech/substrate/blob/890451221db37176e13cb1a306246f02de80590a/primitives/runtime/src/lib.rs#L607 -#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, DecodeAsType)] pub enum TokenError { /// Funds are unavailable. FundsUnavailable, @@ -235,7 +198,7 @@ pub enum TokenError { /// An error relating to arithmetic when dispatching a transaction. // https://github.com/paritytech/substrate/blob/890451221db37176e13cb1a306246f02de80590a/primitives/arithmetic/src/lib.rs#L59 -#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, DecodeAsType)] pub enum ArithmeticError { /// Underflow. Underflow, @@ -247,7 +210,7 @@ pub enum ArithmeticError { /// An error relating to the transactional layers when dispatching a transaction. // https://github.com/paritytech/substrate/blob/890451221db37176e13cb1a306246f02de80590a/primitives/runtime/src/lib.rs#L496 -#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, DecodeAsType)] pub enum TransactionalError { /// Too many transactional layers have been spawned. LimitReached, @@ -265,21 +228,28 @@ pub struct ModuleError { /// A description of the error. pub description: Vec, /// A byte representation of the error. - pub error_data: ModuleErrorData, + pub raw: RawModuleError, +} + +impl PartialEq for ModuleError { + fn eq(&self, other: &Self) -> bool { + // A module error is the same if the raw underlying details are the same. + self.raw == other.raw + } } /// The error details about a module error that has occurred. /// /// **Note**: Structure used to obtain the underlying bytes of a ModuleError. -#[derive(Clone, Debug)] -pub struct ModuleErrorData { +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RawModuleError { /// Index of the pallet that the error came from. pub pallet_index: u8, /// Raw error bytes. pub error: [u8; 4], } -impl ModuleErrorData { +impl RawModuleError { /// Obtain the error index from the underlying byte data. pub fn error_index(&self) -> u8 { // Error index is utilized as the first byte from the error array. diff --git a/node-api/src/error/mod.rs b/node-api/src/error/mod.rs index 1e6dd8605..52b9ad75a 100644 --- a/node-api/src/error/mod.rs +++ b/node-api/src/error/mod.rs @@ -1,22 +1,21 @@ // This file was taken from subxt (Parity Technologies (UK)) // https://github.com/paritytech/subxt/ -// And was adapted by Supercomputing Systems AG and Integritee AG. +// And was adapted by Supercomputing Systems AG. // -// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG and Integritee AG. +// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG. // This file is licensed as Apache-2.0 // see LICENSE for license details. //! General node-api Error implementation. -use alloc::{format, string::String}; +use alloc::{string::String, vec::Vec}; use core::fmt::Debug; use derive_more::From; // Re-expose the errors we use from other crates here: -pub use crate::{ - metadata::{InvalidMetadataError, MetadataError}, - scale_value::{DecodeError, EncodeError}, -}; +pub use crate::metadata::{InvalidMetadataError, MetadataError}; +pub use scale_decode::Error as DecodeError; +pub use scale_encode::Error as EncodeError; pub use sp_core::crypto::SecretStringError; pub use sp_runtime::transaction_validity::TransactionValidityError; @@ -34,8 +33,6 @@ pub enum Error { Serialization(serde_json::error::Error), /// Secret string error. SecretString(SecretStringError), - /// Extrinsic validity error - Invalid(TransactionValidityError), /// Invalid metadata error InvalidMetadata(InvalidMetadataError), /// Invalid metadata error @@ -46,12 +43,8 @@ pub enum Error { DecodeValue(DecodeError), /// Error encoding from a [`crate::dynamic::Value`]. EncodeValue(EncodeError), - /// Transaction progress error. - Transaction(TransactionError), - /// Block related error. - Block(BlockError), - /// An error encoding a storage address. - StorageAddress(StorageAddressError), + /// The bytes representing an error that we were unable to decode. + Unknown(Vec), /// Other error. Other(String), } @@ -61,52 +54,3 @@ impl From<&str> for Error { Error::Other(error.into()) } } - -/// Block error -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum BlockError { - /// The block - BlockHashNotFound(String), -} - -impl BlockError { - /// Produce an error that a block with the given hash cannot be found. - pub fn block_hash_not_found(hash: impl AsRef<[u8]>) -> BlockError { - let hash = format!("0x{}", hex::encode(hash)); - BlockError::BlockHashNotFound(hash) - } -} - -/// Transaction error. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum TransactionError { - /// The finality subscription expired (after ~512 blocks we give up if the - /// block hasn't yet been finalized). - FinalitySubscriptionTimeout, - /// The block hash that the transaction was added to could not be found. - /// This is probably because the block was retracted before being finalized. - BlockHashNotFound, -} - -/// Something went wrong trying to encode a storage address. -#[derive(Clone, Debug)] -pub enum StorageAddressError { - /// Storage map type must be a composite type. - MapTypeMustBeTuple, - /// Storage lookup does not have the expected number of keys. - WrongNumberOfKeys { - /// The actual number of keys needed, based on the metadata. - actual: usize, - /// The number of keys provided in the storage address. - expected: usize, - }, - /// Storage lookup requires a type that wasn't found in the metadata. - TypeNotFound(u32), - /// This storage entry in the metadata does not have the correct number of hashers to fields. - WrongNumberOfHashers { - /// The number of hashers in the metadata for this storage entry. - hashers: usize, - /// The number of fields in the metadata for this storage entry. - fields: usize, - }, -} diff --git a/node-api/src/events/event_details.rs b/node-api/src/events/event_details.rs index dbb4c0d48..0743d6f00 100644 --- a/node-api/src/events/event_details.rs +++ b/node-api/src/events/event_details.rs @@ -1,76 +1,92 @@ -/* - Copyright 2021 Integritee AG and Supercomputing Systems AG - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// This file bases on subxt (Parity Technologies (UK)) +// https://github.com/paritytech/subxt/ +// And was adapted by Supercomputing Systems AG. + +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! A representation of a block of events. use crate::{ error::{DispatchError, Error}, - metadata::EventMetadata, - scale_value::{decode_as_type, Composite, TypeId}, + metadata::{MetadataError, PalletMetadata}, + scale_value::{Composite, TypeId}, Metadata, Phase, StaticEvent, }; -use alloc::{string::ToString, sync::Arc, vec, vec::Vec}; -use codec::{Decode, Error as CodecError}; +use alloc::{sync::Arc, vec::Vec}; +use codec::Decode; use log::*; -use sp_core::H256; +use scale_decode::DecodeAsFields; /// The event details. +/// Based on subxt EventDetails. +/// https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/events/events_type.rs#L197-L216 #[derive(Debug, Clone)] -pub struct EventDetails { +pub struct EventDetails { phase: Phase, + /// The index of the event in the list of events in a given block. index: u32, all_bytes: Arc<[u8]>, // start of the bytes (phase, pallet/variant index and then fields and then topic to follow). start_idx: usize, - // start of the fields (ie after phase nad pallet/variant index). - fields_start_idx: usize, + // start of the event (ie pallet/variant index and then the fields and topic after). + event_start_idx: usize, + // start of the fields (ie after phase and pallet/variant index). + event_fields_start_idx: usize, // end of the fields. - fields_end_idx: usize, + event_fields_end_idx: usize, // end of everything (fields + topics) end_idx: usize, metadata: Metadata, + topics: Vec, } -impl EventDetails { +// Based on subxt: +// https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/events/events_type.rs#L218-L409 +impl EventDetails { // Attempt to dynamically decode a single event from our events input. pub(crate) fn decode_from( metadata: Metadata, all_bytes: Arc<[u8]>, start_idx: usize, index: u32, - ) -> Result { + ) -> Result { let input = &mut &all_bytes[start_idx..]; let phase = Phase::decode(input)?; + + let event_start_idx = all_bytes.len() - input.len(); + let pallet_index = u8::decode(input)?; let variant_index = u8::decode(input)?; - let fields_start_idx = all_bytes.len() - input.len(); + let event_fields_start_idx = all_bytes.len() - input.len(); // Get metadata for the event: - let event_metadata = metadata.event(pallet_index, variant_index)?; - debug!("Decoding Event '{}::{}'", event_metadata.pallet(), event_metadata.event()); + let event_pallet = metadata.pallet_by_index_err(pallet_index)?; + let event_variant = event_pallet + .event_variant_by_index(variant_index) + .ok_or(MetadataError::VariantIndexNotFound(variant_index))?; + debug!("Decoding Event '{}::{}'", event_pallet.name(), &event_variant.name); // Skip over the bytes belonging to this event. - for field_metadata in event_metadata.fields() { + for field_metadata in &event_variant.fields { // Skip over the bytes for this field: - decode_as_type(input, field_metadata.type_id(), &metadata.runtime_metadata().types)?; + scale_decode::visitor::decode_with_visitor( + input, + field_metadata.ty.id, + metadata.types(), + scale_decode::visitor::IgnoreVisitor, + ) + .map_err(scale_decode::Error::from)?; } // the end of the field bytes. - let fields_end_idx = all_bytes.len() - input.len(); + let event_fields_end_idx = all_bytes.len() - input.len(); - // topics come after the event data in EventRecord. They aren't used for - // anything at the moment, so just decode and throw them away. - let _topics = Vec::::decode(input)?; + // topics come after the event data in EventRecord. + let topics = Vec::::decode(input)?; // what bytes did we skip over in total, including topics. let end_idx = all_bytes.len() - input.len(); @@ -79,17 +95,19 @@ impl EventDetails { phase, index, start_idx, - fields_start_idx, - fields_end_idx, + event_start_idx, + event_fields_start_idx, + event_fields_end_idx, end_idx, all_bytes, metadata, + topics, }) } /// When was the event produced? pub fn phase(&self) -> Phase { - self.phase.clone() + self.phase } /// What index is this event in the stored events for this block. @@ -101,31 +119,37 @@ impl EventDetails { pub fn pallet_index(&self) -> u8 { // Note: never panics; we expect these bytes to exist // in order that the EventDetails could be created. - self.all_bytes[self.fields_start_idx - 2] + self.all_bytes[self.event_fields_start_idx - 2] } /// The index of the event variant that the event originated from. pub fn variant_index(&self) -> u8 { // Note: never panics; we expect these bytes to exist // in order that the EventDetails could be created. - self.all_bytes[self.fields_start_idx - 1] + self.all_bytes[self.event_fields_start_idx - 1] } /// The name of the pallet from whence the Event originated. pub fn pallet_name(&self) -> &str { - self.event_metadata().pallet() + self.event_metadata().pallet.name() } /// The name of the event (ie the name of the variant that it corresponds to). pub fn variant_name(&self) -> &str { - self.event_metadata().event() + &self.event_metadata().variant.name } - /// Fetch the metadata for this event. - pub fn event_metadata(&self) -> &EventMetadata { - self.metadata - .event(self.pallet_index(), self.variant_index()) - .expect("this must exist in order to have produced the EventDetails") + /// Fetch details from the metadata for this event. + pub fn event_metadata(&self) -> EventMetadataDetails { + let pallet = self + .metadata + .pallet_by_index(self.pallet_index()) + .expect("event pallet to be found; we did this already during decoding"); + let variant = pallet + .event_variant_by_index(self.variant_index()) + .expect("event variant to be found; we did this already during decoding"); + + EventMetadataDetails { pallet, variant } } /// Return _all_ of the bytes representing this event, which include, in order: @@ -139,55 +163,34 @@ impl EventDetails { /// Return the bytes representing the fields stored in this event. pub fn field_bytes(&self) -> &[u8] { - &self.all_bytes[self.fields_start_idx..self.fields_end_idx] + &self.all_bytes[self.event_fields_start_idx..self.event_fields_end_idx] } /// Decode and provide the event fields back in the form of a [`scale_value::Composite`] - /// type which represents the named or unnamed fields that were - /// present in the event. + /// type which represents the named or unnamed fields that were present in the event. pub fn field_values(&self) -> Result, Error> { let bytes = &mut self.field_bytes(); let event_metadata = self.event_metadata(); - // If the first field has a name, we assume that the rest do too (it'll either - // be a named struct or a tuple type). If no fields, assume unnamed. - let is_named = - event_metadata.fields().get(0).map(|fm| fm.name().is_some()).unwrap_or(false); - - if !is_named { - let mut event_values = vec![]; - for field_metadata in event_metadata.fields() { - let value = decode_as_type( - bytes, - field_metadata.type_id(), - &self.metadata.runtime_metadata().types, - )?; - event_values.push(value); - } - - Ok(Composite::Unnamed(event_values)) - } else { - let mut event_values = vec![]; - for field_metadata in event_metadata.fields() { - let value = decode_as_type( - bytes, - field_metadata.type_id(), - &self.metadata.runtime_metadata().types, - )?; - event_values.push((field_metadata.name().unwrap_or_default().to_string(), value)); - } - - Ok(Composite::Named(event_values)) - } + let mut fields = event_metadata + .variant + .fields + .iter() + .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref())); + + let decoded = + >::decode_as_fields(bytes, &mut fields, self.metadata.types())?; + + Ok(decoded) } /// Attempt to decode these [`EventDetails`] into a specific static event. /// This targets the fields within the event directly. You can also attempt to /// decode the entirety of the event type (including the pallet and event /// variants) using [`EventDetails::as_root_event()`]. - pub fn as_event(&self) -> Result, CodecError> { + pub fn as_event(&self) -> Result, Error> { let ev_metadata = self.event_metadata(); - if ev_metadata.pallet() == E::PALLET && ev_metadata.event() == E::EVENT { + if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT { Ok(Some(E::decode(&mut self.field_bytes())?)) } else { Ok(None) @@ -197,18 +200,57 @@ impl EventDetails { /// Attempt to decode these [`EventDetails`] into a root event type (which includes /// the pallet and event enum variants as well as the event fields). A compatible /// type for this is exposed via static codegen as a root level `Event` type. - pub fn as_root_event(&self) -> Result { - E::decode(&mut self.bytes()) + pub fn as_root_event(&self) -> Result { + let ev_metadata = self.event_metadata(); + let pallet_bytes = &self.all_bytes[self.event_start_idx + 1..self.event_fields_end_idx]; + let pallet_event_ty = ev_metadata + .pallet + .event_ty_id() + .ok_or_else(|| MetadataError::EventTypeNotFoundInPallet(ev_metadata.pallet.index()))?; + + E::root_event(pallet_bytes, self.pallet_name(), pallet_event_ty, &self.metadata) + } + + /// Return the topics associated with this event. + pub fn topics(&self) -> &[Hash] { + &self.topics } } -impl EventDetails { +impl EventDetails { /// Checks if the extrinsic has failed. If so, the corresponding DispatchError is returned. pub fn check_if_failed(&self) -> Result<(), DispatchError> { if self.pallet_name() == "System" && self.variant_name() == "ExtrinsicFailed" { - let dispatch_error = DispatchError::decode_from(self.field_bytes(), &self.metadata); + let dispatch_error = + DispatchError::decode_from(self.field_bytes(), self.metadata.clone()) + .map_err(|_| DispatchError::CannotLookup)?; return Err(dispatch_error) } Ok(()) } } + +/// Details for the given event plucked from the metadata. +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/events/events_type.rs#L411-L415 +pub struct EventMetadataDetails<'a> { + pub pallet: PalletMetadata<'a>, + pub variant: &'a scale_info::Variant, +} + +/// This trait is implemented on the statically generated root event type, so that we're able +/// to decode it properly via a pallet event that impls `DecodeAsMetadata`. This is necessary +/// becasue the "root event" type is generated using pallet info but doesn't actually exist in the +/// metadata types, so we have no easy way to decode things into it via type information and need a +/// little help via codegen. +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/events/events_type.rs#L417-L432 +#[doc(hidden)] +pub trait RootEvent: Sized { + /// Given details of the pallet event we want to decode, and the name of the pallet, try to hand + /// back a "root event". + fn root_event( + pallet_bytes: &[u8], + pallet_name: &str, + pallet_event_ty: u32, + metadata: &Metadata, + ) -> Result; +} diff --git a/node-api/src/events/mod.rs b/node-api/src/events/mod.rs index b5ec21c2d..ad5f29e70 100644 --- a/node-api/src/events/mod.rs +++ b/node-api/src/events/mod.rs @@ -7,9 +7,7 @@ // see LICENSE for license details. //! A representation of a block of events. -//! -//! This file is very similar to subxt, except where noted. -//! Based on https://github.com/paritytech/subxt/commit/1e8d0956cc6aeb882637bde1d09ac44186181781# +//! This file bases on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/subxt/src/events/events_type.rs#L19-L196 use crate::{error::Error, Metadata, StaticEvent}; use alloc::{sync::Arc, vec::Vec}; @@ -20,7 +18,7 @@ pub use event_details::EventDetails; /// A collection of events obtained from a block, bundled with the necessary /// information needed to decode and iterate over them. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Events { metadata: Metadata, block_hash: Hash, @@ -32,7 +30,19 @@ pub struct Events { num_events: u32, } -impl Events { +// Ignore the Metadata when debug-logging events; it's big and distracting. +impl core::fmt::Debug for Events { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Events") + .field("block_hash", &self.block_hash) + .field("event_bytes", &self.event_bytes) + .field("start_idx", &self.start_idx) + .field("num_events", &self.num_events) + .finish() + } +} + +impl Events { pub fn new(metadata: Metadata, block_hash: Hash, event_bytes: Vec) -> Self { // event_bytes is a SCALE encoded vector of events. So, pluck the // compact encoded length from the front, leaving the remaining bytes @@ -77,7 +87,7 @@ impl Events { // use of it with our `FilterEvents` stuff. pub fn iter( &self, - ) -> impl Iterator> + Send + Sync + 'static { + ) -> impl Iterator, Error>> + Send + Sync + 'static { // The event bytes ignoring the compact encoded length on the front: let event_bytes = self.event_bytes.clone(); let metadata = self.metadata.clone(); @@ -140,9 +150,9 @@ mod tests { }, Phase, }; - use codec::Encode; use scale_info::TypeInfo; + use sp_core::H256; use test_case::test_case; /// [`RawEventDetails`] can be annoying to test, because it contains @@ -165,7 +175,7 @@ mod tests { // Just for convenience, pass in the metadata type constructed // by the `metadata` function above to simplify caller code. metadata: &Metadata, - actual: EventDetails, + actual: EventDetails, expected: TestRawEventDetails, ) { let types = &metadata.runtime_metadata().types; diff --git a/node-api/src/lib.rs b/node-api/src/lib.rs index 88ff0c1d6..ef72be468 100644 --- a/node-api/src/lib.rs +++ b/node-api/src/lib.rs @@ -21,8 +21,10 @@ extern crate alloc; use alloc::{borrow::ToOwned, vec::Vec}; use codec::{Decode, Encode}; +pub use alloc::{collections::BTreeMap, vec}; pub use events::{EventDetails, Events}; pub use metadata::{Metadata, MetadataError}; +pub use scale_decode::DecodeAsType; pub mod error; pub mod events; @@ -67,7 +69,7 @@ pub trait StaticEvent: Decode { /// A phase of a block's execution. // https://github.com/paritytech/substrate/blob/2bfc1dd66ef32cf8beb90007dfb544a9d28f1b2f/frame/system/src/lib.rs#L698-L708 -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Encode, Decode)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Encode, Decode)] pub enum Phase { /// Applying an extrinsic. ApplyExtrinsic(u32), diff --git a/node-api/src/metadata/error.rs b/node-api/src/metadata/error.rs index 6d37c1cab..817343710 100644 --- a/node-api/src/metadata/error.rs +++ b/node-api/src/metadata/error.rs @@ -12,15 +12,19 @@ */ use alloc::string::String; -use codec::{Decode, Encode, Error as CodecError}; +use codec::{Decode, Encode}; /// Metadata error originated from inspecting the internal representation of the runtime metadata. #[derive(Debug, Clone, PartialEq, Eq)] pub enum MetadataError { + /// The DispatchError type isn't available in the metadata. + DispatchErrorNotFound, /// Module is not in metadata. - PalletNotFound(String), + PalletNameNotFound(String), /// Pallet is not in metadata. PalletIndexNotFound(u8), + /// Event type not found in metadata. + EventTypeNotFoundInPallet(u8), /// Call is not in metadata. CallNotFound(&'static str), /// Event is not in metadata. @@ -31,22 +35,14 @@ pub enum MetadataError { StorageNotFound(&'static str), /// Storage type does not match requested type. StorageTypeError, - /// Default error. - DefaultError(CodecError), - /// Failure to decode constant value. - ConstantValueError(CodecError), /// Constant is not in metadata. ConstantNotFound(&'static str), + /// Variant not found. + VariantIndexNotFound(u8), /// Type is not in metadata. TypeNotFound(u32), - /// Runtime constant metadata is incompatible with the static one. - IncompatibleConstantMetadata(String, String), - /// Runtime call metadata is incompatible with the static one. - IncompatibleCallMetadata(String, String), - /// Runtime storage metadata is incompatible with the static one. - IncompatibleStorageMetadata(String, String), - /// Runtime metadata is not fully compatible with the static one. - IncompatibleMetadata, + /// Api is not in metadata. + RuntimeApiNotFound(String), } #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Encode, Decode)] diff --git a/node-api/src/metadata/metadata_types.rs b/node-api/src/metadata/metadata_types.rs index c7dd114f8..0d4cf4ceb 100644 --- a/node-api/src/metadata/metadata_types.rs +++ b/node-api/src/metadata/metadata_types.rs @@ -1,34 +1,31 @@ -// This file was taken from subxt (Parity Technologies (UK)) +// This file bases on subxt (Parity Technologies (UK)) // https://github.com/paritytech/subxt/ -// And was adapted by Supercomputing Systems AG and Integritee AG. +// And was adapted by Supercomputing Systems AG. // -// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG and Integritee AG. +// Copyright 2019-2023 Parity Technologies (UK) Ltd and Supercomputing Systems AG. // This file is licensed as Apache-2.0 // see LICENSE for license details. -//! Handle substrate chain metadata -//! -//! This file is mostly subxt. +//! Handle substrate chain metadata. use crate::{ - metadata::{v14_to_v15, InvalidMetadataError, MetadataError}, + metadata::{v14_to_v15, variant_index::VariantIndex, InvalidMetadataError, MetadataError}, storage::GetStorageTypes, - Encoded, }; use alloc::{ - borrow::ToOwned, - // We use `BTreeMap` because we can't use `HashMap` in `no_std`. collections::btree_map::BTreeMap, string::{String, ToString}, - vec, vec::Vec, }; use codec::{Decode, Encode}; use frame_metadata::{ - v15::{PalletConstantMetadata, RuntimeMetadataLastVersion, StorageEntryMetadata}, + v15::{ + ExtrinsicMetadata, PalletConstantMetadata, RuntimeApiMethodMetadata, + RuntimeMetadataLastVersion, StorageEntryMetadata, + }, RuntimeMetadata, RuntimeMetadataPrefixed, META_RESERVED, }; -use scale_info::{form::PortableForm, PortableRegistry, Type}; +use scale_info::{form::PortableForm, PortableRegistry, Type, Variant}; use sp_storage::StorageKey; #[cfg(feature = "std")] @@ -36,77 +33,45 @@ use serde::Serialize; /// Metadata wrapper around the runtime metadata. Offers some extra features, /// such as direct pallets, events and error access. -#[derive(Clone, Debug, Encode, Decode)] +#[derive(Clone, Debug)] pub struct Metadata { - pub runtime_metadata: RuntimeMetadataLastVersion, - pub pallets: BTreeMap, - pub events: BTreeMap<(u8, u8), EventMetadata>, - pub errors: BTreeMap<(u8, u8), ErrorMetadata>, + runtime_metadata: RuntimeMetadataLastVersion, + pallets: BTreeMap, + /// Find the location in the pallet Vec by pallet index. + pallets_by_index: BTreeMap, + /// Metadata of the extrinsic. + extrinsic: ExtrinsicMetadata, // Type of the DispatchError type, which is what comes back if // an extrinsic fails. dispatch_error_ty: Option, - // subxt implements caches, but this is not no_std compatible, - // so we leave it commented for the time being. - // Could be made available in std modus #307 - - // cached_metadata_hash: RwLock>, - // cached_call_hashes: HashCache, - // cached_constant_hashes: HashCache, - // cached_storage_hashes: HashCache, + /// Details about each of the runtime API traits. + apis: BTreeMap, } impl Metadata { - /// Returns a reference to [`PalletMetadata`]. - pub fn pallet(&self, name: &'static str) -> Result<&PalletMetadata, MetadataError> { - self.pallets - .get(name) - .ok_or_else(|| MetadataError::PalletNotFound(name.to_string())) + /// Access a pallet given its name. + #[deprecated(note = "please use `pallet_by_name` or `pallet_by_name_err` instead")] + pub fn pallet(&self, pallet_name: &str) -> Result, MetadataError> { + self.pallet_by_name(pallet_name) + .ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.to_string())) } - /// Returns the metadata for the event at the given pallet and event indices. - pub fn event( - &self, - pallet_index: u8, - event_index: u8, - ) -> Result<&EventMetadata, MetadataError> { - let event = self - .events - .get(&(pallet_index, event_index)) - .ok_or(MetadataError::EventNotFound(pallet_index, event_index))?; - Ok(event) - } - - /// Returns the metadata for all events of a given pallet. - pub fn events(&self, pallet_index: u8) -> Vec { - self.events - .clone() - .into_iter() - .filter(|(k, _v)| k.0 == pallet_index) - .map(|(_k, v)| v) - .collect() - } - - /// Returns the metadata for the error at the given pallet and error indices. - pub fn error( - &self, - pallet_index: u8, - error_index: u8, - ) -> Result<&ErrorMetadata, MetadataError> { - let error = self - .errors - .get(&(pallet_index, error_index)) - .ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?; - Ok(error) - } - - /// Returns the metadata for all errors of a given pallet. - pub fn errors(&self, pallet_index: u8) -> Vec { - self.errors - .clone() - .into_iter() - .filter(|(k, _v)| k.0 == pallet_index) - .map(|(_k, v)| v) - .collect() + /// An iterator over all of the available pallets. + pub fn pallets(&self) -> impl Iterator> { + self.pallets.values().map(|inner| PalletMetadata { inner, types: self.types() }) + } + + /// Access a pallet given its encoded variant index. + pub fn pallet_by_index(&self, variant_index: u8) -> Option> { + let name = self.pallets_by_index.get(&variant_index)?; + self.pallet_by_name(name) + } + + /// Access a pallet given its name. + pub fn pallet_by_name(&self, pallet_name: &str) -> Option> { + let inner = self.pallets.get(pallet_name)?; + + Some(PalletMetadata { inner, types: self.types() }) } /// Return the DispatchError type ID if it exists. @@ -124,11 +89,29 @@ impl Metadata { self.runtime_metadata.types.resolve(id) } - /// Return the runtime metadata. + /// Exposes the runtime metadata. pub fn runtime_metadata(&self) -> &RuntimeMetadataLastVersion { &self.runtime_metadata } + /// Return details about the extrinsic format. + pub fn extrinsic(&self) -> &ExtrinsicMetadata { + &self.extrinsic + } + + /// An iterator over all of the runtime APIs. + pub fn runtime_api_traits(&self) -> impl ExactSizeIterator> { + self.apis + .values() + .map(|inner| RuntimeApiMetadata { inner, types: self.types() }) + } + + /// Access a runtime API trait given its name. + pub fn runtime_api_trait_by_name(&'_ self, name: &str) -> Option> { + let inner = self.apis.get(name)?; + Some(RuntimeApiMetadata { inner, types: self.types() }) + } + #[cfg(feature = "std")] pub fn pretty_format(&self) -> Result { let buf = Vec::new(); @@ -139,275 +122,323 @@ impl Metadata { } } +/// Err wrappers around option. +impl Metadata { + /// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found. + pub fn pallet_by_name_err(&self, name: &str) -> Result, MetadataError> { + self.pallet_by_name(name) + .ok_or_else(|| MetadataError::PalletNameNotFound(name.to_string())) + } + + /// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found. + pub fn pallet_by_index_err(&self, index: u8) -> Result, MetadataError> { + self.pallet_by_index(index).ok_or(MetadataError::PalletIndexNotFound(index)) + } + + /// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found. + pub fn runtime_api_trait_by_name_err( + &self, + name: &str, + ) -> Result, MetadataError> { + self.runtime_api_trait_by_name(name) + .ok_or_else(|| MetadataError::RuntimeApiNotFound(name.to_string())) + } +} + /// Metadata for a specific pallet. -#[derive(Clone, Debug, Encode, Decode)] -pub struct PalletMetadata { - pub index: u8, - pub name: String, - pub call_indexes: BTreeMap, - pub call_ty_id: Option, - pub storage: BTreeMap>, - pub constants: BTreeMap>, +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/lib.rs#L153-L251 +#[derive(Debug, Clone, Copy)] +pub struct PalletMetadata<'a> { + inner: &'a PalletMetadataInner, + types: &'a PortableRegistry, } -impl PalletMetadata { - /// Get the name of the pallet. - pub fn name(&self) -> &str { - &self.name +impl<'a> PalletMetadata<'a> { + /// The pallet name. + pub fn name(&self) -> &'a str { + &self.inner.name } - /// Get the index of this pallet. + /// The pallet index. pub fn index(&self) -> u8 { - self.index + self.inner.index + } + + /// The pallet docs. + pub fn docs(&self) -> &'a [String] { + &self.inner.docs } - /// If calls exist for this pallet, this returns the type ID of the variant - /// representing the different possible calls. + /// Type ID for the pallet's Call type, if it exists. pub fn call_ty_id(&self) -> Option { - self.call_ty_id + self.inner.call_ty } - /// Attempt to resolve a call into an index in this pallet, failing - /// if the call is not found in this pallet. - pub fn call_index(&self, function: &'static str) -> Result { - let fn_index = - *self.call_indexes.get(function).ok_or(MetadataError::CallNotFound(function))?; - Ok(fn_index) + /// Type ID for the pallet's Event type, if it exists. + pub fn event_ty_id(&self) -> Option { + self.inner.event_ty } - pub fn storage( - &self, - key: &'static str, - ) -> Result<&StorageEntryMetadata, MetadataError> { - self.storage.get(key).ok_or(MetadataError::StorageNotFound(key)) + /// Type ID for the pallet's Error type, if it exists. + pub fn error_ty_id(&self) -> Option { + self.inner.error_ty } - /// Get a constant's metadata by name - pub fn constant( + /// An iterator over the constants in this pallet. + pub fn storage(&self) -> impl ExactSizeIterator> { + self.inner.storage.values() + } + /// Return metadata storage entry data for given key. + pub fn storage_entry( &self, key: &'static str, - ) -> Result<&PalletConstantMetadata, MetadataError> { - self.constants.get(key).ok_or(MetadataError::ConstantNotFound(key)) + ) -> Result<&StorageEntryMetadata, MetadataError> { + self.inner.storage.get(key).ok_or(MetadataError::StorageNotFound(key)) } - pub fn encode_call(&self, call_name: &'static str, args: C) -> Result - where - C: Encode, - { - let fn_index = self.call_index(call_name)?; - let mut bytes = vec![self.index, fn_index]; - bytes.extend(args.encode()); - Ok(Encoded(bytes)) + /// Return all of the event variants, if an event type exists. + pub fn event_variants(&self) -> Option<&'a [Variant]> { + VariantIndex::get(self.inner.event_ty, self.types) } -} -/// Metadata for specific field. -#[derive(Clone, Debug, Encode, Decode)] -pub struct EventFieldMetadata { - name: Option, - type_name: Option, - type_id: u32, -} - -impl EventFieldMetadata { - /// Construct a new [`EventFieldMetadata`] - pub fn new(name: Option, type_name: Option, type_id: u32) -> Self { - EventFieldMetadata { name, type_name, type_id } + /// Return an event variant given it's encoded variant index. + pub fn event_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant> { + self.inner.event_variant_index.lookup_by_index( + variant_index, + self.inner.event_ty, + self.types, + ) } - /// Get the name of the field. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + /// Return all of the call variants, if a call type exists. + pub fn call_variants(&self) -> Option<&'a [Variant]> { + VariantIndex::get(self.inner.call_ty, self.types) } - /// Get the type name of the field as it appears in the code - pub fn type_name(&self) -> Option<&str> { - self.type_name.as_deref() + /// Return a call variant given it's encoded variant index. + pub fn call_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant> { + self.inner + .call_variant_index + .lookup_by_index(variant_index, self.inner.call_ty, self.types) } - /// Get the id of a type - pub fn type_id(&self) -> u32 { - self.type_id + /// Return a call variant given it's name. + pub fn call_variant_by_name(&self, call_name: &str) -> Option<&'a Variant> { + self.inner + .call_variant_index + .lookup_by_name(call_name, self.inner.call_ty, self.types) } -} -/// Metadata for specific events. -#[derive(Clone, Debug, Encode, Decode)] -pub struct EventMetadata { - pallet: String, - event: String, - fields: Vec, - docs: Vec, -} - -impl EventMetadata { - /// Get the name of the pallet from which the event was emitted. - pub fn pallet(&self) -> &str { - &self.pallet + /// Return all of the error variants, if an error type exists. + pub fn error_variants(&self) -> Option<&'a [Variant]> { + VariantIndex::get(self.inner.error_ty, self.types) } - /// Get the name of the pallet event which was emitted. - pub fn event(&self) -> &str { - &self.event + /// Return an error variant given it's encoded variant index. + pub fn error_variant_by_index(&self, variant_index: u8) -> Option<&'a Variant> { + self.inner.error_variant_index.lookup_by_index( + variant_index, + self.inner.error_ty, + self.types, + ) } - /// The names, type names & types of each field in the event. - pub fn fields(&self) -> &[EventFieldMetadata] { - &self.fields + /// Return constant details given the constant name. + pub fn constant_by_name(&self, name: &str) -> Option<&'a PalletConstantMetadata> { + self.inner.constants.get(name) } - /// Documentation for this event. - pub fn docs(&self) -> &[String] { - &self.docs + /// An iterator over the constants in this pallet. + pub fn constants( + &self, + ) -> impl ExactSizeIterator> { + self.inner.constants.values() } } -#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Encode, Decode)] -pub struct ErrorMetadata { - pallet: String, - error: String, +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/lib.rs#L274-L298 +#[derive(Debug, Clone)] +struct PalletMetadataInner { + /// Pallet name. + name: String, + /// Pallet index. + index: u8, + /// Pallet storage metadata. + storage: BTreeMap>, + /// Type ID for the pallet Call enum. + call_ty: Option, + /// Call variants by name/u8. + call_variant_index: VariantIndex, + /// Type ID for the pallet Event enum. + event_ty: Option, + /// Event variants by name/u8. + event_variant_index: VariantIndex, + /// Type ID for the pallet Error enum. + error_ty: Option, + /// Error variants by name/u8. + error_variant_index: VariantIndex, + /// Map from constant name to constant details. + constants: BTreeMap>, + /// Pallet documentation. docs: Vec, } -impl ErrorMetadata { - /// Get the name of the pallet from which the error originates. - pub fn pallet(&self) -> &str { - &self.pallet - } - /// Get the name of the specific pallet error. - pub fn error(&self) -> &str { - &self.error - } +/// Metadata for the available runtime APIs. +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/lib.rs#L494-L527 +#[derive(Debug, Clone, Copy)] +pub struct RuntimeApiMetadata<'a> { + inner: &'a RuntimeApiMetadataInner, + types: &'a PortableRegistry, +} - /// Documentation for the error. +impl<'a> RuntimeApiMetadata<'a> { + /// Trait name. + pub fn name(&self) -> &'a str { + &self.inner.name + } + /// Trait documentation. pub fn docs(&self) -> &[String] { - &self.docs + &self.inner.docs + } + /// Return the type registry embedded within the metadata. + pub fn types(&self) -> &'a PortableRegistry { + self.types + } + /// An iterator over the trait methods. + pub fn methods( + &self, + ) -> impl ExactSizeIterator> { + self.inner.methods.values() + } + /// Get a specific trait method given its name. + pub fn method_by_name(&self, name: &str) -> Option<&'a RuntimeApiMethodMetadata> { + self.inner.methods.get(name) } } +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/lib.rs#L529-L537 +#[derive(Debug, Clone)] +struct RuntimeApiMetadataInner { + /// Trait name. + name: String, + /// Trait methods. + methods: BTreeMap>, + /// Trait documentation. + docs: Vec, +} + +// Based on https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/from_into/v15.rs impl TryFrom for Metadata { type Error = InvalidMetadataError; - fn try_from(metadata: RuntimeMetadataPrefixed) -> Result { - if metadata.0 != META_RESERVED { + fn try_from(m: RuntimeMetadataPrefixed) -> Result { + if m.0 != META_RESERVED { return Err(InvalidMetadataError::InvalidPrefix) } - let metadata = match metadata.1 { + + let m = match m.1 { RuntimeMetadata::V14(meta) => v14_to_v15(meta), RuntimeMetadata::V15(meta) => meta, _ => return Err(InvalidMetadataError::InvalidVersion), }; - let get_type_def_variant = |type_id: u32| { - let ty = metadata - .types - .resolve(type_id) - .ok_or(InvalidMetadataError::MissingType(type_id))?; - if let scale_info::TypeDef::Variant(var) = &ty.type_def { - Ok(var) - } else { - Err(InvalidMetadataError::TypeDefNotVariant(type_id)) - } - }; - let pallets = metadata - .pallets - .iter() - .map(|pallet| { - let call_ty_id = pallet.calls.as_ref().map(|c| c.ty.id); - - let call_indexes = pallet.calls.as_ref().map_or(Ok(BTreeMap::new()), |call| { - let type_def_variant = get_type_def_variant(call.ty.id)?; - let call_indexes = type_def_variant - .variants - .iter() - .map(|v| (v.name.clone(), v.index)) - .collect(); - Ok(call_indexes) - })?; - - let storage = pallet.storage.as_ref().map_or(BTreeMap::new(), |storage| { - storage - .entries - .iter() - .map(|entry| (entry.name.clone(), entry.clone())) - .collect() - }); + let mut pallets = BTreeMap::new(); + let mut pallets_by_index = BTreeMap::new(); + for p in m.pallets.clone().into_iter() { + let name = p.name; - let constants = pallet - .constants + let storage = p.storage.as_ref().map_or(BTreeMap::new(), |storage| { + storage + .entries .iter() - .map(|constant| (constant.name.clone(), constant.clone())) - .collect(); - - let pallet_metadata = PalletMetadata { - index: pallet.index, - name: pallet.name.to_string(), - call_indexes, - call_ty_id, + .map(|entry| (entry.name.clone(), entry.clone())) + .collect() + }); + + let constants = p + .constants + .iter() + .map(|constant| (constant.name.clone(), constant.clone())) + .collect(); + + let call_variant_index = + VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &m.types); + let error_variant_index = + VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &m.types); + let event_variant_index = + VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &m.types); + + pallets_by_index.insert(p.index, name.clone()); + pallets.insert( + name.clone(), + PalletMetadataInner { + name, + index: p.index, storage, + call_ty: p.calls.map(|c| c.ty.id), + call_variant_index, + event_ty: p.event.map(|e| e.ty.id), + event_variant_index, + error_ty: p.error.map(|e| e.ty.id), + error_variant_index, constants, - }; - - Ok((pallet.name.to_string(), pallet_metadata)) - }) - .collect::>()?; - - let mut events = BTreeMap::<(u8, u8), EventMetadata>::new(); - for pallet in &metadata.pallets { - if let Some(event) = &pallet.event { - let pallet_name: String = pallet.name.to_string(); - let event_type_id = event.ty.id; - let event_variant = get_type_def_variant(event_type_id)?; - for variant in &event_variant.variants { - events.insert( - (pallet.index, variant.index), - EventMetadata { - pallet: pallet_name.clone(), - event: variant.name.to_owned(), - fields: variant - .fields - .iter() - .map(|f| { - EventFieldMetadata::new( - f.name.clone(), - f.type_name.clone(), - f.ty.id, - ) - }) - .collect(), - docs: variant.docs.to_vec(), - }, - ); - } - } + docs: p.docs, + }, + ); } - let mut errors = BTreeMap::<(u8, u8), ErrorMetadata>::new(); - for pallet in &metadata.pallets { - if let Some(error) = &pallet.error { - let pallet_name: String = pallet.name.to_string(); - let error_variant = get_type_def_variant(error.ty.id)?; - for variant in &error_variant.variants { - errors.insert( - (pallet.index, variant.index), - ErrorMetadata { - pallet: pallet_name.clone(), - error: variant.name.clone(), - docs: variant.docs.to_vec(), - }, - ); - } - } - } + let apis = m + .apis + .iter() + .map(|api| { + (api.name.clone(), { + let name = api.name.clone(); + let docs = api.docs.clone(); + let methods = api + .methods + .iter() + .map(|method| (method.name.clone(), method.clone())) + .collect(); + RuntimeApiMetadataInner { name, docs, methods } + }) + }) + .collect(); - let dispatch_error_ty = metadata + let dispatch_error_ty = m .types .types .iter() .find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"]) .map(|ty| ty.id); - Ok(Metadata { runtime_metadata: metadata, pallets, events, errors, dispatch_error_ty }) + Ok(Metadata { + runtime_metadata: m.clone(), + pallets, + pallets_by_index, + extrinsic: m.extrinsic, + dispatch_error_ty, + apis, + }) + } +} + +// Support decoding metadata from the "wire" format directly into this. +// Errors may be lost in the case that the metadata content is somehow invalid. +impl Decode for Metadata { + fn decode(input: &mut I) -> Result { + let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(input)?; + metadata.try_into().map_err(|_e| "Cannot try_into() to Metadata.".into()) + } +} + +// Metadata can be encoded, too. It will encode into a format that's compatible with what +// Subxt requires, and that it can be decoded back from. The actual specifics of the format +// can change over time. +impl Encode for Metadata { + fn encode_to(&self, dest: &mut T) { + let m: frame_metadata::v15::RuntimeMetadataV15 = self.runtime_metadata().clone(); + let m: frame_metadata::RuntimeMetadataPrefixed = m.into(); + m.encode_to(dest) } } @@ -420,7 +451,11 @@ impl Metadata { pallet: &'static str, storage_item: &'static str, ) -> Result { - Ok(self.pallet(pallet)?.storage(storage_item)?.get_value(pallet)?.key()) + Ok(self + .pallet_by_name_err(pallet)? + .storage_entry(storage_item)? + .get_value(pallet)? + .key()) } pub fn storage_map_key( @@ -429,7 +464,11 @@ impl Metadata { storage_item: &'static str, map_key: K, ) -> Result { - Ok(self.pallet(pallet)?.storage(storage_item)?.get_map::(pallet)?.key(map_key)) + Ok(self + .pallet_by_name_err(pallet)? + .storage_entry(storage_item)? + .get_map::(pallet)? + .key(map_key)) } pub fn storage_map_key_prefix( @@ -437,7 +476,9 @@ impl Metadata { pallet: &'static str, storage_item: &'static str, ) -> Result { - self.pallet(pallet)?.storage(storage_item)?.get_map_prefix(pallet) + self.pallet_by_name_err(pallet)? + .storage_entry(storage_item)? + .get_map_prefix(pallet) } pub fn storage_double_map_key_prefix( @@ -446,8 +487,8 @@ impl Metadata { storage_key_name: &'static str, first: K, ) -> Result { - self.pallet(storage_prefix)? - .storage(storage_key_name)? + self.pallet_by_name_err(storage_prefix)? + .storage_entry(storage_key_name)? .get_double_map_prefix::(storage_prefix, first) } @@ -459,8 +500,8 @@ impl Metadata { second_double_map_key: Q, ) -> Result { Ok(self - .pallet(pallet)? - .storage(storage_item)? + .pallet_by_name_err(pallet)? + .storage_entry(storage_item)? .get_double_map::(pallet)? .key(first_double_map_key, second_double_map_key)) } diff --git a/node-api/src/metadata/mod.rs b/node-api/src/metadata/mod.rs index 6641d66fa..a701baf51 100644 --- a/node-api/src/metadata/mod.rs +++ b/node-api/src/metadata/mod.rs @@ -14,6 +14,7 @@ mod error; mod from_v14_to_v15; mod metadata_types; +mod variant_index; pub use error::*; pub use from_v14_to_v15::v14_to_v15; diff --git a/node-api/src/metadata/print_metadata.rs b/node-api/src/metadata/print_metadata.rs index a225d736e..0e8bedb31 100644 --- a/node-api/src/metadata/print_metadata.rs +++ b/node-api/src/metadata/print_metadata.rs @@ -11,42 +11,48 @@ limitations under the License. */ -use crate::metadata::{ErrorMetadata, EventMetadata, Metadata, PalletMetadata}; +use crate::metadata::{Metadata, PalletMetadata}; impl Metadata { pub fn print_overview(&self) { let mut string = String::new(); - for (name, pallet) in &self.pallets { - string.push_str(name.as_str()); + for pallet in self.pallets() { + string.push_str(pallet.name()); string.push('\n'); - for storage in pallet.storage.keys() { + for storage in pallet.storage() { string.push_str(" s "); - string.push_str(storage.as_str()); + string.push_str(&storage.name); string.push('\n'); } - for call in pallet.call_indexes.keys() { - string.push_str(" c "); - string.push_str(call.as_str()); - string.push('\n'); + if let Some(call_variants) = pallet.call_variants() { + for call in call_variants { + string.push_str(" c "); + string.push_str(&call.name); + string.push('\n'); + } } - for constant in pallet.constants.keys() { + for constant in pallet.constants() { string.push_str(" cst "); - string.push_str(constant.as_str()); + string.push_str(&constant.name); string.push('\n'); } - for event in self.events(pallet.index) { - string.push_str(" e "); - string.push_str(event.event()); - string.push('\n'); + if let Some(events) = pallet.event_variants() { + for event in events { + string.push_str(" e "); + string.push_str(&event.name); + string.push('\n'); + } } - for error in self.errors(pallet.index) { - string.push_str(" err "); - string.push_str(error.error()); - string.push('\n'); + if let Some(errors) = pallet.error_variants() { + for error in errors { + string.push_str(" err "); + string.push_str(&error.name); + string.push('\n'); + } } } @@ -54,102 +60,96 @@ impl Metadata { } pub fn print_pallets(&self) { - for m in self.pallets.values() { - m.print() + for pallet in self.pallets() { + pallet.print() } } pub fn print_pallets_with_calls(&self) { - for m in self.pallets.values() { - if !m.call_indexes.is_empty() { - m.print_calls(); - } + for pallet in self.pallets() { + pallet.print_calls(); } } pub fn print_pallets_with_constants(&self) { - for m in self.pallets.values() { - if !m.constants.is_empty() { - m.print_constants(); - } + for pallet in self.pallets() { + pallet.print_constants(); } } pub fn print_pallet_with_storages(&self) { - for m in self.pallets.values() { - if !m.storage.is_empty() { - m.print_storages(); - } + for pallet in self.pallets() { + pallet.print_storages(); } } pub fn print_pallets_with_events(&self) { - for pallet in self.pallets.values() { - println!("----------------- Events for Pallet: {} -----------------\n", pallet.name); - for m in self.events(pallet.index) { - m.print(); - } - println!(); + for pallet in self.pallets() { + pallet.print_events(); } } pub fn print_pallets_with_errors(&self) { - for pallet in self.pallets.values() { - println!("----------------- Errors for Pallet: {} -----------------\n", pallet.name); - for m in self.errors(pallet.index) { - m.print(); - } - println!(); + for pallet in self.pallets() { + pallet.print_errors(); } } } -impl PalletMetadata { +impl<'a> PalletMetadata<'a> { pub fn print(&self) { - println!("----------------- Pallet: '{}' -----------------\n", self.name); - println!("Pallet id: {}", self.index); - - //self.print_calls(); + println!("----------------- Pallet: '{}' -----------------\n", self.name()); + println!("Pallet id: {}", self.index()); } pub fn print_calls(&self) { - println!("----------------- Calls for Pallet: {} -----------------\n", self.name); - for (name, index) in &self.call_indexes { - println!("Name: {name}, index {index}"); - } + println!("----------------- Calls for Pallet: {} -----------------\n", self.name()); + if let Some(variants) = self.call_variants() { + for variant in variants { + println!("Name: {}, index {}", variant.name, variant.index); + } + }; println!(); } pub fn print_constants(&self) { - println!("----------------- Constants for Pallet: {} -----------------\n", self.name); - for (name, constant) in &self.constants { - println!("Name: {}, Type {:?}, Value {:?}", name, constant.ty, constant.value); + println!("----------------- Constants for Pallet: {} -----------------\n", self.name()); + for constant in self.constants() { + println!("Name: {}, Type {:?}, Value {:?}", constant.name, constant.ty, constant.value); } println!(); } pub fn print_storages(&self) { - println!("----------------- Storages for Pallet: {} -----------------\n", self.name); - for (name, storage) in &self.storage { + println!("----------------- Storages for Pallet: {} -----------------\n", self.name()); + for storage in self.storage() { println!( "Name: {}, Modifier: {:?}, Type {:?}, Default {:?}", - name, storage.modifier, storage.ty, storage.default + storage.name, storage.modifier, storage.ty, storage.default ); } println!(); } -} -impl EventMetadata { - pub fn print(&self) { - println!("Name: {}", self.event()); - println!("Field: {:?}", self.fields()); - println!("Docs: {:?}", self.docs()); - println!() + pub fn print_events(&self) { + println!("----------------- Events for Pallet: {} -----------------\n", self.name()); + if let Some(variants) = self.event_variants() { + for variant in variants { + println!("Name: {}", variant.name); + println!("Field: {:?}", variant.fields); + println!("Docs: {:?}", variant.docs); + println!(); + } + }; + println!(); } -} -impl ErrorMetadata { - pub fn print(&self) { - println!("Name: {}", self.error()); - println!("Docs: {:?}", self.docs()); - println!() + pub fn print_errors(&self) { + println!("----------------- Errors for Pallet: {} -----------------\n", self.name()); + if let Some(variants) = self.error_variants() { + for variant in variants { + println!("Name: {}", variant.name); + println!("Docs: {:?}", variant.docs); + println!(); + } + }; + println!(); } } diff --git a/node-api/src/metadata/variant_index.rs b/node-api/src/metadata/variant_index.rs new file mode 100644 index 000000000..0c2d809a1 --- /dev/null +++ b/node-api/src/metadata/variant_index.rs @@ -0,0 +1,86 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! This file is based on +//! https://github.com/paritytech/subxt/blob/8413c4d2dd625335b9200dc2289670accdf3391a/metadata/src/utils/variant_index.rs + +use alloc::{borrow::ToOwned, collections::BTreeMap, string::String}; +use scale_info::{form::PortableForm, PortableRegistry, TypeDef, Variant}; + +/// Given some type ID and type registry, build a couple of +/// indexes to look up variants by index or name. If the ID provided +/// is not a variant, the index will be empty. +/// +/// API optimized for dealing with the `Option` variant type IDs +/// that we get in metadata pallets. +#[derive(Debug, Clone)] +pub struct VariantIndex { + by_name: BTreeMap, + by_index: BTreeMap, +} + +impl VariantIndex { + /// Build indexes from the optional variant ID. + pub fn build(variant_id: Option, types: &PortableRegistry) -> Self { + let Some(variants) = Self::get(variant_id, types) else { + return Self::empty() + }; + + let mut by_name = BTreeMap::new(); + let mut by_index = BTreeMap::new(); + for (pos, variant) in variants.iter().enumerate() { + by_name.insert(variant.name.to_owned(), pos); + by_index.insert(variant.index, pos); + } + + Self { by_name, by_index } + } + + /// Build an empty index. + pub fn empty() -> Self { + Self { by_name: Default::default(), by_index: Default::default() } + } + + /// Get the variants we're pointing at; None if this isn't possible. + pub fn get( + variant_id: Option, + types: &PortableRegistry, + ) -> Option<&[Variant]> { + let Some(variant_id) = variant_id else { + return None + }; + let TypeDef::Variant(v) = &types.resolve(variant_id)?.type_def else { + return None + }; + Some(&v.variants) + } + + /// Lookup a variant by name; `None` if the type is not a variant or name isn't found. + pub fn lookup_by_name<'a, K>( + &self, + name: &K, + variant_id: Option, + types: &'a PortableRegistry, + ) -> Option<&'a Variant> + where + String: alloc::borrow::Borrow, + K: core::hash::Hash + Eq + ?Sized + Ord, + { + let pos = *self.by_name.get(name)?; + let variants = Self::get(variant_id, types)?; + variants.get(pos) + } + + /// Lookup a variant by index; `None` if the type is not a variant or index isn't found. + pub fn lookup_by_index<'a>( + &self, + index: u8, + variant_id: Option, + types: &'a PortableRegistry, + ) -> Option<&'a Variant> { + let pos = *self.by_index.get(&index)?; + let variants = Self::get(variant_id, types)?; + variants.get(pos) + } +} diff --git a/node-api/src/scale_value/decode.rs b/node-api/src/scale_value/decode.rs index 68e4bb44f..31eb59bec 100644 --- a/node-api/src/scale_value/decode.rs +++ b/node-api/src/scale_value/decode.rs @@ -15,7 +15,7 @@ use scale_decode::FieldIter; use scale_info::{form::PortableForm, Path, PortableRegistry}; // This is emitted if something goes wrong decoding into a Value. -pub use scale_decode::visitor::DecodeError; +pub use scale_decode::visitor::DecodeError as VisitorDecodeError; /// Decode data according to the [`TypeId`] provided. /// The provided pointer to the data slice will be moved forwards as needed @@ -24,7 +24,7 @@ pub fn decode_value_as_type( data: &mut &[u8], ty_id: TypeId, types: &PortableRegistry, -) -> Result, DecodeError> { +) -> Result, VisitorDecodeError> { scale_decode::visitor::decode_with_visitor(data, ty_id, types, DecodeValueVisitor) } @@ -73,7 +73,7 @@ impl scale_decode::IntoVisitor for Value { impl scale_decode::visitor::Visitor for DecodeValueVisitor { type Value<'scale, 'info> = Value; - type Error = DecodeError; + type Error = VisitorDecodeError; fn visit_bool<'scale, 'info>( self, @@ -232,7 +232,7 @@ impl scale_decode::visitor::Visitor for DecodeValueVisitor { /// Extract a named/unnamed Composite type out of scale_decode's Composite. fn visit_composite( value: &mut scale_decode::visitor::types::Composite<'_, '_>, -) -> Result, DecodeError> { +) -> Result, VisitorDecodeError> { let len = value.remaining(); // if no fields, we'll always assume unnamed. let named = len > 0 && !value.has_unnamed_fields(); diff --git a/node-api/src/scale_value/mod.rs b/node-api/src/scale_value/mod.rs index 35389661e..b6a421070 100644 --- a/node-api/src/scale_value/mod.rs +++ b/node-api/src/scale_value/mod.rs @@ -1,8 +1,8 @@ // This file was taken from scale-value (Parity Technologies (UK)) // https://github.com/paritytech/scale-value/ -// And was adapted by Supercomputing Systems AG and Integritee AG. +// And was adapted by Supercomputing Systems AG. // -// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG and Integritee AG. +// Copyright 2019-2022 Parity Technologies (UK) Ltd, Supercomputing Systems AG. // This file is licensed as Apache-2.0 // see LICENSE for license details. @@ -26,7 +26,7 @@ pub use scale_info::PortableRegistry; pub mod scale { use super::TypeId; pub use super::{ - decode::{DecodeError, DecodeValueVisitor}, + decode::{DecodeValueVisitor, VisitorDecodeError}, encode::EncodeError, }; use alloc::vec::Vec; @@ -40,7 +40,7 @@ pub mod scale { data: &mut &[u8], ty_id: TypeId, types: &PortableRegistry, - ) -> Result, DecodeError> { + ) -> Result, VisitorDecodeError> { crate::scale_value::decode::decode_value_as_type(data, ty_id, types) } diff --git a/node-api/src/test_utils.rs b/node-api/src/test_utils.rs index 1f61a7f6d..c93d70ea1 100644 --- a/node-api/src/test_utils.rs +++ b/node-api/src/test_utils.rs @@ -6,10 +6,10 @@ // This file is licensed as Apache-2.0 // see LICENSE for license details. +//! Event related test utilities used outside this module. + use crate::{Events, Metadata, Phase}; -/// Event related test utilities used outside this module. -use codec::Encode; -use codec::{Compact, Decode}; +use codec::{Compact, Decode, Encode}; use frame_metadata::{ v14::{ ExtrinsicMetadata as ExtrinsicMetadataV14, PalletEventMetadata as PalletEventMetadataV14, diff --git a/src/api/api_client.rs b/src/api/api_client.rs index 4b6fc6be4..67fa3ba28 100644 --- a/src/api/api_client.rs +++ b/src/api/api_client.rs @@ -300,11 +300,10 @@ mod tests { GenericAdditionalParams, GenericExtrinsicParams, PlainTip, PolkadotConfig, SubstrateKitchensinkConfig, }; + use frame_metadata::{ExtrinsicMetadata, RuntimeMetadata}; + use scale_info::form::PortableForm; use sp_core::H256; - use std::{ - collections::{BTreeMap, HashMap}, - fs, - }; + use std::{collections::HashMap, fs}; fn create_mock_api( genesis_hash: H256, @@ -353,12 +352,23 @@ mod tests { let runtime_version = RuntimeVersion { spec_version: 10, ..Default::default() }; // Update metadata let encoded_metadata: Bytes = fs::read("./ksm_metadata_v14.bin").unwrap().into(); - let metadata: RuntimeMetadataPrefixed = + let runtime_metadata_prefixed: RuntimeMetadataPrefixed = Decode::decode(&mut encoded_metadata.0.as_slice()).unwrap(); - let metadata = Metadata::try_from(metadata).unwrap(); - - let mut changed_metadata = metadata.clone(); - changed_metadata.errors = BTreeMap::default(); + let mut runtime_metadata = match runtime_metadata_prefixed.1 { + RuntimeMetadata::V14(ref metadata) => metadata.clone(), + _ => unimplemented!(), + }; + + let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap(); + + runtime_metadata.extrinsic = ExtrinsicMetadata:: { + ty: runtime_metadata.extrinsic.ty, + version: 0, + signed_extensions: Vec::new(), + }; + let changed_runtime_metadata_prefixed = + RuntimeMetadataPrefixed(1635018093, RuntimeMetadata::V14(runtime_metadata)); + let changed_metadata = Metadata::try_from(changed_runtime_metadata_prefixed).unwrap(); let data = HashMap::::from([ ( @@ -375,14 +385,14 @@ mod tests { create_mock_api(Default::default(), Default::default(), changed_metadata, data); // Ensure current metadata and runtime version are different. - assert_ne!(api.metadata.errors, metadata.errors); + assert_ne!(api.metadata.extrinsic(), metadata.extrinsic()); assert_ne!(api.runtime_version, runtime_version); // Update runtime. api.update_runtime().unwrap(); // Ensure metadata and runtime version have been updated. - assert_eq!(api.metadata.errors, metadata.errors); + assert_eq!(api.metadata.extrinsic(), metadata.extrinsic()); assert_eq!(api.runtime_version, runtime_version); } } diff --git a/src/api/mod.rs b/src/api/mod.rs index c057f372b..bea5d67f4 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -17,6 +17,7 @@ use ac_node_api::EventDetails; use alloc::{string::String, vec::Vec}; +use codec::Decode; use serde::{Deserialize, Serialize}; use sp_core::Bytes; @@ -35,7 +36,7 @@ pub mod rpc_api; /// Extrinsic report returned upon a submit_and_watch request. /// Holds as much information as available. #[derive(Debug, Clone)] -pub struct ExtrinsicReport { +pub struct ExtrinsicReport { // Hash of the extrinsic. pub extrinsic_hash: Hash, // Block hash of the block the extrinsic was included in. @@ -46,15 +47,15 @@ pub struct ExtrinsicReport { // Events assosciated to the extrinsic. // Only available if explicitly stated, because // extra node queries are necessary to fetch the events. - pub events: Option>, + pub events: Option>>, } -impl ExtrinsicReport { +impl ExtrinsicReport { pub fn new( extrinsic_hash: Hash, block_hash: Option, status: TransactionStatus, - events: Option>, + events: Option>>, ) -> Self { Self { extrinsic_hash, block_hash, status, events } } diff --git a/src/api/rpc_api/author.rs b/src/api/rpc_api/author.rs index c7668d753..d598b590b 100644 --- a/src/api/rpc_api/author.rs +++ b/src/api/rpc_api/author.rs @@ -20,7 +20,7 @@ use crate::{ }; use ac_compose_macros::rpc_params; use ac_primitives::{config::Config, UncheckedExtrinsicV4}; -use codec::Encode; +use codec::{Decode, Encode}; use log::*; use serde::de::DeserializeOwned; use sp_core::Bytes; @@ -83,7 +83,7 @@ where #[maybe_async::maybe_async(?Send)] pub trait SubmitAndWatch { type Client: Subscribe; - type Hash: DeserializeOwned; + type Hash: DeserializeOwned + Decode; /// Submit an extrinsic an return a Subscription /// to watch the extrinsic progress. @@ -141,7 +141,7 @@ pub trait SubmitAndWatch { #[maybe_async::maybe_async(?Send)] pub trait SubmitAndWatchUntilSuccess { type Client: Subscribe; - type Hash; + type Hash: Decode; /// Submit an extrinsic and watch it until /// - wait_for_finalized = false => InBlock diff --git a/src/api/rpc_api/events.rs b/src/api/rpc_api/events.rs index 41d2f1237..9a6bf75cc 100644 --- a/src/api/rpc_api/events.rs +++ b/src/api/rpc_api/events.rs @@ -32,7 +32,7 @@ pub type EventSubscriptionFor = #[maybe_async::maybe_async(?Send)] pub trait FetchEvents { - type Hash; + type Hash: Decode; /// Fetch all block events from node for the given block hash. async fn fetch_events_from_block(&self, block_hash: Self::Hash) -> Result>; @@ -42,7 +42,7 @@ pub trait FetchEvents { &self, block_hash: Self::Hash, extrinsic_hash: Self::Hash, - ) -> Result>; + ) -> Result>>; } #[maybe_async::maybe_async(?Send)] @@ -68,7 +68,7 @@ where &self, block_hash: Self::Hash, extrinsic_hash: Self::Hash, - ) -> Result> { + ) -> Result>> { let extrinsic_index = self.retrieve_extrinsic_index_from_block(block_hash, extrinsic_hash).await?; let block_events = self.fetch_events_from_block(block_hash).await?; @@ -98,7 +98,7 @@ impl EventSubscription { impl EventSubscription where - Hash: DeserializeOwned + Copy, + Hash: DeserializeOwned + Copy + Decode, Subscription: HandleSubscription>, { /// Wait for the next value from the internal subscription. @@ -201,7 +201,7 @@ where &self, events: Events, extrinsic_index: u32, - ) -> Result> { + ) -> Result>> { let extrinsic_event_results = events.iter().filter(|ev| { ev.as_ref() .map_or(true, |ev| ev.phase() == Phase::ApplyExtrinsic(extrinsic_index)) diff --git a/src/api/rpc_api/state.rs b/src/api/rpc_api/state.rs index 9523ff698..b6d0da3a2 100644 --- a/src/api/rpc_api/state.rs +++ b/src/api/rpc_api/state.rs @@ -347,9 +347,8 @@ where ) -> Result { let c = self .metadata() - .pallet(pallet)? - .constants - .get(constant) + .pallet_by_name_err(pallet)? + .constant_by_name(constant) .ok_or(MetadataError::ConstantNotFound(constant))?; Ok(Decode::decode(&mut c.value.as_slice())?) diff --git a/testing/examples/author_tests.rs b/testing/examples/author_tests.rs index 2a014dd2d..a7f49c9e6 100644 --- a/testing/examples/author_tests.rs +++ b/testing/examples/author_tests.rs @@ -21,7 +21,8 @@ use std::{thread, time::Duration}; use substrate_api_client::{ ac_node_api::EventDetails, ac_primitives::{ - ExtrinsicSigner as GenericExtrinsicSigner, SignExtrinsic, SubstrateKitchensinkConfig, + Config, ExtrinsicSigner as GenericExtrinsicSigner, SignExtrinsic, + SubstrateKitchensinkConfig, }, extrinsic::BalancesExtrinsics, rpc::{HandleSubscription, JsonrpseeClient}, @@ -30,6 +31,7 @@ use substrate_api_client::{ type ExtrinsicSigner = GenericExtrinsicSigner; type ExtrinsicAddressOf = >::ExtrinsicAddress; +type Hash = ::Hash; #[tokio::main] async fn main() { @@ -121,7 +123,7 @@ async fn main() { until_finalized_handle.join().unwrap(); } -fn assert_assosciated_events_match_expected(events: Vec) { +fn assert_assosciated_events_match_expected(events: Vec>) { // First event assert_eq!(events[0].pallet_name(), "Balances"); assert_eq!(events[0].variant_name(), "Withdraw"); diff --git a/testing/examples/events_tests.rs b/testing/examples/events_tests.rs index 66454ece0..d24249148 100644 --- a/testing/examples/events_tests.rs +++ b/testing/examples/events_tests.rs @@ -27,6 +27,8 @@ use substrate_api_client::{ Api, FetchEvents, GetChainInfo, SubmitAndWatch, SubscribeEvents, XtStatus, }; +type Hash = ::Hash; + /// Check out frame_system::Event::ExtrinsicSuccess: #[derive(Decode, Debug)] struct ExtrinsicSuccess { @@ -97,7 +99,7 @@ async fn main() { } } -fn assert_assosciated_events_match_expected(events: Vec) { +fn assert_assosciated_events_match_expected(events: Vec>) { // First event assert_eq!(events[0].pallet_name(), "Balances"); assert_eq!(events[0].variant_name(), "Withdraw");