mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-11-08 19:57:06 +00:00
[v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)
* Allow visiting extrinsic fields * fmt * Don't use local scale-decode dep * Clippy and tidy * Extend 'subxt codegen' CLI to work with legacy metadatas * Simplify historic extrinsics example now that AccountId32s have paths/names * clippy * clippy * clippy.. * Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime * Try to fix flaky test * Add custom value decode to extrinsics example * Remove useless else branch ra thought I needed * Simplify examples
This commit is contained in:
1 parent
1a0e218d32
commit
3bbba1b005
16 files changed
+871
-37
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
|
||||
use crate::error::ExtrinsicCallError;
|
||||
use crate::utils::Either;
|
||||
use crate::utils::{AnyResolver, AnyTypeId};
|
||||
use scale_info_legacy::{LookupName, TypeRegistrySet};
|
||||
|
||||
/// This represents the call data in the extrinsic.
|
||||
@@ -53,6 +54,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> {
|
||||
pub struct ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
|
||||
resolver: AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
@@ -60,7 +62,16 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
|
||||
) -> Self {
|
||||
Self { all_bytes, info }
|
||||
let resolver = match info {
|
||||
AnyExtrinsicInfo::Legacy(info) => AnyResolver::B(info.resolver),
|
||||
AnyExtrinsicInfo::Current(info) => AnyResolver::A(info.resolver),
|
||||
};
|
||||
|
||||
Self {
|
||||
all_bytes,
|
||||
info,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the bytes representing the fields stored in this extrinsic.
|
||||
@@ -74,11 +85,12 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
}
|
||||
|
||||
/// Iterate over each of the fields of the extrinsic call data.
|
||||
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'extrinsics, 'atblock>> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'_, 'extrinsics, 'atblock>> {
|
||||
match &self.info {
|
||||
AnyExtrinsicInfo::Legacy(info) => {
|
||||
Either::A(info.info.call_data().map(|named_arg| ExtrinsicCallField {
|
||||
field_bytes: &self.all_bytes[named_arg.range()],
|
||||
resolver: &self.resolver,
|
||||
info: AnyExtrinsicCallFieldInfo::Legacy(ExtrinsicCallFieldInfo {
|
||||
info: named_arg,
|
||||
resolver: info.resolver,
|
||||
@@ -88,6 +100,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
AnyExtrinsicInfo::Current(info) => {
|
||||
Either::B(info.info.call_data().map(|named_arg| ExtrinsicCallField {
|
||||
field_bytes: &self.all_bytes[named_arg.range()],
|
||||
resolver: &self.resolver,
|
||||
info: AnyExtrinsicCallFieldInfo::Current(ExtrinsicCallFieldInfo {
|
||||
info: named_arg,
|
||||
resolver: info.resolver,
|
||||
@@ -119,9 +132,10 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
|
||||
field_bytes: &'extrinsics [u8],
|
||||
info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>,
|
||||
resolver: &'fields AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> {
|
||||
@@ -144,7 +158,7 @@ macro_rules! with_call_field_info {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
|
||||
/// Get the raw bytes for this field.
|
||||
pub fn bytes(&self) -> &'extrinsics [u8] {
|
||||
self.field_bytes
|
||||
@@ -155,6 +169,22 @@ impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
with_call_field_info!(&self.info => info.info.name())
|
||||
}
|
||||
|
||||
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
|
||||
/// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes
|
||||
/// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`].
|
||||
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
|
||||
&self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value<'extrinsics, 'fields>, V::Error> {
|
||||
let type_id = match &self.info {
|
||||
AnyExtrinsicCallFieldInfo::Current(info) => AnyTypeId::A(*info.info.ty()),
|
||||
AnyExtrinsicCallFieldInfo::Legacy(info) => AnyTypeId::B(info.info.ty().clone()),
|
||||
};
|
||||
let cursor = &mut self.bytes();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(cursor, type_id, self.resolver, visitor)
|
||||
}
|
||||
|
||||
/// Attempt to decode the value of this field into the given type.
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
|
||||
with_call_field_info!(&self.info => {
|
||||
|
||||
@@ -110,6 +110,3 @@ impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> {
|
||||
ExtrinsicTransactionParams::new(self.bytes, self.info)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add extrinsic.call() with .bytes, and .decode function to make it easy to decode call fields into Value or whatever.
|
||||
// Then add this to the example. Make sure we can do everything that dot-block-decoder does easily.
|
||||
@@ -20,3 +20,8 @@ pub use error::Error;
|
||||
pub mod ext {
|
||||
pub use futures::stream::{Stream, StreamExt};
|
||||
}
|
||||
|
||||
/// Helper types that could be useful.
|
||||
pub mod helpers {
|
||||
pub use crate::utils::{AnyResolver, AnyResolverError, AnyTypeId};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::storage_info::AnyStorageInfo;
|
||||
use super::storage_info::with_info;
|
||||
use crate::error::StorageValueError;
|
||||
use crate::utils::{AnyResolver, AnyTypeId};
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
@@ -9,12 +10,22 @@ use std::sync::Arc;
|
||||
pub struct StorageValue<'atblock> {
|
||||
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
|
||||
bytes: Cow<'atblock, [u8]>,
|
||||
resolver: AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
impl<'atblock> StorageValue<'atblock> {
|
||||
/// Create a new storage value.
|
||||
pub fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
|
||||
Self { info, bytes }
|
||||
pub(crate) fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
|
||||
let resolver = match &*info {
|
||||
AnyStorageInfo::Current(info) => AnyResolver::A(info.resolver),
|
||||
AnyStorageInfo::Legacy(info) => AnyResolver::B(info.resolver),
|
||||
};
|
||||
|
||||
Self {
|
||||
info,
|
||||
bytes,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage value.
|
||||
@@ -27,6 +38,22 @@ impl<'atblock> StorageValue<'atblock> {
|
||||
self.bytes.to_vec()
|
||||
}
|
||||
|
||||
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
|
||||
/// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes
|
||||
/// and has access to more type information than is available via [`StorageValue::decode_as`].
|
||||
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
|
||||
&self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value<'_, '_>, V::Error> {
|
||||
let type_id = match &*self.info {
|
||||
AnyStorageInfo::Current(info) => AnyTypeId::A(info.info.value_id),
|
||||
AnyStorageInfo::Legacy(info) => AnyTypeId::B(info.info.value_id.clone()),
|
||||
};
|
||||
let cursor = &mut self.bytes();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(cursor, type_id, &self.resolver, visitor)
|
||||
}
|
||||
|
||||
/// Decode this storage value.
|
||||
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
with_info!(info = &*self.info => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
mod any_resolver;
|
||||
mod either;
|
||||
mod range_map;
|
||||
|
||||
pub use any_resolver::{AnyResolver, AnyResolverError, AnyTypeId};
|
||||
pub use either::Either;
|
||||
pub use range_map::RangeMap;
|
||||
@@ -0,0 +1,188 @@
|
||||
use super::Either;
|
||||
use scale_info_legacy::LookupName;
|
||||
use scale_type_resolver::ResolvedTypeVisitor;
|
||||
|
||||
/// A type resolver which could either be for modern or historic resolving.
|
||||
pub type AnyResolver<'resolver> = Either<
|
||||
&'resolver scale_info::PortableRegistry,
|
||||
&'resolver scale_info_legacy::TypeRegistrySet<'resolver>,
|
||||
>;
|
||||
|
||||
/// A type ID which is either a modern or historic ID.
|
||||
pub type AnyTypeId = Either<u32, scale_info_legacy::LookupName>;
|
||||
|
||||
impl Default for AnyTypeId {
|
||||
fn default() -> Self {
|
||||
// Not a sensible default, but we don't need / can't provide a sensible one.
|
||||
AnyTypeId::A(u32::MAX)
|
||||
}
|
||||
}
|
||||
impl From<u32> for AnyTypeId {
|
||||
fn from(value: u32) -> Self {
|
||||
AnyTypeId::A(value)
|
||||
}
|
||||
}
|
||||
impl From<LookupName> for AnyTypeId {
|
||||
fn from(value: LookupName) -> Self {
|
||||
AnyTypeId::B(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<AnyTypeId> for u32 {
|
||||
type Error = ();
|
||||
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
AnyTypeId::A(v) => Ok(v),
|
||||
AnyTypeId::B(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<AnyTypeId> for LookupName {
|
||||
type Error = ();
|
||||
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
AnyTypeId::A(_) => Err(()),
|
||||
AnyTypeId::B(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A resolve error that comes from using [`AnyResolver`] to resolve some [`AnyTypeId`] into a type.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AnyResolverError {
|
||||
#[error("got a {got} type ID but expected a {expected} type ID")]
|
||||
TypeIdMismatch {
|
||||
got: &'static str,
|
||||
expected: &'static str,
|
||||
},
|
||||
#[error("{0}")]
|
||||
ScaleInfo(scale_type_resolver::portable_registry::Error),
|
||||
#[error("{0}")]
|
||||
ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError),
|
||||
}
|
||||
|
||||
impl<'resolver> scale_type_resolver::TypeResolver for AnyResolver<'resolver> {
|
||||
type TypeId = AnyTypeId;
|
||||
type Error = AnyResolverError;
|
||||
|
||||
fn resolve_type<'this, V: ResolvedTypeVisitor<'this, TypeId = Self::TypeId>>(
|
||||
&'this self,
|
||||
type_id: Self::TypeId,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error> {
|
||||
match (self, type_id) {
|
||||
(Either::A(resolver), Either::A(id)) => resolver
|
||||
.resolve_type(id, ModernVisitor(visitor))
|
||||
.map_err(AnyResolverError::ScaleInfo),
|
||||
(Either::B(resolver), Either::B(id)) => resolver
|
||||
.resolve_type(id, LegacyVisitor(visitor))
|
||||
.map_err(AnyResolverError::ScaleInfoLegacy),
|
||||
(Either::A(_), Either::B(_)) => Err(AnyResolverError::TypeIdMismatch {
|
||||
got: "LookupName",
|
||||
expected: "u32",
|
||||
}),
|
||||
(Either::B(_), Either::A(_)) => Err(AnyResolverError::TypeIdMismatch {
|
||||
got: "u32",
|
||||
expected: "LookupName",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to have a visitor which understands only modern or legacy types, and can wrap the more generic visitor
|
||||
// that must be provided to AnyResolver::resolve_type. This then allows us to visit historic _or_ modern types
|
||||
// using the single visitor provided by the user.
|
||||
struct LegacyVisitor<V>(V);
|
||||
struct ModernVisitor<V>(V);
|
||||
|
||||
mod impls {
|
||||
use super::{AnyTypeId, LegacyVisitor, LookupName, ModernVisitor};
|
||||
use scale_type_resolver::*;
|
||||
|
||||
// An ugly implementation which maps from modern or legacy types into our AnyTypeId,
|
||||
// to make LegacyVisitor and ModernVisitor valid visitors when wrapping a generic "any" visitor.
|
||||
macro_rules! impl_visitor_mapper {
|
||||
($struc:ident, $type_id_ty:ident, $variant:ident) => {
|
||||
impl<'this, V> ResolvedTypeVisitor<'this> for $struc<V>
|
||||
where
|
||||
V: ResolvedTypeVisitor<'this, TypeId = AnyTypeId>,
|
||||
{
|
||||
type TypeId = $type_id_ty;
|
||||
type Value = V::Value;
|
||||
|
||||
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
|
||||
self.0.visit_unhandled(kind)
|
||||
}
|
||||
fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
|
||||
self.0.visit_array(AnyTypeId::$variant(type_id), len)
|
||||
}
|
||||
fn visit_not_found(self) -> Self::Value {
|
||||
self.0.visit_not_found()
|
||||
}
|
||||
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
Fields: FieldIter<'this, Self::TypeId>,
|
||||
{
|
||||
self.0.visit_composite(
|
||||
path,
|
||||
fields.map(|field| Field {
|
||||
name: field.name,
|
||||
id: AnyTypeId::$variant(field.id),
|
||||
}),
|
||||
)
|
||||
}
|
||||
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
Fields: FieldIter<'this, Self::TypeId>,
|
||||
Var: VariantIter<'this, Fields>,
|
||||
{
|
||||
self.0.visit_variant(
|
||||
path,
|
||||
variants.map(|variant| Variant {
|
||||
index: variant.index,
|
||||
name: variant.name,
|
||||
fields: variant.fields.map(|field| Field {
|
||||
name: field.name,
|
||||
id: AnyTypeId::$variant(field.id),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
fn visit_sequence<Path>(self, path: Path, type_id: Self::TypeId) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
{
|
||||
self.0.visit_sequence(path, AnyTypeId::$variant(type_id))
|
||||
}
|
||||
|
||||
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
|
||||
where
|
||||
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
|
||||
{
|
||||
self.0
|
||||
.visit_tuple(type_ids.map(|id| AnyTypeId::$variant(id)))
|
||||
}
|
||||
|
||||
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
|
||||
self.0.visit_primitive(primitive)
|
||||
}
|
||||
|
||||
fn visit_compact(self, type_id: Self::TypeId) -> Self::Value {
|
||||
self.0.visit_compact(AnyTypeId::$variant(type_id))
|
||||
}
|
||||
|
||||
fn visit_bit_sequence(
|
||||
self,
|
||||
store_format: BitsStoreFormat,
|
||||
order_format: BitsOrderFormat,
|
||||
) -> Self::Value {
|
||||
self.0.visit_bit_sequence(store_format, order_format)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_visitor_mapper!(ModernVisitor, u32, A);
|
||||
impl_visitor_mapper!(LegacyVisitor, LookupName, B);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
macro_rules! either {
|
||||
($name:ident( $fst:ident, $($variant:ident),* )) => {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum $name<$fst, $($variant),*> {
|
||||
$fst($fst),
|
||||
$($variant($variant),)*
|
||||
|
||||
Reference in new issue
Block a user