Second pass over Address type and start impl in Subxt

This commit is contained in:
James Wilson
2025-09-26 15:20:22 +01:00
parent 9243ea739e
commit 331c54063f
13 changed files with 490 additions and 274 deletions
Generated
+1
View File
@@ -5566,6 +5566,7 @@ dependencies = [
"bitvec",
"derive-where",
"either",
"frame-decode",
"frame-metadata 23.0.0",
"futures",
"hex",
+2
View File
@@ -27,6 +27,8 @@ std = [
"tracing/std",
"impl-serde/std",
"primitive-types/std",
"sp-core/std",
"sp-keyring/std",
"sp-crypto-hashing/std",
]
+1 -1
View File
@@ -41,7 +41,7 @@ impl<T: Config> Extrinsics<T> {
// Try to decode the extrinsic.
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
metadata.deref(),
&metadata,
metadata.types(),
)
.map_err(|error| BlockError::ExtrinsicDecodeError {
+86
View File
@@ -6,6 +6,19 @@ use crate::error::MetadataError;
use alloc::borrow::ToOwned;
use alloc::sync::Arc;
use frame_decode::extrinsics::{
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoError,
ExtrinsicSignatureInfo,
};
use frame_decode::storage::{
StorageEntry, StorageInfo, StorageInfoError
};
use frame_decode::runtime_apis::{
RuntimeApi, RuntimeApiInfo, RuntimeApiInfoError
};
use frame_decode::view_functions::{
ViewFunction, ViewFunctionInfo, ViewFunctionInfoError
};
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
@@ -20,6 +33,79 @@ impl core::ops::Deref for Metadata {
}
}
impl frame_decode::storage::StorageTypeInfo for Metadata {
type TypeId = u32;
fn storage_info(
&self,
pallet_name: &str,
storage_entry: &str,
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
self.inner.storage_info(pallet_name, storage_entry)
}
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
self.inner.storage_entries()
}
}
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
type TypeId = u32;
fn runtime_api_info(
&self,
trait_name: &str,
method_name: &str,
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
self.inner.runtime_api_info(trait_name, method_name)
}
fn runtime_apis(&self) -> impl Iterator<Item = RuntimeApi<'_>> {
self.inner.runtime_apis()
}
}
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
type TypeId = u32;
fn extrinsic_call_info(
&self,
pallet_index: u8,
call_index: u8,
) -> Result<ExtrinsicCallInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_call_info(pallet_index, call_index)
}
fn extrinsic_signature_info(
&self,
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_signature_info()
}
fn extrinsic_extension_info(
&self,
extension_version: Option<u8>,
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_extension_info(extension_version)
}
}
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
type TypeId = u32;
fn view_function_info(
&self,
pallet_name: &str,
function_name: &str,
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
self.inner.view_function_info(pallet_name, function_name)
}
fn view_functions(&self) -> impl Iterator<Item = ViewFunction<'_>> {
self.inner.view_functions()
}
}
impl Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
+38 -166
View File
@@ -5,17 +5,28 @@
//! Construct addresses to access storage entries with.
use alloc::borrow::Cow;
use alloc::vec::Vec;
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
use frame_decode::storage::IntoEncodableValues;
use scale_decode::DecodeAsType;
use crate::utils::Maybe;
/// A storage address. Concrete addresses are expected to implement either [`FetchableAddress`]
/// or [`IterableAddress`], which extends this to define fetchable and iterable storage keys.
/// A storage address. This allows access to a given storage entry, which can then
/// be iterated over or fetched from by providing the relevant set of keys, or
/// otherwise inspected.
pub trait Address {
/// A set of types we'll hash and append to the prefix to build the storage key.
/// All of the keys required to get to an individual value at this address.
/// Keys must always impl [`IntoEncodableValues`], and for iteration must
/// also impl [`frame_decode::storage::IntoDecodableValues`].
type KeyParts: IntoEncodableValues;
/// Type of the storage value at this location.
type Value: DecodeAsType;
/// Does the address have a default value defined for it.
/// Set to [`crate::utils::Yes`] to enable APIs which require one,
/// or [`crate::utils::Maybe`] to enable APIs which allow one
type HasDefaultValue;
/// Does the address point to a map (as opposed to a plain value)?
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
type IsMap;
/// The pallet containing this storage entry.
fn pallet_name(&self) -> &str;
@@ -23,69 +34,35 @@ pub trait Address {
/// The name of the storage entry.
fn entry_name(&self) -> &str;
/// Return the input key parts needed to point to this storage entry / entries.
fn key_parts(&self) -> impl IntoEncodableValues;
/// Return a unique hash for this address which can be used to validate it against metadata.
fn validation_hash(&self) -> Option<[u8; 32]>;
}
/// This trait represents any storage address which points to a single value we can fetch.
pub trait FetchableAddress: Address {
/// Does the address have a default value defined for it.
/// Set to [`Yes`] to enable APIs which require one.
type HasDefaultValue;
}
/// This trait represents any storage address which points to multiple 0 or more values to iterate over.
pub trait IterableAddress: Address {
/// The storage key values that we'll decode for each value
type OutputKeys: IntoDecodableValues;
}
/// An address which points to an individual storage value.
pub struct StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
/// An address which is generated by the static APIs.
pub struct StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
key_parts: KeyParts,
validation_hash: Option<[u8; 32]>,
marker: core::marker::PhantomData<(Value, HasDefaultValue)>,
marker: core::marker::PhantomData<(KeyParts, Value, HasDefaultValue, IsMap)>,
}
impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
/// Create a new [`StaticFetchableAddress`] using static strings for the pallet and call name.
impl<KeyParts, Value, HasDefaultValue, IsMap> StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
/// Create a new [`StaticAddress`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
key_parts: KeyParts,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
key_parts,
validation_hash: Some(hash),
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticFetchableAddress`].
pub fn new(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
key_parts: KeyParts,
) -> Self {
Self {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
key_parts,
validation_hash: None,
marker: core::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
@@ -93,105 +70,16 @@ impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, H
}
}
impl<KeyParts, Value, HasDefaultValue> Address
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
impl<KeyParts, Value, HasDefaultValue, IsMap> Address
for StaticAddress<KeyParts, Value, HasDefaultValue, IsMap>
where
KeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type KeyParts = KeyParts;
type Value = Value;
fn key_parts(&self) -> impl IntoEncodableValues {
&self.key_parts
}
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
impl<KeyParts, Value, HasDefaultValue> FetchableAddress
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
where
KeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type HasDefaultValue = HasDefaultValue;
}
/// An address which points to a set of storage values.
pub struct StaticIterableAddress<InputKeyParts, OutputKeyParts, Value> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
input_key_parts: InputKeyParts,
validation_hash: Option<[u8; 32]>,
marker: core::marker::PhantomData<(OutputKeyParts, Value)>,
}
impl<InputKeyParts, OutputKeyParts, Value>
StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
{
/// Create a new [`StaticIterableAddress`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
input_key_parts: InputKeyParts,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
input_key_parts,
validation_hash: Some(hash),
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticIterableAddress`].
pub fn new(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
input_key_parts: InputKeyParts,
) -> Self {
Self {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
input_key_parts,
validation_hash: None,
marker: core::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
self
}
}
impl<InputKeyParts, OutputKeyParts, Value> Address
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
where
InputKeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type KeyParts = InputKeyParts;
type Value = Value;
fn key_parts(&self) -> impl IntoEncodableValues {
&self.input_key_parts
}
type IsMap = IsMap;
fn pallet_name(&self) -> &str {
&self.pallet_name
@@ -206,38 +94,22 @@ where
}
}
impl<InputKeyParts, OutputKeyParts, Value> IterableAddress
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
where
InputKeyParts: IntoEncodableValues,
OutputKeyParts: IntoDecodableValues,
Value: DecodeAsType,
{
type OutputKeys = OutputKeyParts;
}
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
/// entry could be a map and could have a default value.
pub type DynamicAddress<KeyParts, Value> = StaticAddress<KeyParts, Value, Maybe, Maybe>;
/// Construct a new dynamic storage fetch address.
pub fn dynamic_fetch<Keys: IntoEncodableValues>(
/// Construct a new dynamic storage address. You can define the type of the
/// storage keys and value yourself here, but have no guarantee that they will
/// be correct.
pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
storage_entry_keys: Keys,
) -> impl FetchableAddress {
StaticFetchableAddress::<Keys, scale_value::Value, ()>::new(
pallet_name,
entry_name,
storage_entry_keys,
)
) -> DynamicAddress<KeyParts, Value> {
DynamicAddress::<KeyParts, Value> {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
validation_hash: None,
marker: core::marker::PhantomData
}
}
/// Construct a new dynamic storage iter address.
pub fn dynamic_iter<Keys: IntoEncodableValues>(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
storage_entry_keys: Keys,
) -> impl IterableAddress {
StaticIterableAddress::<Keys, Vec<scale_value::Value>, scale_value::Value>::new(
pallet_name,
entry_name,
storage_entry_keys,
)
}
+7 -2
View File
@@ -41,6 +41,8 @@
//! println!("Alice's account info: {value:?}");
//! ```
mod prefix_of;
pub mod address;
use crate::{
@@ -52,6 +54,8 @@ use alloc::vec::Vec;
use frame_decode::storage::StorageTypeInfo;
use scale_decode::IntoVisitor;
pub use prefix_of::{ EqualOrPrefixOf, PrefixOf };
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the storage value is the same as the shape expected by the static address.
///
@@ -78,14 +82,15 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
/// Given a storage address and some metadata, this encodes the address into bytes which can be
/// handed to a node to retrieve the corresponding value.
pub fn get_address_bytes<Addr: Address>(
pub fn get_address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(
address: &Addr,
metadata: &Metadata,
keys: Keys,
) -> Result<Vec<u8>, Error> {
frame_decode::storage::encode_storage_key(
address.pallet_name(),
address.entry_name(),
&address.key_parts(),
&keys,
&**metadata,
metadata.types(),
)
+197
View File
@@ -0,0 +1,197 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use frame_decode::helpers::IntoEncodableValues;
use scale_encode::EncodeAsType;
/// For a given set of values that can be used as keys for a storage entry,
/// this is implemented for any prefixes of that set. ie if the keys `(A,B,C)`
/// would access a storage value, then `PrefixOf<(A,B,C)>` is implemented for
/// `(A,B)`, `(A,)` and `()`.
pub trait PrefixOf<Keys>: IntoEncodableValues {}
// Any reference which impls PrefixOf<K> also impls PrefixOf<K>
impl <'a, K, T: PrefixOf<K>> PrefixOf<K> for &'a T {}
// Impls for tuples up to length 6 (storage maps rarely require more than 2 entries
// so it's very unlikely we'll ever need to go this deep).
impl <A> PrefixOf<(A,)> for () {}
impl <A, B> PrefixOf<(A,B)> for () {}
impl <A, B> PrefixOf<(A, B)> for (A,)
where (A,): IntoEncodableValues {}
impl <A, B, C> PrefixOf<(A, B, C)> for () {}
impl <A, B, C> PrefixOf<(A, B, C)> for (A,)
where (A,): IntoEncodableValues {}
impl <A, B, C> PrefixOf<(A, B, C)> for (A, B)
where (A, B): IntoEncodableValues {}
impl <A, B, C, D> PrefixOf<(A, B, C, D)> for () {}
impl <A, B, C, D> PrefixOf<(A, B, C, D)> for (A,)
where (A,): IntoEncodableValues {}
impl <A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B)
where (A, B): IntoEncodableValues {}
impl <A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B, C)
where (A, B, C): IntoEncodableValues {}
impl <A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for () {}
impl <A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A,)
where (A,): IntoEncodableValues {}
impl <A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B)
where (A, B): IntoEncodableValues {}
impl <A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C)
where (A, B, C): IntoEncodableValues {}
impl <A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C, D)
where (A, B, C, D): IntoEncodableValues {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for () {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A,)
where (A,): IntoEncodableValues {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B)
where (A, B): IntoEncodableValues {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C)
where (A, B, C): IntoEncodableValues {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D)
where (A, B, C, D): IntoEncodableValues {}
impl <A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D, E)
where (A, B, C, D, E): IntoEncodableValues {}
// Vecs are prefixes of vecs. The length is not statically known and so
// these would be given dynamically only, leaving the correct length to the user.
impl <T: EncodeAsType> PrefixOf<Vec<T>> for Vec<T> {}
// We don't use arrays in Subxt for storage entry access, but `IntoEncodableValues`
// supports them so let's allow impls which do use them to benefit too.
macro_rules! array_impl {
($n:literal: $($p:literal)+) => {
$(
impl <T: EncodeAsType> PrefixOf<[T; $n]> for [T; $p] {}
)+
}
}
array_impl!(1: 0);
array_impl!(2: 1 0);
array_impl!(3: 2 1 0);
array_impl!(4: 3 2 1 0);
array_impl!(5: 4 3 2 1 0);
array_impl!(6: 5 4 3 2 1 0);
/// This is much like [`PrefixOf`] except that it also includes `Self` as an allowed type,
/// where `Self` must impl [`IntoEncodableValues`] just as every [`PrefixOf<Self>`] does.
pub trait EqualOrPrefixOf<K>: IntoEncodableValues {}
// Tuples
macro_rules! tuple_impl_eq {
($($t:ident)+) => {
// Any T that is a PrefixOf<Keys> impls EqualOrPrefixOf<keys> too
impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {}
// Keys impls EqualOrPrefixOf<Keys>
impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {}
// &'a Keys impls EqualOrPrefixOf<Keys>
impl <'a, $($t),+> EqualOrPrefixOf<($($t,)+)> for &'a ($($t,)+) where ($($t,)+): IntoEncodableValues {}
}
}
tuple_impl_eq!(A);
tuple_impl_eq!(A B);
tuple_impl_eq!(A B C);
tuple_impl_eq!(A B C D);
tuple_impl_eq!(A B C D E);
tuple_impl_eq!(A B C D E F);
// Vec
impl <T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for Vec<T> {}
impl <'a, T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &'a Vec<T> {}
// Arrays
macro_rules! array_impl_eq {
($($n:literal)+) => {
$(
impl <A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for [A; $n] {}
impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {}
)+
}
}
impl <const N: usize, A, T> EqualOrPrefixOf<[A; N]> for T where T: PrefixOf<[A; N]> {}
array_impl_eq!(1 2 3 4 5 6);
#[cfg(test)]
mod test {
use super::*;
struct Test<Keys: IntoEncodableValues>(core::marker::PhantomData<Keys>);
impl <Keys: IntoEncodableValues> Test<Keys> {
fn new() -> Self {
Test(core::marker::PhantomData)
}
fn accepts_prefix_of<P: PrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
fn accepts_eq_or_prefix_of<P: EqualOrPrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
}
#[test]
fn test_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
//// This shouldn't work:
// t.accepts_prefix_of((true, String::from("hi"), 0));
t.accepts_prefix_of(&(true, String::from("hi")));
t.accepts_prefix_of((true, String::from("hi")));
t.accepts_prefix_of((true,));
t.accepts_prefix_of(());
let t = Test::<[u64; 5]>::new();
//// This shouldn't work:
// t.accepts_prefix_of([0,1,2,3,4]);
t.accepts_prefix_of(&[0,1,2,3]);
t.accepts_prefix_of([0,1,2,3]);
t.accepts_prefix_of([0,1,2]);
t.accepts_prefix_of([0,1]);
t.accepts_prefix_of([0]);
t.accepts_prefix_of([]);
}
#[test]
fn test_eq_or_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
t.accepts_eq_or_prefix_of(&(true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of(&(true, String::from("hi")));
t.accepts_eq_or_prefix_of(&(true,));
t.accepts_eq_or_prefix_of(&());
t.accepts_eq_or_prefix_of((true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of((true, String::from("hi")));
t.accepts_eq_or_prefix_of((true,));
t.accepts_eq_or_prefix_of(());
let t = Test::<[u64; 5]>::new();
t.accepts_eq_or_prefix_of(&[0,1,2,3,4]);
t.accepts_eq_or_prefix_of(&[0,1,2,3]);
t.accepts_eq_or_prefix_of(&[0,1,2]);
t.accepts_eq_or_prefix_of(&[0,1]);
t.accepts_eq_or_prefix_of(&[0,]);
t.accepts_eq_or_prefix_of(&[]);
t.accepts_eq_or_prefix_of([0,1,2,3,4]);
t.accepts_eq_or_prefix_of([0,1,2,3]);
t.accepts_eq_or_prefix_of([0,1,2]);
t.accepts_eq_or_prefix_of([0,1]);
t.accepts_eq_or_prefix_of([0]);
t.accepts_eq_or_prefix_of([]);
}
}
+6 -2
View File
@@ -73,8 +73,12 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// A unit marker struct.
pub struct Yes;
/// A unit marker enum.
pub enum Yes {}
/// A unit marker enum.
pub enum Maybe {}
/// A unit marker enum.
pub enum No {}
/// A quick helper to encode some bytes to hex.
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
+1
View File
@@ -90,6 +90,7 @@ sp-crypto-hashing = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
frame-metadata = { workspace = true }
frame-decode = { workspace = true }
either = { workspace = true }
web-time = { workspace = true }
+3 -3
View File
@@ -10,7 +10,7 @@ use crate::{
error::{BlockError, DecodeError, Error},
events,
runtime_api::RuntimeApi,
storage::Storage,
storage::StorageClientAt,
};
use codec::{Decode, Encode};
@@ -104,8 +104,8 @@ where
}
/// Work with storage.
pub fn storage(&self) -> Storage<T, C> {
Storage::new(self.client.clone(), self.block_ref.clone())
pub fn storage(&self) -> StorageClientAt<T, C> {
StorageClientAt::new(self.client.clone(), self.block_ref.clone())
}
/// Execute a runtime API call at this block.
+6 -88
View File
@@ -4,94 +4,12 @@
//! Types associated with accessing and working with storage items.
// mod storage_client;
// mod storage_type;
mod storage_client;
mod storage_client_at;
// pub use storage_client::StorageClient;
// pub use storage_type::{Storage, StorageKeyValuePair};
// pub use subxt_core::storage::address::{
// Address, DefaultAddress, DynamicAddress, StaticAddress, StaticStorageKey, StorageKey, dynamic,
// }; // TODO re-add
use crate::{
backend::BlockRef,
client::{OfflineClientT, OnlineClientT},
config::{Config, HashFor},
error::Error,
pub use storage_client::StorageClient;
pub use storage_client_at::StorageClientAt;
pub use subxt_core::storage::address::{
Address, StaticAddress, DynamicAddress, dynamic,
};
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
/// Query the runtime storage.
#[derive_where(Clone; Client)]
pub struct StorageClient<T, Client> {
client: Client,
_marker: PhantomData<T>,
}
impl<T, Client> StorageClient<T, Client> {
/// Create a new [`StorageClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomData,
}
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OfflineClientT<T>,
{
/// Run the validation logic against some storage address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// 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<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve the entries at the root of the associated address.
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> Vec<u8> {
subxt_core::storage::get_address_root_bytes(address)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve an entry. This fails if [`Address::append_entry_bytes`] does; in the built-in
/// implementation this would be if the pallet and storage entry being asked for is not available on the
/// node you're communicating with, or if the metadata is missing some type information (which should not
/// happen).
pub fn address_bytes<Addr: Address>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Obtain storage at some block hash.
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> Storage<T, Client> {
Storage::new(self.client.clone(), block_ref.into())
}
/// Obtain storage at the latest finalized block.
pub fn at_latest(
&self,
) -> impl Future<Output = Result<Storage<T, Client>, Error>> + Send + 'static {
// Clone and pass the client in like this so that we can explicitly
// return a Future that's Send + 'static, rather than tied to &self.
let client = self.client.clone();
async move {
// get the ref for the latest finalized block and use that.
let block_ref = client.backend().latest_finalized_block_ref().await?;
Ok(Storage::new(client, block_ref))
}
}
}
+9 -8
View File
@@ -2,7 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_type::Storage;
use super::storage_client_at::StorageClientAt;
use crate::{
backend::BlockRef,
client::{OfflineClientT, OnlineClientT},
@@ -12,6 +12,7 @@ use crate::{
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
use subxt_core::storage::address::Address;
use subxt_core::storage::EqualOrPrefixOf;
/// Query the runtime storage.
#[derive_where(Clone; Client)]
@@ -45,7 +46,7 @@ where
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve the entries at the root of the associated address.
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> Vec<u8> {
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> [u8; 32] {
subxt_core::storage::get_address_root_bytes(address)
}
@@ -54,8 +55,8 @@ where
/// implementation this would be if the pallet and storage entry being asked for is not available on the
/// node you're communicating with, or if the metadata is missing some type information (which should not
/// happen).
pub fn address_bytes<Addr: Address>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
pub fn address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(&self, address: &Addr, keys: Keys) -> Result<Vec<u8>, Error> {
subxt_core::storage::get_address_bytes(address, &self.client.metadata(), keys).map_err(Into::into)
}
}
@@ -65,14 +66,14 @@ where
Client: OnlineClientT<T>,
{
/// Obtain storage at some block hash.
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> Storage<T, Client> {
Storage::new(self.client.clone(), block_ref.into())
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> StorageClientAt<T, Client> {
StorageClientAt::new(self.client.clone(), block_ref.into())
}
/// Obtain storage at the latest finalized block.
pub fn at_latest(
&self,
) -> impl Future<Output = Result<Storage<T, Client>, Error>> + Send + 'static {
) -> impl Future<Output = Result<StorageClientAt<T, Client>, Error>> + Send + 'static {
// Clone and pass the client in like this so that we can explicitly
// return a Future that's Send + 'static, rather than tied to &self.
let client = self.client.clone();
@@ -80,7 +81,7 @@ where
// get the ref for the latest finalized block and use that.
let block_ref = client.backend().latest_finalized_block_ref().await?;
Ok(Storage::new(client, block_ref))
Ok(StorageClientAt::new(client, block_ref))
}
}
}
@@ -13,21 +13,21 @@ use codec::Decode;
use derive_where::derive_where;
use futures::StreamExt;
use std::{future::Future, marker::PhantomData};
use subxt_core::storage::address::{Address, StorageHashers, StorageKey};
use subxt_core::utils::Yes;
use subxt_core::storage::address::Address;
use subxt_core::utils::{Maybe, Yes, No};
/// This is returned from a couple of storage functions.
pub use crate::backend::StreamOfResults;
/// Query the runtime storage.
#[derive_where(Clone; Client)]
pub struct Storage<T: Config, Client> {
pub struct StorageClientAt<T: Config, Client> {
client: Client,
block_ref: BlockRef<HashFor<T>>,
_marker: PhantomData<T>,
}
impl<T: Config, Client> Storage<T, Client> {
impl<T: Config, Client> StorageClientAt<T, Client> {
/// Create a new [`Storage`]
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
Self {
@@ -38,6 +38,134 @@ impl<T: Config, Client> Storage<T, Client> {
}
}
impl<T, Client> StorageClientAt<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
pub fn entry<Addr: Address>(&self, address: Addr) -> Result<StorageEntryClient<T, Client, Addr, Addr::IsMap>, Error> {
subxt_core::storage::validate(&address, &self.client.metadata())?;
use frame_decode::storage::StorageTypeInfo;
let storage_info = self
.client
.metadata()
.storage_info(address.pallet_name(), address.entry_name())?;
let value = if storage_info.keys.is_empty() {
StorageEntryClientValue::Plain(StorageEntryPlainClient {
client: self.client.clone(),
block_ref: self.block_ref.clone(),
address,
_marker: core::marker::PhantomData
})
} else {
StorageEntryClientValue::Map(StorageEntryMapClient {
client: self.client.clone(),
block_ref: self.block_ref.clone(),
address,
_marker: core::marker::PhantomData
})
};
Ok(StorageEntryClient {
value,
marker: core::marker::PhantomData
})
}
}
pub struct StorageEntryClient<T: Config, Client, Addr, IsMap> {
value: StorageEntryClientValue<T, Client, Addr>,
marker: core::marker::PhantomData<IsMap>,
}
enum StorageEntryClientValue<T: Config, Client, Addr> {
Plain(StorageEntryPlainClient<T, Client, Addr>),
Map(StorageEntryMapClient<T, Client, Addr>),
}
impl <T: Config, Client, Addr: Address, IsMap> StorageEntryClient<T, Client, Addr, IsMap> {
pub fn pallet_name(&self) -> &str {
match &self.value {
StorageEntryClientValue::Plain(client) => client.address.pallet_name(),
StorageEntryClientValue::Map(client) => client.address.pallet_name(),
}
}
pub fn storage_name(&self) -> &str {
match &self.value {
StorageEntryClientValue::Plain(client) => client.address.entry_name(),
StorageEntryClientValue::Map(client) => client.address.entry_name(),
}
}
}
// When IsMap = Yes, we have statically asserted that the entry is a map. This can only be false
// if we skip validation of a static call, and the storage entry, while still present, has changed from
// plain to map.
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Yes> {
pub fn into_map(self) -> StorageEntryMapClient<T, Client, Addr> {
match self.value {
StorageEntryClientValue::Map(this) => this,
StorageEntryClientValue::Plain(_) => panic!("When IsMap = Yes, StorageEntryClient should always be a map.")
}
}
}
// When IsMap = No, we have statically asserted that the entry is a plain value. This can only be false
// if we skip validation of a static call, and the storage entry, while still present, has changed from
// map to plain value.
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, No> {
pub fn into_plain(self) -> StorageEntryPlainClient<T, Client, Addr> {
match self.value {
StorageEntryClientValue::Map(_) => panic!("When IsMap = No, StorageEntryClient should always be a plain value."),
StorageEntryClientValue::Plain(this) => this,
}
}
}
// Regardless, we can do the "safe" thing and try to convert the entry into a map or plain entry.
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Maybe> {
pub fn into_map(self) -> Option<StorageEntryMapClient<T, Client, Addr>> {
match self.value {
StorageEntryClientValue::Map(client) => Some(client),
StorageEntryClientValue::Plain(_) => None,
}
}
pub fn into_plain(self) -> Option<StorageEntryPlainClient<T, Client, Addr>> {
match self.value {
StorageEntryClientValue::Plain(client) => Some(client),
StorageEntryClientValue::Map(_) => None,
}
}
pub fn is_plain(&self) -> bool {
matches!(self.value, StorageEntryClientValue::Plain(_))
}
pub fn is_map(&self) -> bool {
matches!(self.value, StorageEntryClientValue::Map(_))
}
}
pub struct StorageEntryPlainClient<T: Config, Client, Addr> {
client: Client,
block_ref: BlockRef<HashFor<T>>,
address: Addr,
_marker: PhantomData<T>,
}
pub struct StorageEntryMapClient<T: Config, Client, Addr> {
client: Client,
block_ref: BlockRef<HashFor<T>>,
address: Addr,
_marker: PhantomData<T>,
}
/*
impl<T, Client> Storage<T, Client>
where
T: Config,
@@ -321,3 +449,4 @@ pub struct StorageKeyValuePair<T: Address> {
/// The value of the storage entry.
pub value: T::Target,
}
*/