Merge remote-tracking branch 'origin/master' into lexnv/light_client_support

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Alexandru Vasile
2023-05-25 14:11:15 +03:00
73 changed files with 8208 additions and 5586 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ where
/// Fetch and return the block body.
pub async fn body(&self) -> Result<BlockBody<T, C>, Error> {
let ids = ExtrinsicPartTypeIds::new(self.client.metadata().runtime_metadata())?;
let ids = ExtrinsicPartTypeIds::new(&self.client.metadata())?;
let block_hash = self.header.hash();
let Some(block_details) = self.client.rpc().block(Some(block_hash)).await? else {
return Err(BlockError::not_found(block_hash).into());
+45 -33
View File
@@ -6,16 +6,15 @@ use crate::{
blocks::block_types::{get_events, CachedEvents},
client::{OfflineClientT, OnlineClientT},
config::{Config, Hasher},
error::{BlockError, Error},
error::{BlockError, Error, MetadataError},
events,
metadata::ExtrinsicMetadata,
metadata::types::PalletMetadata,
rpc::types::ChainBlockExtrinsic,
Metadata,
};
use codec::Decode;
use derivative::Derivative;
use frame_metadata::v15::RuntimeMetadataV15;
use scale_decode::DecodeAsFields;
use std::{collections::HashMap, sync::Arc};
@@ -242,7 +241,7 @@ where
scale_decode::visitor::decode_with_visitor(
cursor,
ids.address,
&metadata.runtime_metadata().types,
metadata.types(),
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
@@ -251,7 +250,7 @@ where
scale_decode::visitor::decode_with_visitor(
cursor,
ids.signature,
&metadata.runtime_metadata().types,
metadata.types(),
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
@@ -259,7 +258,7 @@ where
scale_decode::visitor::decode_with_visitor(
cursor,
ids.extra,
&metadata.runtime_metadata().types,
metadata.types(),
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
@@ -357,19 +356,25 @@ where
/// The name of the pallet from whence the extrinsic originated.
pub fn pallet_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.pallet())
Ok(self.extrinsic_metadata()?.pallet.name())
}
/// The name of the call (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.call())
Ok(&self.extrinsic_metadata()?.variant.name)
}
/// Fetch the metadata for this extrinsic.
pub fn extrinsic_metadata(&self) -> Result<&ExtrinsicMetadata, Error> {
Ok(self
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails, Error> {
let pallet = self
.metadata
.extrinsic(self.pallet_index(), self.variant_index())?)
.pallet_by_index(self.pallet_index())
.ok_or_else(|| MetadataError::PalletIndexNotFound(self.pallet_index()))?;
let variant = pallet
.call_variant_by_index(self.variant_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
Ok(ExtrinsicMetadataDetails { pallet, variant })
}
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
@@ -382,8 +387,8 @@ where
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
bytes,
extrinsic_metadata.fields(),
&self.metadata.runtime_metadata().types,
&extrinsic_metadata.variant.fields,
self.metadata.types(),
)?;
Ok(decoded)
@@ -393,10 +398,12 @@ where
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
let extrinsic_metadata = self.extrinsic_metadata()?;
if extrinsic_metadata.pallet() == E::PALLET && extrinsic_metadata.call() == E::CALL {
if extrinsic_metadata.pallet.name() == E::PALLET
&& extrinsic_metadata.variant.name == E::CALL
{
let decoded = E::decode_as_fields(
&mut self.field_bytes(),
extrinsic_metadata.fields(),
&extrinsic_metadata.variant.fields,
self.metadata.types(),
)?;
Ok(Some(decoded))
@@ -409,18 +416,15 @@ where
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
/// type for this is exposed via static codegen as a root level `Call` type.
pub fn as_root_extrinsic<E: RootExtrinsic>(&self) -> Result<E, Error> {
let pallet = self.metadata.pallet(self.pallet_name()?)?;
let pallet_extrinsic_ty = pallet.call_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::ExtrinsicNotFound(
pallet.index(),
self.variant_index(),
))
let md = self.extrinsic_metadata()?;
let pallet_extrinsic_ty = md.pallet.call_ty_id().ok_or_else(|| {
Error::Metadata(MetadataError::CallTypeNotFoundInPallet(md.pallet.index()))
})?;
// Ignore root enum index.
E::root_extrinsic(
&self.call_bytes()[1..],
self.pallet_name()?,
md.pallet.name(),
pallet_extrinsic_ty,
&self.metadata,
)
@@ -440,6 +444,12 @@ where
}
}
/// Details for the given extrinsic plucked from the metadata.
pub struct ExtrinsicMetadataDetails<'a> {
pub pallet: PalletMetadata<'a>,
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the substrate-based chain.
@@ -459,15 +469,15 @@ pub(crate) struct ExtrinsicPartTypeIds {
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
pub(crate) fn new(metadata: &RuntimeMetadataV15) -> Result<Self, BlockError> {
pub(crate) fn new(metadata: &Metadata) -> Result<Self, BlockError> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
const EXTRA: &str = "Extra";
let id = metadata.extrinsic.ty.id;
let id = metadata.extrinsic().ty();
let Some(ty) = metadata.types.resolve(id) else {
let Some(ty) = metadata.types().resolve(id) else {
return Err(BlockError::MissingType);
};
@@ -732,7 +742,7 @@ mod tests {
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::try_from(runtime_metadata).unwrap()
Metadata::new(runtime_metadata.try_into().unwrap())
}
/// Build an offline client to work with the test metadata.
@@ -752,18 +762,20 @@ mod tests {
let metadata = metadata();
// Except our metadata to contain the registered types.
let extrinsic = metadata
.extrinsic(0, 2)
let pallet = metadata.pallet_by_index(0).expect("pallet exists");
let extrinsic = pallet
.call_variant_by_index(2)
.expect("metadata contains the RuntimeCall enum with this pallet");
assert_eq!(extrinsic.pallet(), "Test");
assert_eq!(extrinsic.call(), "TestCall");
assert_eq!(pallet.name(), "Test");
assert_eq!(&extrinsic.name, "TestCall");
}
#[test]
fn insufficient_extrinsic_bytes() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
// Decode with empty bytes.
let result = ExtrinsicDetails::decode_from(
@@ -781,7 +793,7 @@ mod tests {
fn unsupported_version_extrinsic() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
// Decode with invalid version.
let result = ExtrinsicDetails::decode_from(
@@ -805,7 +817,7 @@ mod tests {
fn statically_decode_extrinsic() {
let metadata = metadata();
let client = client(metadata.clone());
let ids = ExtrinsicPartTypeIds::new(metadata.runtime_metadata()).unwrap();
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
let tx = crate::tx::dynamic(
"Test",
+2 -1
View File
@@ -38,7 +38,8 @@
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let runtime_call = subxt::dynamic::runtime_api_call(
//! "Metadata_metadata_versions",
//! "Metadata",
//! "metadata_versions",
//! Vec::<Value<()>>::new()
//! );
//! ```
+2 -2
View File
@@ -73,13 +73,13 @@ impl<T: Config> OfflineClient<T> {
pub fn new(
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
metadata: impl Into<Metadata>,
) -> OfflineClient<T> {
OfflineClient {
inner: Arc::new(Inner {
genesis_hash,
runtime_version,
metadata,
metadata: metadata.into(),
}),
}
}
+14 -15
View File
@@ -19,8 +19,7 @@ use crate::{
};
use derivative::Derivative;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
/// A trait representing a client that can perform
/// online actions.
@@ -119,14 +118,14 @@ impl<T: Config> OnlineClient<T> {
pub fn from_rpc_client_with<R: RpcClientT>(
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
metadata: impl Into<Metadata>,
rpc_client: Arc<R>,
) -> Result<OnlineClient<T>, Error> {
Ok(OnlineClient {
inner: Arc::new(RwLock::new(Inner {
genesis_hash,
runtime_version,
metadata,
metadata: metadata.into(),
})),
rpc: Rpc::new(rpc_client),
})
@@ -196,7 +195,7 @@ impl<T: Config> OnlineClient<T> {
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
let inner = self.inner.read();
let inner = self.inner.read().expect("shouldn't be poisoned");
inner.metadata.clone()
}
@@ -206,14 +205,14 @@ impl<T: Config> OnlineClient<T> {
///
/// Setting custom metadata may leave Subxt unable to work with certain blocks,
/// subscribe to latest blocks or submit valid transactions.
pub fn set_metadata(&self, metadata: Metadata) {
let mut inner = self.inner.write();
inner.metadata = metadata;
pub fn set_metadata(&self, metadata: impl Into<Metadata>) {
let mut inner = self.inner.write().expect("shouldn't be poisoned");
inner.metadata = metadata.into();
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
let inner = self.inner.read();
let inner = self.inner.read().expect("shouldn't be poisoned");
inner.genesis_hash
}
@@ -224,13 +223,13 @@ impl<T: Config> OnlineClient<T> {
/// Setting a custom genesis hash may leave Subxt unable to
/// submit valid transactions.
pub fn set_genesis_hash(&self, genesis_hash: T::Hash) {
let mut inner = self.inner.write();
let mut inner = self.inner.write().expect("shouldn't be poisoned");
inner.genesis_hash = genesis_hash;
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
let inner = self.inner.read();
let inner = self.inner.read().expect("shouldn't be poisoned");
inner.runtime_version.clone()
}
@@ -241,7 +240,7 @@ impl<T: Config> OnlineClient<T> {
/// Setting a custom runtime version may leave Subxt unable to
/// submit valid transactions.
pub fn set_runtime_version(&self, runtime_version: RuntimeVersion) {
let mut inner = self.inner.write();
let mut inner = self.inner.write().expect("shouldn't be poisoned");
inner.runtime_version = runtime_version;
}
@@ -252,7 +251,7 @@ impl<T: Config> OnlineClient<T> {
/// Return an offline client with the same configuration as this.
pub fn offline(&self) -> OfflineClient<T> {
let inner = self.inner.read();
let inner = self.inner.read().expect("shouldn't be poisoned");
OfflineClient::new(
inner.genesis_hash,
inner.runtime_version.clone(),
@@ -318,12 +317,12 @@ pub struct ClientRuntimeUpdater<T: Config>(OnlineClient<T>);
impl<T: Config> ClientRuntimeUpdater<T> {
fn is_runtime_version_different(&self, new: &RuntimeVersion) -> bool {
let curr = self.0.inner.read();
let curr = self.0.inner.read().expect("shouldn't be poisoned");
&curr.runtime_version != new
}
fn do_update(&self, update: Update) {
let mut writable = self.0.inner.write();
let mut writable = self.0.inner.write().expect("shouldn't be poisoned");
writable.metadata = update.metadata;
writable.runtime_version = update.runtime_version;
}
+19 -12
View File
@@ -5,8 +5,8 @@
use super::ConstantAddress;
use crate::{
client::OfflineClientT,
error::Error,
metadata::{DecodeWithMetadata, MetadataError},
error::{Error, MetadataError},
metadata::DecodeWithMetadata,
Config,
};
use derivative::Derivative;
@@ -39,13 +39,14 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
let expected_hash = self
.client
.metadata()
.constant_hash(address.pallet_name(), address.constant_name())?;
.pallet_by_name(address.pallet_name())
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleConstantMetadata(
address.pallet_name().into(),
address.constant_name().into(),
)
.into());
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
@@ -64,11 +65,17 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
self.validate(address)?;
// 2. Attempt to decode the constant into the type given:
let pallet = metadata.pallet(address.pallet_name())?;
let constant = pallet.constant(address.constant_name())?;
let pallet = metadata
.pallet_by_name(address.pallet_name())
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?;
let constant = pallet
.constant_by_name(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut &*constant.value,
constant.ty.id,
&mut constant.value(),
constant.ty(),
&metadata,
)?;
Ok(value)
+30 -18
View File
@@ -10,7 +10,7 @@ use core::fmt::Debug;
use scale_decode::visitor::DecodeAsTypeResult;
use std::borrow::Cow;
use super::Error;
use super::{Error, MetadataError};
use crate::error::RootError;
/// An error dispatching a transaction.
@@ -145,20 +145,27 @@ impl std::fmt::Display for ModuleError {
return f.write_str("Unknown pallet error (pallet and error details cannot be retrieved)");
};
let pallet = details.pallet();
let error = details.error();
let pallet = details.pallet.name();
let error = &details.variant.name;
write!(f, "Pallet error {pallet}::{error}")
}
}
impl ModuleError {
/// Return more details about this error.
pub fn details(&self) -> Result<&crate::metadata::ErrorMetadata, super::Error> {
let error_details = self
pub fn details(&self) -> Result<ModuleErrorDetails, MetadataError> {
let pallet = self
.metadata
.error(self.raw.pallet_index, self.raw.error[0])?;
Ok(error_details)
.pallet_by_index(self.raw.pallet_index)
.ok_or(MetadataError::PalletIndexNotFound(self.raw.pallet_index))?;
let variant = pallet
.error_variant_by_index(self.raw.error[0])
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.raw.error[0]))?;
Ok(ModuleErrorDetails { pallet, variant })
}
/// Return the underlying module error data that was decoded.
pub fn raw(&self) -> RawModuleError {
self.raw
@@ -167,10 +174,22 @@ impl ModuleError {
/// Attempts to decode the ModuleError into a value implementing the trait `RootError`
/// where the actual type of value is the generated top level enum `Error`.
pub fn as_root_error<E: RootError>(&self) -> Result<E, Error> {
E::root_error(&self.raw.error, self.details()?.pallet(), &self.metadata)
E::root_error(
&self.raw.error,
self.details()?.pallet.name(),
&self.metadata,
)
}
}
/// Details about the module error.
pub struct ModuleErrorDetails<'a> {
/// The pallet that the error came from
pub pallet: crate::metadata::types::PalletMetadata<'a>,
/// The variant representing the error
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// The error details about a module error that has occurred.
///
/// **Note**: Structure used to obtain the underlying bytes of a ModuleError.
@@ -198,16 +217,9 @@ impl DispatchError {
metadata: Metadata,
) -> Result<Self, super::Error> {
let bytes = bytes.into();
let dispatch_error_ty_id = match metadata.dispatch_error_ty() {
Some(id) => id,
None => {
tracing::warn!(
"Can't decode error: sp_runtime::DispatchError was not found in Metadata"
);
return Err(super::Error::Unknown(bytes.into_owned()));
}
};
let dispatch_error_ty_id = metadata
.dispatch_error_ty()
.ok_or(MetadataError::DispatchErrorNotFound)?;
// 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
+54 -8
View File
@@ -17,9 +17,10 @@ pub use dispatch_error::{
};
// Re-expose the errors we use from other crates here:
pub use crate::metadata::{InvalidMetadataError, Metadata, MetadataError};
pub use crate::metadata::Metadata;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
pub use subxt_metadata::TryFromError as MetadataTryFromError;
/// The underlying error enum, generic over the type held by the `Runtime`
/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
@@ -39,12 +40,12 @@ pub enum Error {
/// Serde serialization error
#[error("Serde json error: {0}")]
Serialization(#[from] serde_json::error::Error),
/// Invalid metadata error
#[error("Invalid Metadata: {0}")]
InvalidMetadata(#[from] InvalidMetadataError),
/// Invalid metadata error
/// Error working with metadata.
#[error("Metadata: {0}")]
Metadata(#[from] MetadataError),
/// Error decoding metadata.
#[error("Metadata: {0}")]
MetadataDecoding(#[from] MetadataTryFromError),
/// Runtime error.
#[error("Runtime error: {0:?}")]
Runtime(#[from] DispatchError),
@@ -167,9 +168,6 @@ pub enum StorageAddressError {
/// The number of keys provided in the storage address.
expected: usize,
},
/// Storage lookup requires a type that wasn't found in the metadata.
#[error("Storage lookup requires type {0} to exist in the metadata, but it was not found")]
TypeNotFound(u32),
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[error("Storage entry in metadata does not have the correct number of hashers to fields")]
WrongNumberOfHashers {
@@ -180,6 +178,54 @@ pub enum StorageAddressError {
},
}
/// Something went wrong trying to access details in the metadata.
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum MetadataError {
/// The DispatchError type isn't available in the metadata
#[error("The DispatchError type isn't available")]
DispatchErrorNotFound,
/// Type not found in metadata.
#[error("Type with ID {0} not found")]
TypeNotFound(u32),
/// Pallet not found (index).
#[error("Pallet with index {0} not found")]
PalletIndexNotFound(u8),
/// Pallet not found (name).
#[error("Pallet with name {0} not found")]
PalletNameNotFound(String),
/// Variant not found.
#[error("Variant with index {0} not found")]
VariantIndexNotFound(u8),
/// Constant not found.
#[error("Constant with name {0} not found")]
ConstantNameNotFound(String),
/// Call not found.
#[error("Call with name {0} not found")]
CallNameNotFound(String),
/// Runtime trait not found.
#[error("Runtime trait with name {0} not found")]
RuntimeTraitNotFound(String),
/// Runtime method not found.
#[error("Runtime method with name {0} not found")]
RuntimeMethodNotFound(String),
/// Call type not found in metadata.
#[error("Call type not found in pallet with index {0}")]
CallTypeNotFoundInPallet(u8),
/// Event type not found in metadata.
#[error("Event type not found in pallet with index {0}")]
EventTypeNotFoundInPallet(u8),
/// Storage details not found in metadata.
#[error("Storage details not found in pallet with name {0}")]
StorageNotFoundInPallet(String),
/// Storage entry not found.
#[error("Storage entry {0} not found")]
StorageEntryNotFound(String),
/// The generated interface used is not compatible with the node.
#[error("The generated code is not compatible with the node")]
IncompatibleCodegen,
}
/// This trait is implemented on the statically generated root ModuleError type
#[doc(hidden)]
pub trait RootError: Sized {
+45 -28
View File
@@ -6,8 +6,11 @@
use super::{Phase, StaticEvent};
use crate::{
client::OnlineClientT, error::Error, events::events_client::get_event_bytes,
metadata::EventMetadata, Config, Metadata,
client::OnlineClientT,
error::{Error, MetadataError},
events::events_client::get_event_bytes,
metadata::types::PalletMetadata,
Config, Metadata,
};
use codec::{Compact, Decode};
use derivative::Derivative;
@@ -224,20 +227,25 @@ impl EventDetails {
let event_fields_start_idx = all_bytes.len() - input.len();
// Get metadata for the event:
let event_metadata = metadata.event(pallet_index, variant_index)?;
let event_pallet = metadata
.pallet_by_index(pallet_index)
.ok_or(MetadataError::PalletIndexNotFound(pallet_index))?;
let event_variant = event_pallet
.event_variant_by_index(variant_index)
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
tracing::debug!(
"Decoding Event '{}::{}'",
event_metadata.pallet(),
event_metadata.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:
scale_decode::visitor::decode_with_visitor(
input,
field_metadata.ty.id,
&metadata.runtime_metadata().types,
metadata.types(),
scale_decode::visitor::IgnoreVisitor,
)
.map_err(scale_decode::Error::from)?;
@@ -292,19 +300,25 @@ impl EventDetails {
/// 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:
@@ -332,8 +346,8 @@ impl EventDetails {
use scale_decode::DecodeAsFields;
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
bytes,
event_metadata.fields(),
&self.metadata.runtime_metadata().types,
&event_metadata.variant.fields,
self.metadata.types(),
)?;
Ok(decoded)
@@ -343,10 +357,10 @@ impl EventDetails {
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, 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 {
let decoded = E::decode_as_fields(
&mut self.field_bytes(),
ev_metadata.fields(),
&ev_metadata.variant.fields,
self.metadata.types(),
)?;
Ok(Some(decoded))
@@ -359,14 +373,12 @@ impl EventDetails {
/// 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<E: RootEvent>(&self) -> Result<E, Error> {
let ev_metadata = self.event_metadata();
let pallet_bytes = &self.all_bytes[self.event_start_idx + 1..self.event_fields_end_idx];
let pallet = self.metadata.pallet(self.pallet_name())?;
let pallet_event_ty = pallet.event_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::EventNotFound(
pallet.index(),
self.variant_index(),
))
})?;
let pallet_event_ty = ev_metadata
.pallet
.event_ty_id()
.ok_or_else(|| MetadataError::EventTypeNotFoundInPallet(ev_metadata.pallet.index()))?;
E::root_event(
pallet_bytes,
@@ -377,6 +389,12 @@ impl EventDetails {
}
}
/// Details for the given event plucked from the metadata.
pub struct EventMetadataDetails<'a> {
pub pallet: PalletMetadata<'a>,
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// 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
@@ -406,7 +424,6 @@ pub(crate) mod test_utils {
RuntimeMetadataPrefixed,
};
use scale_info::{meta_type, TypeInfo};
use std::convert::TryFrom;
/// An "outer" events enum containing exactly one event.
#[derive(
@@ -511,7 +528,7 @@ pub(crate) mod test_utils {
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::try_from(runtime_metadata).unwrap()
Metadata::new(runtime_metadata.try_into().unwrap())
}
/// Build an `Events` object for test purposes, based on the details provided,
@@ -584,7 +601,7 @@ mod tests {
actual: EventDetails,
expected: TestRawEventDetails,
) {
let types = &metadata.runtime_metadata().types;
let types = &metadata.types();
// Make sure that the bytes handed back line up with the fields handed back;
// encode the fields back into bytes and they should be equal.
-92
View File
@@ -1,92 +0,0 @@
// 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.
use parking_lot::RwLock;
use std::{borrow::Cow, collections::HashMap};
/// A cache with the simple goal of storing 32 byte hashes against root+item keys
#[derive(Default, Debug)]
pub struct HashCache {
inner: RwLock<HashMap<RootItemKey<'static>, [u8; 32]>>,
}
impl HashCache {
/// get a hash out of the cache by its root and item key. If the item doesn't exist,
/// run the function provided to obtain a hash to insert (or bail with some error on failure).
pub fn get_or_insert<F, E>(&self, root: &str, item: &str, f: F) -> Result<[u8; 32], E>
where
F: FnOnce() -> Result<[u8; 32], E>,
{
let maybe_hash = self
.inner
.read()
.get(&RootItemKey::new(root, item))
.copied();
if let Some(hash) = maybe_hash {
return Ok(hash);
}
let hash = f()?;
self.inner
.write()
.insert(RootItemKey::new(root.to_string(), item.to_string()), hash);
Ok(hash)
}
}
/// This exists so that we can look items up in the cache using &strs, without having to allocate
/// Strings first (as you'd have to do to construct something like an `&(String,String)` key).
#[derive(Debug, PartialEq, Eq, Hash)]
struct RootItemKey<'a> {
pallet: Cow<'a, str>,
item: Cow<'a, str>,
}
impl<'a> RootItemKey<'a> {
fn new(pallet: impl Into<Cow<'a, str>>, item: impl Into<Cow<'a, str>>) -> Self {
RootItemKey {
pallet: pallet.into(),
item: item.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_cache_validation() {
let cache = HashCache::default();
let pallet = "System";
let item = "Account";
let mut call_number = 0;
let value = cache.get_or_insert(pallet, item, || -> Result<[u8; 32], ()> {
call_number += 1;
Ok([0; 32])
});
assert_eq!(
cache
.inner
.read()
.get(&RootItemKey::new(pallet, item))
.unwrap(),
&value.unwrap()
);
assert_eq!(value.unwrap(), [0; 32]);
assert_eq!(call_number, 1);
// Further calls must be hashed.
let value = cache.get_or_insert(pallet, item, || -> Result<[u8; 32], ()> {
call_number += 1;
Ok([0; 32])
});
assert_eq!(call_number, 1);
assert_eq!(value.unwrap(), [0; 32]);
}
}
-14
View File
@@ -1,14 +0,0 @@
// 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.
/// Locate an item of a known type in the metadata.
/// We should already know that the item we're looking
/// for is a call or event for instance, and then with this,
/// we can dig up details for that item in the metadata.
pub trait MetadataLocation {
/// The pallet in which the item lives.
fn pallet(&self) -> &str;
/// The name of the item.
fn item(&self) -> &str;
}
+19 -870
View File
@@ -2,888 +2,37 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::hash_cache::HashCache;
use codec::Error as CodecError;
use frame_metadata::{
v15::PalletConstantMetadata, v15::RuntimeMetadataV15, v15::StorageEntryMetadata,
RuntimeMetadata, RuntimeMetadataPrefixed, META_RESERVED,
};
use parking_lot::RwLock;
use scale_info::{form::PortableForm, PortableRegistry, Type};
use std::{collections::HashMap, convert::TryFrom, sync::Arc};
use std::sync::Arc;
/// Metadata error originated from inspecting the internal representation of the runtime metadata.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum MetadataError {
/// Module is not in metadata.
#[error("Pallet not found")]
PalletNotFound,
/// Pallet is not in metadata.
#[error("Pallet index {0} not found")]
PalletIndexNotFound(u8),
/// Call is not in metadata.
#[error("Call not found")]
CallNotFound,
/// Event is not in metadata.
#[error("Pallet {0}, Event {0} not found")]
EventNotFound(u8, u8),
/// Extrinsic is not in metadata.
#[error("Pallet {0}, Extrinsic {0} not found")]
ExtrinsicNotFound(u8, u8),
/// Event is not in metadata.
#[error("Pallet {0}, Error {0} not found")]
ErrorNotFound(u8, u8),
/// Runtime function is not in metadata.
#[error("Runtime function not found")]
RuntimeFnNotFound,
/// Storage is not in metadata.
#[error("Storage not found")]
StorageNotFound,
/// Storage type does not match requested type.
#[error("Storage type error")]
StorageTypeError,
/// Default error.
#[error("Failed to decode default: {0}")]
DefaultError(CodecError),
/// Failure to decode constant value.
#[error("Failed to decode constant value: {0}")]
ConstantValueError(CodecError),
/// Constant is not in metadata.
#[error("Constant not found")]
ConstantNotFound,
/// Type is not in metadata.
#[error("Type {0} missing from type registry")]
TypeNotFound(u32),
/// Runtime constant metadata is incompatible with the static one.
#[error("Pallet {0} Constant {0} has incompatible metadata")]
IncompatibleConstantMetadata(String, String),
/// Runtime call metadata is incompatible with the static one.
#[error("Pallet {0} Call {0} has incompatible metadata")]
IncompatibleCallMetadata(String, String),
/// Runtime storage metadata is incompatible with the static one.
#[error("Pallet {0} Storage {0} has incompatible metadata")]
IncompatibleStorageMetadata(String, String),
/// Runtime API metadata is incompatible with the static one.
#[error("Runtime API Trait {0} Method {0} has incompatible metadata")]
IncompatibleRuntimeApiMetadata(String, String),
/// Runtime metadata is not fully compatible with the static one.
#[error("Node metadata is not fully compatible")]
IncompatibleMetadata,
}
// We hide the innards behind an Arc so that it's easy to clone and share.
#[derive(Debug)]
struct MetadataInner {
metadata: RuntimeMetadataV15,
// Events are hashed by pallet an error index (decode oriented)
events: HashMap<(u8, u8), EventMetadata>,
// Extrinsics are hashed by pallet an error index (decode oriented)
extrinsics: HashMap<(u8, u8), ExtrinsicMetadata>,
// Errors are hashed by pallet and error index (decode oriented)
errors: HashMap<(u8, u8), ErrorMetadata>,
// Other pallet details are hashed by pallet name.
pallets: HashMap<String, PalletMetadata>,
// Type of the DispatchError type, which is what comes back if
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// Runtime API metadata
runtime_apis: HashMap<String, RuntimeFnMetadata>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
cached_metadata_hash: RwLock<Option<[u8; 32]>>,
cached_call_hashes: HashCache,
cached_constant_hashes: HashCache,
cached_storage_hashes: HashCache,
cached_runtime_hashes: HashCache,
}
/// A representation of the runtime metadata received from a node.
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
pub struct Metadata {
inner: Arc<MetadataInner>,
inner: Arc<subxt_metadata::Metadata>,
}
impl std::ops::Deref for Metadata {
type Target = subxt_metadata::Metadata;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Metadata {
/// Returns a reference to [`RuntimeFnMetadata`].
pub fn runtime_fn(&self, name: &str) -> Result<&RuntimeFnMetadata, MetadataError> {
self.inner
.runtime_apis
.get(name)
.ok_or(MetadataError::RuntimeFnNotFound)
}
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
.pallets
.get(name)
.ok_or(MetadataError::PalletNotFound)
}
/// 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
.inner
.events
.get(&(pallet_index, event_index))
.ok_or(MetadataError::EventNotFound(pallet_index, event_index))?;
Ok(event)
}
/// Returns the metadata for the extrinsic at the given pallet and call indices.
pub fn extrinsic(
&self,
pallet_index: u8,
call_index: u8,
) -> Result<&ExtrinsicMetadata, MetadataError> {
let event = self
.inner
.extrinsics
.get(&(pallet_index, call_index))
.ok_or(MetadataError::ExtrinsicNotFound(pallet_index, call_index))?;
Ok(event)
}
/// 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
.inner
.errors
.get(&(pallet_index, error_index))
.ok_or(MetadataError::ErrorNotFound(pallet_index, error_index))?;
Ok(error)
}
/// Return the DispatchError type ID if it exists.
pub fn dispatch_error_ty(&self) -> Option<u32> {
self.inner.dispatch_error_ty
}
/// Return the type registry embedded within the metadata.
pub fn types(&self) -> &PortableRegistry {
&self.inner.metadata.types
}
/// Resolve a type definition.
pub fn resolve_type(&self, id: u32) -> Option<&Type<PortableForm>> {
self.inner.metadata.types.resolve(id)
}
/// Return the runtime metadata.
pub fn runtime_metadata(&self) -> &RuntimeMetadataV15 {
&self.inner.metadata
}
/// Obtain the unique hash for a specific storage entry.
pub fn storage_hash(&self, pallet: &str, storage: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_storage_hashes
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage).map_err(
|e| match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
},
)
})
}
/// Obtain the unique hash for a constant.
pub fn constant_hash(&self, pallet: &str, constant: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_constant_hashes
.get_or_insert(pallet, constant, || {
subxt_metadata::get_constant_hash(&self.inner.metadata, pallet, constant).map_err(
|e| match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::ConstantNotFound,
},
)
})
}
/// Obtain the unique hash for a call.
pub fn call_hash(&self, pallet: &str, function: &str) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_call_hashes
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function).map_err(|e| {
match e {
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
/// Obtain the unique hash for a runtime API function.
pub fn runtime_api_hash(
&self,
trait_name: &str,
method_name: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_runtime_hashes
.get_or_insert(trait_name, method_name, || {
subxt_metadata::get_runtime_api_hash(&self.inner.metadata, trait_name, method_name)
.map_err(|_| MetadataError::RuntimeFnNotFound)
})
}
/// Obtain the unique hash for this metadata.
pub fn metadata_hash<T: AsRef<str>>(&self, pallets: &[T]) -> [u8; 32] {
if let Some(hash) = *self.inner.cached_metadata_hash.read() {
return hash;
pub(crate) fn new(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
let hash = subxt_metadata::MetadataHasher::new()
.only_these_pallets(pallets)
.hash(self.runtime_metadata());
*self.inner.cached_metadata_hash.write() = Some(hash);
hash
}
}
/// Metadata for a specific runtime API function.
#[derive(Clone, Debug)]
pub struct RuntimeFnMetadata {
/// The trait name of the runtime function.
trait_name: String,
/// The method name of the runtime function.
method_name: String,
/// The parameter name and type IDs interpreted as `scale_info::Field`
/// for ease of decoding.
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
/// The type ID of the return type.
return_id: u32,
}
impl RuntimeFnMetadata {
/// Get the parameters as fields.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Return the trait name of the runtime function.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Return the method name of the runtime function.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Get the type ID of the return type.
pub fn return_id(&self) -> u32 {
self.return_id
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata::new(md)
}
}
/// Metadata for a specific pallet.
#[derive(Clone, Debug)]
pub struct PalletMetadata {
index: u8,
name: String,
call_metadata: HashMap<String, CallMetadata>,
call_ty_id: Option<u32>,
event_ty_id: Option<u32>,
storage: HashMap<String, StorageEntryMetadata<PortableForm>>,
constants: HashMap<String, PalletConstantMetadata<PortableForm>>,
}
impl PalletMetadata {
/// Get the name of the pallet.
pub fn name(&self) -> &str {
&self.name
}
/// Get the index of this pallet.
pub fn index(&self) -> u8 {
self.index
}
/// If calls exist for this pallet, this returns the type ID of the variant
/// representing the different possible calls.
pub fn call_ty_id(&self) -> Option<u32> {
self.call_ty_id
}
/// If events exist for this pallet, this returns the type ID of the variant
/// representing the different possible events.
pub fn event_ty_id(&self) -> Option<u32> {
self.event_ty_id
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call(&self, function: &str) -> Result<&CallMetadata, MetadataError> {
let fn_index = self
.call_metadata
.get(function)
.ok_or(MetadataError::CallNotFound)?;
Ok(fn_index)
}
/// Return [`StorageEntryMetadata`] given some storage key.
pub fn storage(&self, key: &str) -> Result<&StorageEntryMetadata<PortableForm>, MetadataError> {
self.storage.get(key).ok_or(MetadataError::StorageNotFound)
}
/// Get a constant's metadata by name.
pub fn constant(
&self,
key: &str,
) -> Result<&PalletConstantMetadata<PortableForm>, MetadataError> {
self.constants
.get(key)
.ok_or(MetadataError::ConstantNotFound)
}
}
#[derive(Clone, Debug)]
pub struct CallMetadata {
call_index: u8,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
}
impl CallMetadata {
/// Index of this call.
pub fn index(&self) -> u8 {
self.call_index
}
/// The names, type names & types of each field in the call data.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
}
/// Metadata for specific events.
#[derive(Clone, Debug)]
pub struct EventMetadata {
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
event: String,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
impl EventMetadata {
/// Get the name of the pallet from which the event was emitted.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the pallet event which was emitted.
pub fn event(&self) -> &str {
&self.event
}
/// The names, type names & types of each field in the event.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Documentation for this event.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Metadata for specific extrinsics.
#[derive(Clone, Debug)]
pub struct ExtrinsicMetadata {
// The pallet name is shared across every extrinsic, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
call: String,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
impl ExtrinsicMetadata {
/// Get the name of the pallet from which the extrinsic was emitted.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// Get the name of the extrinsic call.
pub fn call(&self) -> &str {
&self.call
}
/// The names, type names & types of each field in the extrinsic.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
/// Documentation for this extrinsic.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Details about a specific runtime error.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
error: String,
docs: Vec<String>,
}
impl ErrorMetadata {
/// Get the name of the pallet from which the error originates.
pub fn pallet(&self) -> &str {
&self.pallet
}
/// The name of the error.
pub fn error(&self) -> &str {
&self.error
}
/// Documentation for the error.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Error originated from converting a runtime metadata [RuntimeMetadataPrefixed] to
/// the internal [Metadata] representation.
#[derive(Debug, thiserror::Error)]
pub enum InvalidMetadataError {
/// Invalid prefix
#[error("Invalid prefix")]
InvalidPrefix,
/// Invalid version
#[error("Invalid version")]
InvalidVersion,
/// Type missing from type registry
#[error("Type {0} missing from type registry")]
MissingType(u32),
/// Type missing extrinsic "Call" type
#[error("Missing extrinsic Call type")]
MissingCallType,
/// The extrinsic variant expected to contain a single field.
#[error("Extrinsic variant at index {0} expected to contain a single field")]
InvalidExtrinsicVariant(u8),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type")]
TypeDefNotVariant(u32),
}
impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
type Error = InvalidMetadataError;
fn try_from(metadata: RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
if metadata.0 != META_RESERVED {
return Err(InvalidMetadataError::InvalidPrefix);
}
let metadata = match metadata.1 {
RuntimeMetadata::V14(v14) => subxt_metadata::metadata_v14_to_latest(v14),
RuntimeMetadata::V15(v15) => v15,
_ => return Err(InvalidMetadataError::InvalidVersion),
};
let runtime_apis: HashMap<String, RuntimeFnMetadata> = metadata
.apis
.iter()
.flat_map(|trait_metadata| {
let trait_name = &trait_metadata.name;
trait_metadata
.methods
.iter()
.map(|method_metadata| {
// Function named used by substrate to identify the runtime call.
let fn_name = format!("{}_{}", trait_name, method_metadata.name);
// Parameters mapped as `scale_info::Field` to allow dynamic decoding.
let fields: Vec<_> = method_metadata
.inputs
.iter()
.map(|input| {
let name = input.name.clone();
let ty = input.ty.id;
scale_info::Field {
name: Some(name),
ty: ty.into(),
type_name: None,
docs: Default::default(),
}
})
.collect();
let return_id = method_metadata.output.id;
let metadata = RuntimeFnMetadata {
fields,
return_id,
trait_name: trait_name.clone(),
method_name: method_metadata.name.clone(),
};
(fn_name, metadata)
})
.collect::<Vec<_>>()
})
.collect();
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 event_ty_id = pallet.event.as_ref().map(|e| e.ty.id);
let call_metadata = pallet.calls.as_ref().map_or(Ok(HashMap::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(),
CallMetadata {
call_index: v.index,
fields: v.fields.clone(),
},
)
})
.collect();
Ok(call_indexes)
})?;
let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| {
storage
.entries
.iter()
.map(|entry| (entry.name.clone(), entry.clone()))
.collect()
});
let constants = pallet
.constants
.iter()
.map(|constant| (constant.name.clone(), constant.clone()))
.collect();
let pallet_metadata = PalletMetadata {
index: pallet.index,
name: pallet.name.to_string(),
call_metadata,
call_ty_id,
event_ty_id,
storage,
constants,
};
Ok((pallet.name.to_string(), pallet_metadata))
})
.collect::<Result<_, _>>()?;
let mut events = HashMap::<(u8, u8), EventMetadata>::new();
for pallet in &metadata.pallets {
if let Some(event) = &pallet.event {
let pallet_name: Arc<str> = pallet.name.to_string().into();
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.clone(),
fields: variant.fields.clone(),
docs: variant.docs.clone(),
},
);
}
}
}
let mut errors = HashMap::<(u8, u8), ErrorMetadata>::new();
for pallet in &metadata.pallets {
if let Some(error) = &pallet.error {
let pallet_name: Arc<str> = pallet.name.to_string().into();
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.clone(),
},
);
}
}
}
let dispatch_error_ty = metadata
.types
.types
.iter()
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id);
let extrinsic_ty = metadata
.types
.resolve(metadata.extrinsic.ty.id)
.ok_or(InvalidMetadataError::MissingType(metadata.extrinsic.ty.id))?;
let Some(call_id) = extrinsic_ty.type_params
.iter()
.find(|ty| ty.name == "Call")
.and_then(|ty| ty.ty)
.map(|ty| ty.id) else {
return Err(InvalidMetadataError::MissingCallType);
};
let call_type_variants = get_type_def_variant(call_id)?;
let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new();
for variant in &call_type_variants.variants {
let pallet_name: Arc<str> = variant.name.to_string().into();
let pallet_index = variant.index;
// Pallet variants must contain one single call variant.
// In the following form:
//
// enum RuntimeCall {
// Pallet(pallet_call)
// }
if variant.fields.len() != 1 {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
}
let Some(ty) = variant.fields.first() else {
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
};
// Get the call variant.
let call_type_variant = get_type_def_variant(ty.ty.id)?;
for variant in &call_type_variant.variants {
extrinsics.insert(
(pallet_index, variant.index),
ExtrinsicMetadata {
pallet: pallet_name.clone(),
call: variant.name.to_string(),
fields: variant.fields.clone(),
docs: variant.docs.clone(),
},
);
}
}
Ok(Metadata {
inner: Arc::new(MetadataInner {
metadata,
pallets,
events,
extrinsics,
errors,
dispatch_error_ty,
runtime_apis,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
cached_storage_hashes: Default::default(),
cached_runtime_hashes: Default::default(),
}),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_metadata::v15::{
ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, PalletStorageMetadata,
StorageEntryModifier, StorageEntryType,
};
use scale_info::{meta_type, TypeInfo};
fn load_metadata() -> Metadata {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(TypeInfo)]
enum Call {
fill_block { param: u128 },
}
let storage = PalletStorageMetadata {
prefix: "System",
entries: vec![StorageEntryMetadata {
name: "Account",
modifier: StorageEntryModifier::Optional,
ty: StorageEntryType::Plain(meta_type::<u32>()),
default: vec![0],
docs: vec![],
}],
};
let constant = PalletConstantMetadata {
name: "BlockWeights",
ty: meta_type::<u32>(),
value: vec![1, 2, 3],
docs: vec![],
};
let pallet = PalletMetadata {
index: 0,
name: "System",
calls: Some(PalletCallMetadata {
ty: meta_type::<Call>(),
}),
storage: Some(storage),
constants: vec![constant],
event: None,
error: None,
docs: vec![],
};
let metadata = RuntimeMetadataV15::new(
vec![pallet],
ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
},
meta_type::<()>(),
vec![],
);
let prefixed = RuntimeMetadataPrefixed::from(metadata);
Metadata::try_from(prefixed)
.expect("Cannot translate runtime metadata to internal Metadata")
}
#[test]
fn metadata_inner_cache() {
// Note: Dependency on test_runtime can be removed if complex metadata
// is manually constructed.
let metadata = load_metadata();
let hash = metadata.metadata_hash(&["System"]);
// Check inner caching.
assert_eq!(metadata.inner.cached_metadata_hash.read().unwrap(), hash);
// The cache `metadata.inner.cached_metadata_hash` is already populated from
// the previous call. Therefore, changing the pallets argument must not
// change the methods behavior.
let hash_old = metadata.metadata_hash(&["no-pallet"]);
assert_eq!(hash_old, hash);
}
#[test]
fn metadata_call_inner_cache() {
let metadata = load_metadata();
let hash = metadata.call_hash("System", "fill_block");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_call_hashes.get_or_insert(
"System",
"fill_block",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
}
#[test]
fn metadata_constant_inner_cache() {
let metadata = load_metadata();
let hash = metadata.constant_hash("System", "BlockWeights");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_constant_hashes.get_or_insert(
"System",
"BlockWeights",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
}
#[test]
fn metadata_storage_inner_cache() {
let metadata = load_metadata();
let hash = metadata.storage_hash("System", "Account");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_storage_hashes.get_or_insert(
"System",
"Account",
|| -> Result<[u8; 32], MetadataError> {
call_number += 1;
Ok([0; 32])
},
);
// Check function is never called (e.i, value fetched from cache).
assert_eq!(call_number, 0);
assert_eq!(hash.unwrap(), hash_cached.unwrap());
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::new)
}
}
+4 -9
View File
@@ -5,15 +5,10 @@
//! Types representing the metadata obtained from a node.
mod decode_encode_traits;
mod hash_cache;
mod metadata_location;
mod metadata_type;
pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata, EventMetadata, ExtrinsicMetadata, InvalidMetadataError, Metadata, MetadataError,
PalletMetadata, RuntimeFnMetadata,
};
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
+3 -9
View File
@@ -34,7 +34,6 @@
use std::sync::Arc;
use codec::{Decode, Encode};
use frame_metadata::RuntimeMetadataPrefixed;
use serde::Serialize;
use crate::{error::Error, utils::PhantomDataSendSync, Config, Metadata};
@@ -149,8 +148,7 @@ impl<T: Config> Rpc<T> {
.client
.request("state_getMetadata", rpc_params![at])
.await?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes[..])?;
let metadata: Metadata = meta.try_into()?;
let metadata = Metadata::decode(&mut &bytes[..])?;
Ok(metadata)
}
@@ -387,9 +385,7 @@ impl<T: Config> Rpc<T> {
let bytes = opaque.ok_or(Error::Other("Metadata version not found".into()))?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
let metadata: Metadata = Decode::decode(&mut &bytes.0[..])?;
Ok(metadata)
}
@@ -404,9 +400,7 @@ impl<T: Config> Rpc<T> {
let bytes: frame_metadata::OpaqueMetadata =
self.state_call("Metadata_metadata", None, None).await?;
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
let metadata: Metadata = meta.try_into()?;
let metadata: Metadata = Decode::decode(&mut &bytes.0[..])?;
Ok(metadata)
}
+61 -17
View File
@@ -8,6 +8,7 @@ use scale_value::Composite;
use std::borrow::Cow;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::{metadata::DecodeWithMetadata, Error, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
@@ -36,8 +37,11 @@ pub trait RuntimeApiPayload {
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API function name.
fn fn_name(&self) -> &str;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
@@ -63,7 +67,8 @@ pub trait RuntimeApiPayload {
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Clone, Debug)]
pub struct Payload<ArgsData, ReturnTy> {
fn_name: Cow<'static, str>,
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
@@ -74,16 +79,42 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
{
type ReturnType = ReturnTy;
fn fn_name(&self) -> &str {
&self.fn_name
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let fn_metadata = metadata.runtime_fn(&self.fn_name)?;
let api_trait = metadata
.runtime_api_trait_by_name(&self.trait_name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound((*self.trait_name).to_owned()))?;
let api_method = api_trait
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
// TODO [jsdw]: This isn't good. The options are:
// 1. Tweak the Metadata to store things in this way (not great, but
// it's what we did previously).
// 2. Tweak scale-encode's methods to accept iterators over
// { name: Option<&str>, type_id: u32 }, because they shouldn't
// need to allocate anyway. I'd like us to do this, and then we can
// remove allocations from this code and probably a couple of other
// places.
let fields: Vec<_> = api_method
.inputs()
.map(|input| scale_info::Field {
name: Some(input.name.to_owned()),
ty: input.ty.into(),
type_name: None,
docs: Default::default(),
})
.collect();
self.args_data
.encode_as_fields_to(fn_metadata.fields(), metadata.types(), out)?;
.encode_as_fields_to(&fields, metadata.types(), out)?;
Ok(())
}
@@ -97,9 +128,14 @@ pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(fn_name: impl Into<String>, args_data: ArgsData) -> Self {
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
) -> Self {
Payload {
fn_name: Cow::Owned(fn_name.into()),
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
@@ -112,12 +148,14 @@ impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
fn_name: &'static str,
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
fn_name: Cow::Borrowed(fn_name),
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
@@ -132,9 +170,14 @@ impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
}
}
/// Returns the function name.
pub fn fn_name(&self) -> &str {
&self.fn_name
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
@@ -145,8 +188,9 @@ impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
fn_name: impl Into<String>,
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload {
Payload::new(fn_name, args_data.into())
Payload::new(trait_name, method_name, args_data.into())
}
+23 -18
View File
@@ -2,7 +2,12 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{client::OnlineClientT, error::Error, metadata::DecodeWithMetadata, Config};
use crate::{
client::OnlineClientT,
error::{Error, MetadataError},
metadata::DecodeWithMetadata,
Config,
};
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
@@ -64,26 +69,25 @@ where
// which is a temporary thing we'll be throwing away quickly:
async move {
let metadata = client.metadata();
let function = payload.fn_name();
// Check if the function is present in the runtime metadata.
let fn_metadata = metadata.runtime_fn(function)?;
// Return type ID used for dynamic decoding.
let return_id = fn_metadata.return_id();
let api_trait = metadata
.runtime_api_trait_by_name(payload.trait_name())
.ok_or_else(|| {
MetadataError::RuntimeTraitNotFound(payload.trait_name().to_owned())
})?;
let api_method = api_trait
.method_by_name(payload.method_name())
.ok_or_else(|| {
MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned())
})?;
// Validate the runtime API payload hash against the compile hash from codegen.
if let Some(static_hash) = payload.validation_hash() {
let runtime_hash = metadata
.runtime_api_hash(fn_metadata.trait_name(), fn_metadata.method_name())?;
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if static_hash != runtime_hash {
return Err(
crate::metadata::MetadataError::IncompatibleRuntimeApiMetadata(
fn_metadata.trait_name().into(),
fn_metadata.method_name().into(),
)
.into(),
);
return Err(MetadataError::IncompatibleCodegen.into());
}
}
@@ -91,15 +95,16 @@ where
// For static payloads (codegen) this is pass-through, bytes are not altered.
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
let params = payload.encode_args(&metadata)?;
let call_name = format!("{}_{}", payload.trait_name(), payload.method_name());
let bytes = client
.rpc()
.state_call_raw(function, Some(params.as_slice()), Some(block_hash))
.state_call_raw(&call_name, Some(params.as_slice()), Some(block_hash))
.await?;
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
return_id,
api_method.output_ty(),
&metadata,
)?;
Ok(value)
+19 -9
View File
@@ -4,13 +4,13 @@
use crate::{
dynamic::{DecodedValueThunk, Value},
error::{Error, StorageAddressError},
error::{Error, MetadataError, StorageAddressError},
metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata},
utils::{Encoded, Static},
};
use frame_metadata::v15::{StorageEntryType, StorageHasher};
use scale_info::TypeDef;
use std::borrow::Cow;
use subxt_metadata::{StorageEntryType, StorageHasher};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
@@ -138,10 +138,17 @@ where
}
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let storage = pallet.storage(&self.entry_name)?;
let pallet = metadata
.pallet_by_name(self.pallet_name())
.ok_or_else(|| MetadataError::PalletNameNotFound(self.pallet_name().to_owned()))?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
match &storage.ty {
match entry.entry_type() {
StorageEntryType::Plain(_) => {
if !self.storage_entry_keys.is_empty() {
Err(StorageAddressError::WrongNumberOfKeys {
@@ -153,10 +160,13 @@ where
Ok(())
}
}
StorageEntryType::Map { hashers, key, .. } => {
StorageEntryType::Map {
hashers, key_ty, ..
} => {
let ty = metadata
.resolve_type(key.id)
.ok_or(StorageAddressError::TypeNotFound(key.id))?;
.types()
.resolve(*key_ty)
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
// If the key is a tuple, we encode each value to the corresponding tuple type.
// If the key is not a tuple, encode a single value to the key type.
@@ -164,7 +174,7 @@ where
TypeDef::Tuple(tuple) => {
either::Either::Left(tuple.fields.iter().map(|f| f.id))
}
_other => either::Either::Right(std::iter::once(key.id)),
_other => either::Either::Right(std::iter::once(*key_ty)),
};
if type_ids.len() != self.storage_entry_keys.len() {
+6 -2
View File
@@ -9,7 +9,7 @@ use super::{
use crate::{
client::{OfflineClientT, OnlineClientT},
error::Error,
error::{Error, MetadataError},
Config,
};
use derivative::Derivative;
@@ -43,7 +43,11 @@ where
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or storage entry in question do not exist at all).
pub fn validate<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
validate_storage_address(address, &self.client.metadata())
let metadata = self.client.metadata();
let pallet_metadata = metadata
.pallet_by_name(address.pallet_name())
.ok_or_else(|| MetadataError::PalletNameNotFound(address.pallet_name().to_owned()))?;
validate_storage_address(address, pallet_metadata)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
+57 -64
View File
@@ -5,15 +5,14 @@
use super::storage_address::{StorageAddress, Yes};
use crate::{
client::OnlineClientT,
error::Error,
error::{Error, MetadataError},
metadata::{DecodeWithMetadata, Metadata},
rpc::types::{StorageData, StorageKey},
Config,
};
use derivative::Derivative;
use frame_metadata::v15::StorageEntryType;
use scale_info::form::PortableForm;
use std::{future::Future, marker::PhantomData};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
/// Query the runtime storage.
#[derive(Derivative)]
@@ -94,22 +93,21 @@ where
{
let client = self.clone();
async move {
let metadata = client.client.metadata();
let (pallet, entry) =
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
validate_storage_address(address, &client.client.metadata())?;
validate_storage_address(address, pallet)?;
// Look up the return type ID to enable DecodeWithMetadata:
let metadata = client.client.metadata();
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
if let Some(data) = client.fetch_raw(&lookup_bytes).await? {
let val = decode_storage_with_metadata::<Address::Target>(
&mut &*data,
address.pallet_name(),
address.entry_name(),
&metadata,
)?;
let val =
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
Ok(Some(val))
} else {
Ok(None)
@@ -128,18 +126,17 @@ where
let client = self.clone();
async move {
let pallet_name = address.pallet_name();
let storage_name = address.entry_name();
let entry_name = address.entry_name();
// Metadata validation happens via .fetch():
if let Some(data) = client.fetch(address).await? {
Ok(data)
} else {
let metadata = client.client.metadata();
let (_pallet_metadata, storage_entry) =
lookup_entry_details(pallet_name, entry_name, &metadata)?;
// We have to dig into metadata already, so no point using the optimised `decode_storage_with_metadata` call.
let pallet_metadata = metadata.pallet(pallet_name)?;
let storage_metadata = pallet_metadata.storage(storage_name)?;
let return_ty_id = return_type_from_storage_entry_type(&storage_metadata.ty);
let bytes = &mut &storage_metadata.default[..];
let return_ty_id = return_type_from_storage_entry_type(storage_entry.entry_type());
let bytes = &mut storage_entry.default_bytes();
let val = Address::Target::decode_with_metadata(bytes, return_ty_id, &metadata)?;
Ok(val)
@@ -209,19 +206,20 @@ where
let client = self.clone();
let block_hash = self.block_hash;
async move {
let metadata = client.client.metadata();
let (pallet, entry) =
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
validate_storage_address(&address, &client.client.metadata())?;
let metadata = client.client.metadata();
validate_storage_address(&address, pallet)?;
// Look up the return type for flexible decoding. Do this once here to avoid
// potentially doing it every iteration if we used `decode_storage_with_metadata`
// in the iterator.
let return_type_id =
lookup_storage_return_type(&metadata, address.pallet_name(), address.entry_name())?;
let return_type_id = return_type_from_storage_entry_type(entry.entry_type());
// The root pallet/entry bytes for this storage entry:
let address_root_bytes = super::utils::storage_address_root_bytes(&address);
@@ -309,68 +307,63 @@ where
/// Validate a storage address against the metadata.
pub(crate) fn validate_storage_address<Address: StorageAddress>(
address: &Address,
metadata: &Metadata,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(address.pallet_name(), address.entry_name(), hash, metadata)?;
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Validate a storage entry against the metadata.
fn validate_storage(
/// Return details about the given storage entry.
fn lookup_entry_details<'a>(
pallet_name: &str,
storage_name: &str,
hash: [u8; 32],
metadata: &Metadata,
) -> Result<(), Error> {
let expected_hash = match metadata.storage_hash(pallet_name, storage_name) {
Ok(hash) => hash,
Err(e) => return Err(e.into()),
};
match expected_hash == hash {
true => Ok(()),
false => Err(crate::error::MetadataError::IncompatibleStorageMetadata(
pallet_name.into(),
storage_name.into(),
)
.into()),
}
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.to_owned()))?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// look up a return type ID for some storage entry.
fn lookup_storage_return_type(
metadata: &Metadata,
pallet: &str,
entry: &str,
) -> Result<u32, Error> {
let storage_entry_type = &metadata.pallet(pallet)?.storage(entry)?.ty;
Ok(return_type_from_storage_entry_type(storage_entry_type))
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into())
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Fetch the return type out of a [`StorageEntryType`].
fn return_type_from_storage_entry_type(entry: &StorageEntryType<PortableForm>) -> u32 {
fn return_type_from_storage_entry_type(entry: &StorageEntryType) -> u32 {
match entry {
StorageEntryType::Plain(ty) => ty.id,
StorageEntryType::Map { value, .. } => value.id,
StorageEntryType::Plain(ty) => *ty,
StorageEntryType::Map { value_ty, .. } => *value_ty,
}
}
/// Given some bytes, a pallet and storage name, decode the response.
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
pallet_name: &str,
storage_entry: &str,
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
let id = match ty {
StorageEntryType::Plain(ty) => ty.id,
StorageEntryType::Map { value, .. } => value.id,
};
let val = T::decode_with_metadata(bytes, id, metadata)?;
let ty = storage_metadata.entry_type();
let return_ty = return_type_from_storage_entry_type(ty);
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}
+10 -8
View File
@@ -11,7 +11,7 @@ use sp_core_hashing::blake2_256;
use crate::{
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, Hasher},
error::Error,
error::{Error, MetadataError},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
};
@@ -47,14 +47,16 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
Call: TxPayload,
{
if let Some(details) = call.validation_details() {
let metadata = self.client.metadata();
let expected_hash = metadata.call_hash(details.pallet_name, details.call_name)?;
let expected_hash = self
.client
.metadata()
.pallet_by_name(details.pallet_name)
.ok_or_else(|| MetadataError::PalletNameNotFound(details.pallet_name.to_owned()))?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
if details.hash != expected_hash {
return Err(crate::metadata::MetadataError::IncompatibleCallMetadata(
details.pallet_name.into(),
details.call_name.into(),
)
.into());
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
+13 -5
View File
@@ -5,7 +5,11 @@
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::{dynamic::Value, error::Error, metadata::Metadata};
use crate::{
dynamic::Value,
error::{Error, MetadataError},
metadata::Metadata,
};
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{Composite, ValueDef, Variant};
@@ -137,17 +141,21 @@ impl Payload<Composite<()>> {
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call = pallet.call(&self.call_name)?;
let pallet = metadata
.pallet_by_name(&self.pallet_name)
.ok_or_else(|| MetadataError::PalletNameNotFound((*self.pallet_name).to_owned()))?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
self.call_data
.encode_as_fields_to(call.fields(), metadata.types(), out)?;
.encode_as_fields_to(&call.fields, metadata.types(), out)?;
Ok(())
}