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
+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,
}
*/