Make subxt-core ready for publishing (#1508)

* Move Extrinsic decoding things to subxt_core and various tidy-ups

* A couple more fixes and fmt

* first pass moving tx logic to subxt_core

* cargo fmt

* fix wasm example

* clippy

* more clippy

* WIP Adding examples and such

* Move storage functionality more fully to subxt_core and nice examples for storage and txs

* Add example for events

* consistify how addresses/payloads are exposed in subxt-core and add runtime API fns

* Add runtime API core example

* fmt

* remove scale-info patch

* Add a little to the top level docs

* swap args around

* clippy

* cargo fmt and fix wasm-example

* doc fixes

* no-std-ise new subxt-core additions

* alloc, not core

* more no-std fixes

* A couple more fixes

* Add back extrinsic decode test
This commit is contained in:
James Wilson
2024-04-15 15:20:11 +01:00
committed by GitHub
parent b527c857ea
commit 1e111ea9db
89 changed files with 4459 additions and 3500 deletions
+10 -6
View File
@@ -181,16 +181,20 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
let details = self.methods.state_get_runtime_version(None).await?;
Ok(RuntimeVersion::new(
details.spec_version,
details.transaction_version,
))
Ok(RuntimeVersion {
spec_version: details.spec_version,
transaction_version: details.transaction_version,
})
}
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
let sub = self.methods.state_subscribe_runtime_version().await?;
let sub =
sub.map(|r| r.map(|v| RuntimeVersion::new(v.spec_version, v.transaction_version)));
let sub = sub.map(|r| {
r.map(|v| RuntimeVersion {
spec_version: v.spec_version,
transaction_version: v.transaction_version,
})
});
Ok(StreamOf(Box::pin(sub)))
}
+4 -1
View File
@@ -416,7 +416,10 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
RuntimeEvent::Valid(ev) => ev,
};
let runtime_version = RuntimeVersion::new(runtime_details.spec.spec_version, runtime_details.spec.transaction_version);
let runtime_version = RuntimeVersion {
spec_version: runtime_details.spec.spec_version,
transaction_version: runtime_details.spec.transaction_version
};
std::future::ready(Some(Ok(runtime_version)))
});
+2 -4
View File
@@ -4,7 +4,7 @@
use crate::{
backend::BlockRef,
blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics},
blocks::Extrinsics,
client::{OfflineClientT, OnlineClientT},
config::{Config, Header},
error::{BlockError, DecodeError, Error},
@@ -79,7 +79,6 @@ where
/// Fetch and return the extrinsics in the block body.
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
let ids = ExtrinsicPartTypeIds::new(&self.client.metadata())?;
let block_hash = self.header.hash();
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
return Err(BlockError::not_found(block_hash).into());
@@ -89,9 +88,8 @@ where
self.client.clone(),
extrinsics,
self.cached_events.clone(),
ids,
block_hash,
))
)?)
}
/// Work with storage.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -15,7 +15,7 @@ pub use block_types::Block;
pub use blocks_client::BlocksClient;
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions,
Extrinsics, StaticExtrinsic,
Extrinsics, FoundExtrinsic, StaticExtrinsic,
};
// We get account nonce info in tx_client, too, so re-use the logic:
+1 -1
View File
@@ -40,7 +40,7 @@
//! );
//! ```
//!
//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which
//! All valid runtime calls implement [`crate::runtime_api::PayloadT`], a trait which
//! describes how to encode the runtime call arguments and what return type to decode from the
//! response.
//!
+2 -2
View File
@@ -66,7 +66,7 @@
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
//! a given `u8`, `bool` and `u16` value.
//!
//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing
//! All valid storage queries implement [`crate::storage::AddressT`]. As well as describing
//! how to build a valid storage query, this trait also has some associated types that determine the
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
//! over storage entries using it).
@@ -124,7 +124,7 @@
//!
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
//! obtained from a [`crate::storage::StorageAddress`] by using
//! obtained from a [`crate::storage::AddressT`] by using
//! [`crate::storage::StorageClient::address_bytes()`] or
//! [`crate::storage::StorageClient::address_root_bytes()`].
//!
+1 -1
View File
@@ -53,7 +53,7 @@
//! represents any type of data that can be SCALE encoded or decoded. It can be serialized,
//! deserialized and parsed from/to strings.
//!
//! A valid transaction payload is just something that implements the [`crate::tx::TxPayload`] trait;
//! A valid transaction payload is just something that implements the [`crate::tx::PayloadT`] trait;
//! you can implement this trait on your own custom types if the built-in ones are not suitable for
//! your needs.
//!
+16 -19
View File
@@ -17,13 +17,20 @@ use subxt_core::client::{ClientState, RuntimeVersion};
pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
/// Return the provided [`Metadata`].
fn metadata(&self) -> Metadata;
/// Return the provided genesis hash.
fn genesis_hash(&self) -> T::Hash;
/// Return the provided [`RuntimeVersion`].
fn runtime_version(&self) -> RuntimeVersion;
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
fn client_state(&self) -> ClientState<T> {
ClientState::new(self.genesis_hash(), self.runtime_version(), self.metadata())
ClientState {
genesis_hash: self.genesis_hash(),
runtime_version: self.runtime_version(),
metadata: self.metadata(),
}
}
/// Work with transactions.
@@ -77,37 +84,30 @@ impl<T: Config> OfflineClient<T> {
runtime_version: RuntimeVersion,
metadata: impl Into<Metadata>,
) -> OfflineClient<T> {
let metadata = metadata.into();
OfflineClient {
inner: Arc::new(ClientState::new(
inner: Arc::new(ClientState {
genesis_hash,
runtime_version,
metadata.into(),
)),
metadata,
}),
}
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
self.inner.genesis_hash()
self.inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
self.inner.runtime_version()
self.inner.runtime_version
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
self.inner.metadata()
}
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
pub fn client_state(&self) -> ClientState<T> {
ClientState::new(
self.inner.genesis_hash(),
self.inner.runtime_version(),
self.inner.metadata(),
)
self.inner.metadata.clone()
}
// Just a copy of the most important trait methods so that people
@@ -149,9 +149,6 @@ impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
fn metadata(&self) -> Metadata {
self.metadata()
}
fn client_state(&self) -> ClientState<T> {
self.client_state()
}
}
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
+9 -14
View File
@@ -228,7 +228,7 @@ impl<T: Config> OnlineClient<T> {
/// let mut update_stream = updater.runtime_updates().await.unwrap();
///
/// while let Some(Ok(update)) = update_stream.next().await {
/// let version = update.runtime_version().spec_version();
/// let version = update.runtime_version().spec_version;
///
/// match updater.apply_update(update) {
/// Ok(()) => {
@@ -286,16 +286,6 @@ impl<T: Config> OnlineClient<T> {
inner.runtime_version
}
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
pub fn client_state(&self) -> ClientState<T> {
let inner = self.inner.read().expect("shouldn't be poisoned");
ClientState::new(
inner.genesis_hash,
inner.runtime_version,
inner.metadata.clone(),
)
}
/// Change the [`RuntimeVersion`] used in this client.
///
/// # Warning
@@ -371,9 +361,14 @@ impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
// This is provided by default, but we can optimise here and only lock once:
fn client_state(&self) -> ClientState<T> {
self.client_state()
let inner = self.inner.read().expect("shouldn't be poisoned");
ClientState {
genesis_hash: inner.genesis_hash,
runtime_version: inner.runtime_version,
metadata: inner.metadata.clone(),
}
}
}
@@ -551,7 +546,7 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
// We are waiting for the chain to have the same spec version
// as sent out via the runtime subscription.
if spec_version == runtime_version.spec_version() {
if spec_version == runtime_version.spec_version {
break block_ref;
}
};
+5 -8
View File
@@ -2,9 +2,9 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::ConstantAddress;
use crate::{client::OfflineClientT, error::Error, Config};
use derive_where::derive_where;
use subxt_core::constants::address::AddressT;
/// A client for accessing constants.
#[derive_where(Clone; Client)]
@@ -28,19 +28,16 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// 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 constant in question do not exist at all).
pub fn validate<Address: ConstantAddress>(&self, address: &Address) -> Result<(), Error> {
pub fn validate<Address: AddressT>(&self, address: &Address) -> Result<(), Error> {
let metadata = self.client.metadata();
subxt_core::constants::validate_constant(&metadata, address).map_err(Error::from)
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
}
/// Access the constant at the address given, returning the type defined by this address.
/// This is probably used with addresses given from static codegen, although you can manually
/// construct your own, too.
pub fn at<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<Address::Target, Error> {
pub fn at<Address: AddressT>(&self, address: &Address) -> Result<Address::Target, Error> {
let metadata = self.client.metadata();
subxt_core::constants::get_constant(&metadata, address).map_err(Error::from)
subxt_core::constants::get(address, &metadata).map_err(Error::from)
}
}
+1 -1
View File
@@ -7,4 +7,4 @@
mod constants_client;
pub use constants_client::ConstantsClient;
pub use subxt_core::constants::{dynamic, Address, ConstantAddress, DynamicAddress};
pub use subxt_core::constants::address::{dynamic, Address, AddressT, DynamicAddress};
+12 -15
View File
@@ -2,10 +2,7 @@ use crate::client::OfflineClientT;
use crate::{Config, Error};
use derive_where::derive_where;
use subxt_core::custom_values::{
get_custom_value, get_custom_value_bytes, validate_custom_value, CustomValueAddress,
};
use subxt_core::utils::Yes;
use subxt_core::custom_values::address::{AddressT, Yes};
/// A client for accessing custom values stored in the metadata.
#[derive_where(Clone; Client)]
@@ -27,29 +24,26 @@ impl<T, Client> CustomValuesClient<T, Client> {
impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn at<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
pub fn at<Address: AddressT<IsDecodable = Yes> + ?Sized>(
&self,
address: &Address,
) -> Result<Address::Target, Error> {
get_custom_value(&self.client.metadata(), address).map_err(Into::into)
subxt_core::custom_values::get(address, &self.client.metadata()).map_err(Into::into)
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn bytes_at<Address: CustomValueAddress + ?Sized>(
pub fn bytes_at<Address: AddressT + ?Sized>(
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
get_custom_value_bytes(&self.client.metadata(), address).map_err(Into::into)
subxt_core::custom_values::get_bytes(address, &self.client.metadata()).map_err(Into::into)
}
/// Run the validation logic against some custom value 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).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate<Address: CustomValueAddress + ?Sized>(
&self,
address: &Address,
) -> Result<(), Error> {
validate_custom_value(&self.client.metadata(), address).map_err(Into::into)
pub fn validate<Address: AddressT + ?Sized>(&self, address: &Address) -> Result<(), Error> {
subxt_core::custom_values::validate(address, &self.client.metadata()).map_err(Into::into)
}
}
@@ -113,14 +107,17 @@ mod tests {
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::new(metadata)
Metadata::from(metadata)
}
#[test]
fn test_decoding() {
let client = OfflineClient::<SubstrateConfig>::new(
Default::default(),
RuntimeVersion::new(0, 0),
RuntimeVersion {
spec_version: 0,
transaction_version: 0,
},
mock_metadata(),
);
let custom_value_client = CustomValuesClient::new(client);
+1 -1
View File
@@ -7,4 +7,4 @@
mod custom_values_client;
pub use custom_values_client::CustomValuesClient;
pub use subxt_core::custom_values::{CustomValueAddress, StaticAddress};
pub use subxt_core::custom_values::address::{AddressT, StaticAddress, Yes};
+21 -8
View File
@@ -6,6 +6,8 @@
mod dispatch_error;
use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError};
crate::macros::cfg_unstable_light_client! {
pub use subxt_lightclient::LightClientError;
}
@@ -80,15 +82,16 @@ pub enum Error {
Other(String),
}
impl From<subxt_core::Error> for Error {
fn from(value: subxt_core::Error) -> Self {
impl From<CoreError> for Error {
fn from(value: CoreError) -> Self {
match value {
subxt_core::Error::Codec(e) => Error::Codec(e),
subxt_core::Error::Metadata(e) => Error::Metadata(e),
subxt_core::Error::StorageAddress(e) => Error::StorageAddress(e),
subxt_core::Error::Decode(e) => Error::Decode(e),
subxt_core::Error::Encode(e) => Error::Encode(e),
subxt_core::Error::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Codec(e) => Error::Codec(e),
CoreError::Metadata(e) => Error::Metadata(e),
CoreError::StorageAddress(e) => Error::StorageAddress(e),
CoreError::Decode(e) => Error::Decode(e),
CoreError::Encode(e) => Error::Encode(e),
CoreError::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
CoreError::Block(e) => Error::Block(e.into()),
}
}
}
@@ -175,6 +178,16 @@ pub enum BlockError {
DecodingError(codec::Error),
}
impl From<CoreBlockError> for BlockError {
fn from(value: CoreBlockError) -> Self {
match value {
CoreBlockError::MissingType => BlockError::MissingType,
CoreBlockError::UnsupportedVersion(n) => BlockError::UnsupportedVersion(n),
CoreBlockError::DecodingError(e) => BlockError::DecodingError(e),
}
}
}
impl BlockError {
/// Produce an error that a block with the given hash cannot be found.
pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError {
+1 -1
View File
@@ -64,7 +64,7 @@ where
};
let event_bytes = get_event_bytes(client.backend(), block_ref.hash()).await?;
Ok(Events::decode_from(client.metadata(), event_bytes))
Ok(Events::decode_from(event_bytes, client.metadata()))
}
}
}
+1 -1
View File
@@ -24,5 +24,5 @@ where
C: OnlineClientT<T>,
{
let event_bytes = events_client::get_event_bytes(client.backend(), block_hash).await?;
Ok(Events::<T>::decode_from(metadata, event_bytes))
Ok(Events::<T>::decode_from(event_bytes, metadata))
}
+1 -1
View File
@@ -71,7 +71,7 @@ pub mod config {
/// Types representing the metadata obtained from a node.
pub mod metadata {
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata, MetadataExt};
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
}
+1 -1
View File
@@ -9,4 +9,4 @@ mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_types::RuntimeApi;
pub use subxt_core::runtime_api::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
pub use subxt_core::runtime_api::payload::{dynamic, DynamicPayload, Payload, PayloadT};
+18 -30
View File
@@ -2,19 +2,17 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::PayloadT;
use crate::{
backend::{BackendExt, BlockRef},
client::OnlineClientT,
error::{Error, MetadataError},
metadata::DecodeWithMetadata,
error::Error,
Config,
};
use codec::Decode;
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
use super::RuntimeApiPayload;
/// Execute runtime API calls.
#[derive_where(Clone; Client)]
pub struct RuntimeApi<T: Config, Client> {
@@ -39,6 +37,14 @@ where
T: Config,
Client: OnlineClientT<T>,
{
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the runtime API in question do not exist at all)
pub fn validate<Call: PayloadT>(&self, payload: &Call) -> Result<(), Error> {
subxt_core::runtime_api::validate(payload, &self.client.metadata()).map_err(Into::into)
}
/// Execute a raw runtime API call.
pub fn call_raw<'a, Res: Decode>(
&self,
@@ -59,7 +65,7 @@ where
}
/// Execute a runtime API call.
pub fn call<Call: RuntimeApiPayload>(
pub fn call<Call: PayloadT>(
&self,
payload: Call,
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
@@ -70,39 +76,21 @@ where
async move {
let metadata = client.metadata();
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
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 Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if static_hash != runtime_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
subxt_core::runtime_api::validate(&payload, &metadata)?;
// Encode the arguments of the runtime call.
// 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 call_name = subxt_core::runtime_api::call_name(&payload);
let call_args = subxt_core::runtime_api::call_args(&payload, &metadata)?;
// Make the call.
let bytes = client
.backend()
.call(&call_name, Some(params.as_slice()), block_hash)
.call(&call_name, Some(call_args.as_slice()), block_hash)
.await?;
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
api_method.output_ty(),
&metadata,
)?;
// Decode the response.
let value = subxt_core::runtime_api::decode_value(&mut &*bytes, &payload, &metadata)?;
Ok(value)
}
}
+3 -15
View File
@@ -8,19 +8,7 @@ mod storage_client;
mod storage_type;
pub use storage_client::StorageClient;
pub use storage_type::{Storage, StorageKeyValuePair};
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use subxt_core::storage::address::{
dynamic, Address, DynamicAddress, StaticStorageKey, StorageAddress, StorageKey,
};
}
pub use subxt_core::storage::StorageKey;
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use address::{dynamic, Address, DynamicAddress, StorageAddress};
pub use subxt_core::storage::address::{
dynamic, Address, AddressT, DynamicAddress, StaticStorageKey, StorageKey,
};
+9 -17
View File
@@ -2,10 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{
storage_type::{validate_storage_address, Storage},
StorageAddress,
};
use super::storage_type::Storage;
use crate::{
backend::BlockRef,
client::{OfflineClientT, OnlineClientT},
@@ -14,6 +11,7 @@ use crate::{
};
use derive_where::derive_where;
use std::{future::Future, marker::PhantomData};
use subxt_core::storage::address::AddressT;
/// Query the runtime storage.
#[derive_where(Clone; Client)]
@@ -41,29 +39,23 @@ where
/// 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<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
let metadata = self.client.metadata();
let pallet_metadata = metadata.pallet_by_name_err(address.pallet_name())?;
validate_storage_address(address, pallet_metadata)
pub fn validate<Address: AddressT>(&self, address: &Address) -> 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<Address: StorageAddress>(&self, address: &Address) -> Vec<u8> {
subxt_core::storage::utils::storage_address_root_bytes(address)
pub fn address_root_bytes<Address: AddressT>(&self, address: &Address) -> 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 [`StorageAddress::append_entry_bytes`] does; in the built-in
/// to retrieve an entry. This fails if [`AddressT::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<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
subxt_core::storage::utils::storage_address_bytes(address, &self.client.metadata())
.map_err(Into::into)
pub fn address_bytes<Address: AddressT>(&self, address: &Address) -> Result<Vec<u8>, Error> {
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
}
}
+18 -94
View File
@@ -2,23 +2,19 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use subxt_core::storage::address::{StorageAddress, StorageHashers, StorageKey};
use subxt_core::utils::Yes;
use crate::{
backend::{BackendExt, BlockRef},
client::OnlineClientT,
error::{Error, MetadataError, StorageAddressError},
metadata::{DecodeWithMetadata, Metadata},
metadata::DecodeWithMetadata,
Config,
};
use codec::Decode;
use derive_where::derive_where;
use futures::StreamExt;
use std::{future::Future, marker::PhantomData};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
use subxt_core::storage::address::{AddressT, StorageHashers, StorageKey};
use subxt_core::utils::Yes;
/// This is returned from a couple of storage functions.
pub use crate::backend::StreamOfResults;
@@ -119,26 +115,22 @@ where
address: &'address Address,
) -> impl Future<Output = Result<Option<Address::Target>, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes> + 'address,
Address: AddressT<IsFetchable = Yes> + 'address,
{
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, pallet)?;
subxt_core::storage::validate(address, &metadata)?;
// Look up the return type ID to enable DecodeWithMetadata:
let lookup_bytes =
subxt_core::storage::utils::storage_address_bytes(address, &metadata)?;
let lookup_bytes = subxt_core::storage::get_address_bytes(address, &metadata)?;
if let Some(data) = client.fetch_raw(lookup_bytes).await? {
let val =
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
let val = subxt_core::storage::decode_value(&mut &*data, address, &metadata)?;
Ok(Some(val))
} else {
Ok(None)
@@ -152,24 +144,16 @@ where
address: &'address Address,
) -> impl Future<Output = Result<Address::Target, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
Address: AddressT<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
{
let client = self.clone();
async move {
let pallet_name = address.pallet_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)?;
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)?;
let val = subxt_core::storage::default_value(address, &metadata)?;
Ok(val)
}
}
@@ -211,21 +195,24 @@ where
address: Address,
) -> impl Future<Output = Result<StreamOfResults<StorageKeyValuePair<Address>>, Error>> + 'static
where
Address: StorageAddress<IsIterable = Yes> + 'static,
Address: AddressT<IsIterable = Yes> + 'static,
Address::Keys: 'static + Sized,
{
let client = self.client.clone();
let block_ref = self.block_ref.clone();
async move {
let metadata = client.metadata();
let (pallet, entry) =
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
let (_pallet, entry) = subxt_core::storage::lookup_storage_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, pallet)?;
subxt_core::storage::validate(&address, &metadata)?;
// 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`
@@ -236,8 +223,7 @@ where
let hashers = StorageHashers::new(entry, metadata.types())?;
// The address bytes of this entry:
let address_bytes =
subxt_core::storage::utils::storage_address_bytes(&address, &metadata)?;
let address_bytes = subxt_core::storage::get_address_bytes(&address, &metadata)?;
let s = client
.backend()
.storage_fetch_descendant_values(address_bytes, block_ref.hash())
@@ -327,7 +313,7 @@ fn strip_storage_address_root_bytes(address_bytes: &mut &[u8]) -> Result<(), Sto
/// A pair of keys and values together with all the bytes that make up the storage address.
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct StorageKeyValuePair<T: StorageAddress> {
pub struct StorageKeyValuePair<T: AddressT> {
/// The bytes that make up the address of the storage entry.
pub key_bytes: Vec<u8>,
/// The keys that can be used to construct the address of this storage entry.
@@ -335,65 +321,3 @@ pub struct StorageKeyValuePair<T: StorageAddress> {
/// The value of the storage entry.
pub value: T::Target,
}
/// Validate a storage address against the metadata.
pub(crate) fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Return details about the given storage entry.
fn lookup_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
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))
}
/// 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) -> u32 {
match entry {
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],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
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)
}
-60
View File
@@ -1,60 +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.
//! these utility methods complement the [`StorageAddress`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use subxt_metadata::StorageHasher;
use super::StorageAddress;
use crate::{error::Error, metadata::Metadata};
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_crypto_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_crypto_hashing::twox_64(input));
bytes.extend(input);
}
}
}
+7 -12
View File
@@ -14,21 +14,16 @@ use crate::macros::cfg_substrate_compat;
mod tx_client;
mod tx_progress;
pub use subxt_core::tx as tx_payload;
pub use subxt_core::tx::signer;
// The PairSigner impl currently relies on Substrate bits and pieces, so make it an optional
// feature if we want to avoid needing sp_core and sp_runtime.
cfg_substrate_compat! {
pub use signer::PairSigner;
pub use subxt_core::tx::signer::PairSigner;
}
pub use self::{
signer::Signer,
tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
},
tx_payload::{dynamic, DynamicPayload, Payload, TxPayload},
tx_progress::{TxInBlock, TxProgress, TxStatus},
pub use subxt_core::tx::payload::{dynamic, DynamicPayload, Payload, PayloadT};
pub use subxt_core::tx::signer::{self, Signer};
pub use tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
};
pub use tx_progress::{TxInBlock, TxProgress, TxStatus};
+56 -153
View File
@@ -2,22 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::borrow::Cow;
use crate::{
backend::{BackendExt, BlockRef, TransactionStatus},
client::{OfflineClientT, OnlineClientT},
config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header, RefineParams,
RefineParamsData,
},
error::{BlockError, Error, MetadataError},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
config::{Config, ExtrinsicParams, Header, RefineParams, RefineParamsData},
error::{BlockError, Error},
tx::{PayloadT, Signer as SignerT, TxProgress},
utils::PhantomDataSendSync,
};
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use sp_crypto_hashing::blake2_256;
/// A client for working with transactions.
#[derive_where(Clone; Client)]
@@ -43,65 +37,30 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// the pallet or call in question do not exist at all).
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
where
Call: TxPayload,
Call: PayloadT,
{
if let Some(details) = call.validation_details() {
let expected_hash = self
.client
.metadata()
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
subxt_core::tx::validate(call, &self.client.metadata()).map_err(Into::into)
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data_to(&metadata, &mut bytes)?;
Ok(bytes)
subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
}
/// Creates an unsigned extrinsic without submitting it.
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Encode extrinsic
let extrinsic = {
let mut encoded_inner = Vec::new();
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data_to(&self.client.metadata(), &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
Ok(SubmittableExtrinsic::from_bytes(
self.client.clone(),
extrinsic,
))
subxt_core::tx::create_unsigned(call, &self.client.metadata())
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
/// Create a partial extrinsic.
@@ -114,25 +73,14 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = self.call_data(call)?;
// 3. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(&self.client.client_state(), params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialExtrinsic {
client: self.client.clone(),
call_data,
additional_and_extra_params,
})
subxt_core::tx::create_partial_signed(call, &self.client.client_state(), params)
.map(|tx| PartialExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
/// Creates a signed extrinsic without submitting it.
@@ -146,19 +94,15 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. Gather the "additional" and "extra" params along with the encoded call data,
// ready to be signed.
let partial_signed = self.create_partial_signed_offline(call, params)?;
// 3. Sign and construct an extrinsic from these details.
Ok(partial_signed.sign(signer))
subxt_core::tx::create_signed(call, &self.client.client_state(), signer, params)
.map(|tx| SubmittableExtrinsic {
client: self.client.clone(),
inner: tx,
})
.map_err(Into::into)
}
}
@@ -205,7 +149,7 @@ where
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
{
// Refine the params by adding account nonce and latest block information:
self.refine_params(account_id, &mut params).await?;
@@ -221,7 +165,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
@@ -249,7 +193,7 @@ where
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
{
@@ -268,7 +212,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params)
@@ -293,7 +237,7 @@ where
signer: &Signer,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
{
@@ -315,7 +259,7 @@ where
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Call: PayloadT,
Signer: SignerT<T>,
{
self.create_signed(call, signer, params)
@@ -328,8 +272,7 @@ where
/// This payload contains the information needed to produce an extrinsic.
pub struct PartialExtrinsic<T: Config, C> {
client: C,
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
inner: subxt_core::tx::PartialTransaction<T>,
}
impl<T, C> PartialExtrinsic<T, C>
@@ -337,34 +280,16 @@ where
T: Config,
C: OfflineClientT<T>,
{
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases when compared to
// [`PartialExtrinsic::signer_payload()`].
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params.encode_extra_to(&mut bytes);
self.additional_and_extra_params
.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
} else {
f(Cow::Owned(bytes))
}
}
/// Return the signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
self.inner.signer_payload()
}
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
self.inner.call_data()
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
@@ -374,10 +299,10 @@ where
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
// Now, use the signature and "from" address to build the extrinsic.
self.sign_with_address_and_signature(&signer.address(), &signature)
SubmittableExtrinsic {
client: self.client.clone(),
inner: self.inner.sign(signer),
}
}
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
@@ -389,40 +314,19 @@ where
address: &T::Address,
signature: &T::Signature,
) -> SubmittableExtrinsic<T, C> {
// Encode the extrinsic (into the format expected by protocol version 4)
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
address.encode_to(&mut encoded_inner);
// the signature
signature.encode_to(&mut encoded_inner);
// attach custom extra params
self.additional_and_extra_params
.encode_extra_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Return an extrinsic ready to be submitted.
SubmittableExtrinsic::from_bytes(self.client.clone(), extrinsic)
SubmittableExtrinsic {
client: self.client.clone(),
inner: self
.inner
.sign_with_address_and_signature(address, signature),
}
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SubmittableExtrinsic<T, C> {
client: C,
encoded: Encoded,
marker: std::marker::PhantomData<T>,
inner: subxt_core::tx::Transaction<T>,
}
impl<T, C> SubmittableExtrinsic<T, C>
@@ -440,25 +344,24 @@ where
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
Self {
client,
encoded: Encoded(tx_bytes),
marker: std::marker::PhantomData,
inner: subxt_core::tx::Transaction::from_bytes(tx_bytes),
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> T::Hash {
T::Hasher::hash_of(&self.encoded)
self.inner.hash()
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
self.inner.encoded()
}
/// Consumes [`SubmittableExtrinsic`] and returns the SCALE encoded
/// extrinsic bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.encoded.0
self.inner.into_encoded()
}
}
@@ -479,7 +382,7 @@ where
let sub = self
.client
.backend()
.submit_transaction(&self.encoded.0)
.submit_transaction(self.encoded())
.await?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
@@ -495,7 +398,7 @@ where
let mut sub = self
.client
.backend()
.submit_transaction(&self.encoded.0)
.submit_transaction(self.encoded())
.await?;
// If we get a bad status or error back straight away then error, else return the hash.
@@ -543,7 +446,7 @@ where
let block_hash = at.into().hash();
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
let mut params = Vec::with_capacity(8 + self.encoded.0.len() + 8);
let mut params = Vec::with_capacity(8 + self.encoded().len() + 8);
2u8.encode_to(&mut params);
params.extend(self.encoded().iter());
block_hash.encode_to(&mut params);