core integration into subxt, except for examples

This commit is contained in:
Tadeo hepperle
2024-02-02 16:12:15 +01:00
parent 14b21ab0df
commit 4e2d3fd9cf
38 changed files with 173 additions and 2013 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ syn = { workspace = true }
scale-info = { workspace = true }
subxt-metadata = { workspace = true }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport-native-tls", "http-client"], optional = true }
hex = { workspace = true }
hex = { workspace = true, features = ["std"] }
tokio = { workspace = true, features = ["rt-multi-thread"], optional = true }
thiserror = { workspace = true }
scale-typegen = { workspace = true }
+10 -1
View File
@@ -8,7 +8,7 @@ use crate::{config::Config, metadata::Metadata};
/// - metadata
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
pub struct ClientBase<C: Config> {
pub genesis_hash: C::Hash,
pub runtime_version: RuntimeVersion,
@@ -17,6 +17,15 @@ pub struct ClientBase<C: Config> {
}
impl<C: Config> ClientBase<C> {
pub fn new(genesis_hash: C::Hash, runtime_version: RuntimeVersion, metadata: Metadata) -> Self {
Self {
genesis_hash,
runtime_version,
metadata,
marker: core::marker::PhantomData,
}
}
pub fn metadata(&self) -> Metadata {
self.metadata.clone()
}
+1 -42
View File
@@ -10,54 +10,13 @@
use crate::client::ClientBase;
use super::Config;
use crate::ExtrinsicParamsError;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::Debug;
use derive_more::Display;
/// An error that can be emitted when trying to construct an instance of [`ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Display, Debug)]
#[non_exhaustive]
pub enum ExtrinsicParamsError {
/// Cannot find a type id in the metadata. The context provides some additional
/// information about the source of the error (eg the signed extension name).
#[display(fmt = "Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
type_id: u32,
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
/// A signed extension in use on some chain was not provided.
#[display(
fmt = "The chain expects a signed extension with the name {_0}, but we did not provide one"
)]
UnknownSignedExtension(String),
/// Some custom error.
#[display(fmt = "Error constructing extrinsic parameters: {_0}")]
#[cfg(feature = "std")]
Custom(CustomExtrinsicParamsError),
}
/// A custom error.
#[cfg(feature = "std")]
pub type CustomExtrinsicParamsError = Box<dyn std::error::Error + Send + Sync + 'static>;
impl From<core::convert::Infallible> for ExtrinsicParamsError {
fn from(value: core::convert::Infallible) -> Self {
match value {}
}
}
#[cfg(feature = "std")]
impl From<CustomExtrinsicParamsError> for ExtrinsicParamsError {
fn from(value: CustomExtrinsicParamsError) -> Self {
ExtrinsicParamsError::Custom(value)
}
}
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
+1 -1
View File
@@ -23,7 +23,7 @@ use scale_encode::EncodeAsType;
use serde::{de::DeserializeOwned, Serialize};
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use signed_extensions::SignedExtension;
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
+2 -1
View File
@@ -7,10 +7,11 @@
//! [`AnyOf`] to configure the set of signed extensions which are known about
//! when interacting with a chain.
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
use super::Config;
use crate::client::ClientBase;
use crate::utils::Era;
use crate::ExtrinsicParamsError;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
+52 -1
View File
@@ -6,13 +6,16 @@ pub enum Error {
#[display(fmt = "Metadata Error: {_0}")]
Metadata(MetadataError),
#[display(fmt = "Storage Error: {_0}")]
Storage(StorageAddressError),
StorageAddress(StorageAddressError),
/// Error decoding to a [`crate::dynamic::Value`].
#[display(fmt = "Error decoding into dynamic value: {_0}")]
Decode(scale_decode::Error),
/// Error encoding from a [`crate::dynamic::Value`].
#[display(fmt = "Error encoding from dynamic value: {_0}")]
Encode(scale_encode::Error),
/// Error constructing the appropriate extrinsic params.
#[display(fmt = "Extrinsic params error: {_0}")]
ExtrinsicParams(ExtrinsicParamsError),
}
#[cfg(feature = "std")]
@@ -98,3 +101,51 @@ pub enum StorageAddressError {
fields: usize,
},
}
#[cfg(feature = "std")]
impl std::error::Error for StorageAddressError {}
/// An error that can be emitted when trying to construct an instance of [`ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Display, Debug)]
#[non_exhaustive]
pub enum ExtrinsicParamsError {
/// Cannot find a type id in the metadata. The context provides some additional
/// information about the source of the error (eg the signed extension name).
#[display(fmt = "Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
type_id: u32,
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
/// A signed extension in use on some chain was not provided.
#[display(
fmt = "The chain expects a signed extension with the name {_0}, but we did not provide one"
)]
UnknownSignedExtension(String),
/// Some custom error.
#[display(fmt = "Error constructing extrinsic parameters: {_0}")]
#[cfg(feature = "std")]
Custom(CustomExtrinsicParamsError),
}
/// A custom error.
#[cfg(feature = "std")]
pub type CustomExtrinsicParamsError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[cfg(feature = "std")]
impl std::error::Error for ExtrinsicParamsError {}
impl From<core::convert::Infallible> for ExtrinsicParamsError {
fn from(value: core::convert::Infallible) -> Self {
match value {}
}
}
#[cfg(feature = "std")]
impl From<CustomExtrinsicParamsError> for ExtrinsicParamsError {
fn from(value: CustomExtrinsicParamsError) -> Self {
ExtrinsicParamsError::Custom(value)
}
}
+5 -6
View File
@@ -23,18 +23,17 @@ pub mod storage;
pub mod tx;
pub mod utils;
pub use error::{Error, MetadataError, StorageAddressError};
pub use error::{Error, ExtrinsicParamsError, MetadataError, StorageAddressError};
pub use client::{ClientBase, RuntimeVersion};
pub use config::{
BlockHash, Config, ExtrinsicParams, ExtrinsicParamsEncoder, PolkadotConfig,
PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams,
};
pub use utils::{AccountId32, MultiAddress, MultiSignature, H160, H256, H512};
pub use signer::Signer;
pub use metadata::Metadata;
pub use signer::Signer;
pub use storage::StorageAddress;
pub use utils::{AccountId32, MultiAddress, MultiSignature, H160, H256, H512};
#[macro_use]
mod macros;
+2 -14
View File
@@ -24,23 +24,11 @@ default = ["jsonrpsee", "native"]
# Enable this for native (ie non web/wasm builds).
# Exactly 1 of "web" and "native" is expected.
native = [
"jsonrpsee?/async-client",
"jsonrpsee?/client-ws-transport-native-tls",
"subxt-lightclient?/native",
"tokio-util"
]
native = ["jsonrpsee?/async-client", "jsonrpsee?/client-ws-transport-native-tls", "subxt-lightclient?/native", "tokio-util"]
# Enable this for web/wasm builds.
# Exactly 1 of "web" and "native" is expected.
web = [
"jsonrpsee?/async-wasm-client",
"jsonrpsee?/client-web-transport",
"getrandom/js",
"subxt-lightclient?/web",
"subxt-macro/web",
"instant/wasm-bindgen"
]
web = ["jsonrpsee?/async-wasm-client", "jsonrpsee?/client-web-transport", "getrandom/js", "subxt-lightclient?/web", "subxt-macro/web", "instant/wasm-bindgen"]
# Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`).
jsonrpsee = ["dep:jsonrpsee"]
+1 -20
View File
@@ -17,6 +17,7 @@ use codec::{Decode, Encode};
use futures::{Stream, StreamExt};
use std::pin::Pin;
use std::sync::Arc;
use subxt_core::client::RuntimeVersion;
use subxt_core::metadata::Metadata;
/// Prevent the backend trait being implemented externally.
@@ -276,26 +277,6 @@ impl<T> StreamOf<T> {
/// A stream of [`Result<Item, Error>`].
pub type StreamOfResults<T> = StreamOf<Result<T, Error>>;
/// Runtime version information needed to submit transactions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimeVersion {
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change. If this
/// number changes, then `spec_version` must change, also.
///
/// This number must change when an existing dispatchable (module ID, dispatch ID) is changed,
/// either through an alteration in its user-level semantics, a parameter
/// added/removed/changed, a dispatchable being removed, a module being removed, or a
/// dispatchable/module changing its index.
///
/// It need *not* change when a new module is added or when a dispatchable is added.
pub transaction_version: u32,
}
/// The status of the transaction.
///
/// If the status is [`TransactionStatus::InFinalizedBlock`], [`TransactionStatus::Error`],
+3 -2
View File
@@ -10,7 +10,7 @@ use crate::{
events, Metadata,
};
use subxt_core::metadata::types::PalletMetadata;
use subxt_core::metadata::{types::PalletMetadata, MetadatExt};
use crate::config::signed_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
@@ -770,7 +770,7 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{backend::RuntimeVersion, OfflineClient, PolkadotConfig};
use crate::{OfflineClient, PolkadotConfig};
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_metadata::v15::{CustomMetadata, OuterEnums};
@@ -781,6 +781,7 @@ mod tests {
use primitive_types::H256;
use scale_info::{meta_type, TypeInfo};
use scale_value::Value;
use subxt_core::client::RuntimeVersion;
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
+13 -15
View File
@@ -4,11 +4,12 @@
use crate::custom_values::CustomValuesClient;
use crate::{
backend::RuntimeVersion, blocks::BlocksClient, constants::ConstantsClient,
events::EventsClient, runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient,
Config, Metadata,
blocks::BlocksClient, constants::ConstantsClient, events::EventsClient,
runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient, Config, Metadata,
};
use derivative::Derivative;
use subxt_core::client::ClientBase;
use subxt_core::RuntimeVersion;
use std::sync::Arc;
@@ -21,6 +22,8 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
fn genesis_hash(&self) -> T::Hash;
/// Return the provided [`RuntimeVersion`].
fn runtime_version(&self) -> RuntimeVersion;
/// Return the inner [`subxt_core::ClientBase`].
fn base(&self) -> ClientBase<T>;
/// Work with transactions.
fn tx(&self) -> TxClient<T, Self> {
@@ -63,15 +66,7 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
pub struct OfflineClient<T: Config> {
inner: Arc<Inner<T>>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
inner: Arc<ClientBase<T>>,
}
impl<T: Config> OfflineClient<T> {
@@ -83,11 +78,11 @@ impl<T: Config> OfflineClient<T> {
metadata: impl Into<Metadata>,
) -> OfflineClient<T> {
OfflineClient {
inner: Arc::new(Inner {
inner: Arc::new(ClientBase::new(
genesis_hash,
runtime_version,
metadata: metadata.into(),
}),
metadata.into(),
)),
}
}
@@ -145,6 +140,9 @@ impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
fn metadata(&self) -> Metadata {
self.metadata()
}
fn base(&self) -> ClientBase<T> {
(*self.inner).clone()
}
}
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
+13 -16
View File
@@ -5,9 +5,7 @@
use super::{OfflineClient, OfflineClientT};
use crate::custom_values::CustomValuesClient;
use crate::{
backend::{
legacy::LegacyBackend, rpc::RpcClient, Backend, BackendExt, RuntimeVersion, StreamOfResults,
},
backend::{legacy::LegacyBackend, rpc::RpcClient, Backend, BackendExt, StreamOfResults},
blocks::{BlockRef, BlocksClient},
constants::ConstantsClient,
error::Error,
@@ -19,7 +17,9 @@ use crate::{
};
use derivative::Derivative;
use futures::future;
use std::borrow::Borrow;
use std::sync::{Arc, RwLock};
use subxt_core::{ClientBase, RuntimeVersion};
/// A trait representing a client that can perform
/// online actions.
@@ -33,18 +33,10 @@ pub trait OnlineClientT<T: Config>: OfflineClientT<T> {
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct OnlineClient<T: Config> {
inner: Arc<RwLock<Inner<T>>>,
inner: Arc<RwLock<ClientBase<T>>>,
backend: Arc<dyn Backend<T>>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<T: Config> std::fmt::Debug for OnlineClient<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
@@ -146,11 +138,11 @@ impl<T: Config> OnlineClient<T> {
backend: Arc<B>,
) -> Result<OnlineClient<T>, Error> {
Ok(OnlineClient {
inner: Arc::new(RwLock::new(Inner {
inner: Arc::new(RwLock::new(ClientBase::new(
genesis_hash,
runtime_version,
metadata: metadata.into(),
})),
metadata.into(),
))),
backend,
})
}
@@ -360,6 +352,11 @@ impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
fn base(&self) -> ClientBase<T> {
let inner = self.inner.read().expect("shouldn't be poisoned");
inner.clone()
}
}
impl<T: Config> OnlineClientT<T> for OnlineClient<T> {
@@ -521,7 +518,7 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
let scale_val = match chunk.to_value() {
Ok(v) => v,
Err(e) => return Some(Err(e)),
Err(e) => return Some(Err(e.into())),
};
let Some(Ok(spec_version)) = scale_val
@@ -1,144 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{signed_extensions, ExtrinsicParams};
use super::{Config, Header};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
T,
(
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<T>,
signed_extensions::CheckMortality<T>,
signed_extensions::ChargeAssetTxPayment<T>,
signed_extensions::ChargeTransactionPayment,
),
>;
/// A builder that outputs the set of [`super::ExtrinsicParams::OtherParams`] required for
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
/// chain; such values will simply be ignored if so.
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
/// `None` means the tx will be immortal.
mortality: Option<Mortality<T::Hash>>,
/// `None` means we'll use the native token.
tip_of_asset_id: Option<T::AssetId>,
tip: u128,
tip_of: u128,
}
struct Mortality<Hash> {
/// Block hash that mortality starts from
checkpoint_hash: Hash,
/// Block number that mortality starts from (must
// point to the same block as the hash above)
checkpoint_number: u64,
/// How many blocks the tx is mortal for
period: u64,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
mortality: None,
tip: 0,
tip_of: 0,
tip_of_asset_id: None,
}
}
}
impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
/// Configure new extrinsic params. We default to providing no tip
/// and using an immortal transaction unless otherwise configured
pub fn new() -> Self {
Default::default()
}
/// Make the transaction mortal, given a block header that it should be mortal from,
/// and the number of blocks (roughly; it'll be rounded to a power of two) that it will
/// be mortal for.
pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block.hash(),
checkpoint_number: from_block.number().into(),
period: for_n_blocks,
});
self
}
/// Make the transaction mortal, given a block number and block hash (which must both point to
/// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be
/// rounded to a power of two) that it will be mortal for.
///
/// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash
/// and number align.
pub fn mortal_unchecked(
mut self,
from_block_number: u64,
from_block_hash: T::Hash,
for_n_blocks: u64,
) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block_hash,
checkpoint_number: from_block_number,
period: for_n_blocks,
});
self
}
/// Provide a tip to the block author in the chain's native token.
pub fn tip(mut self, tip: u128) -> Self {
self.tip = tip;
self.tip_of = tip;
self.tip_of_asset_id = None;
self
}
/// Provide a tip to the block author using the token denominated by the `asset_id` provided. This
/// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this
/// case, no tip will be given.
pub fn tip_of(mut self, tip: u128, asset_id: T::AssetId) -> Self {
self.tip = 0;
self.tip_of = tip;
self.tip_of_asset_id = Some(asset_id);
self
}
/// Build the extrinsic parameters.
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::OtherParams {
let check_mortality_params = if let Some(mortality) = self.mortality {
signed_extensions::CheckMortalityParams::mortal(
mortality.period,
mortality.checkpoint_number,
mortality.checkpoint_hash,
)
} else {
signed_extensions::CheckMortalityParams::immortal()
};
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
signed_extensions::ChargeTransactionPaymentParams::tip(self.tip);
(
(),
(),
(),
(),
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
)
}
}
-81
View File
@@ -1,81 +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.
//! This module contains a trait which controls the parameters that must
//! be provided in order to successfully construct an extrinsic.
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use crate::{client::OfflineClientT, Config};
use core::fmt::Debug;
/// An error that can be emitted when trying to construct an instance of [`ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ExtrinsicParamsError {
/// Cannot find a type id in the metadata. The context provides some additional
/// information about the source of the error (eg the signed extension name).
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
type_id: u32,
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
/// A signed extension in use on some chain was not provided.
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
UnknownSignedExtension(String),
/// Some custom error.
#[error("Error constructing extrinsic parameters: {0}")]
Custom(CustomExtrinsicParamsError),
}
/// A custom error.
pub type CustomExtrinsicParamsError = Box<dyn std::error::Error + Send + Sync + 'static>;
impl From<std::convert::Infallible> for ExtrinsicParamsError {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}
impl From<CustomExtrinsicParamsError> for ExtrinsicParamsError {
fn from(value: CustomExtrinsicParamsError) -> Self {
ExtrinsicParamsError::Custom(value)
}
}
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
type OtherParams;
/// Construct a new instance of our [`ExtrinsicParams`].
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError>;
}
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
/// defines how to encode the "additional" and "extra" params. Both functions
/// are optional and will encode nothing by default.
pub trait ExtrinsicParamsEncoder: 'static {
/// This is expected to SCALE encode the "signed extra" parameters
/// to some buffer that has been provided. These are the parameters
/// which are sent along with the transaction, as well as taken into
/// account when signing the transaction.
fn encode_extra_to(&self, _v: &mut Vec<u8>) {}
/// This is expected to SCALE encode the "additional" parameters
/// to some buffer that has been provided. These parameters are _not_
/// sent along with the transaction, but are taken into account when
/// signing it, meaning the client and node must agree on their values.
fn encode_additional_to(&self, _v: &mut Vec<u8>) {}
}
-154
View File
@@ -1,154 +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.
//! This module provides a [`Config`] type, which is used to define various
//! types that are important in order to speak to a particular chain.
//! [`SubstrateConfig`] provides a default set of these types suitable for the
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
mod default_extrinsic_params;
mod extrinsic_params;
pub mod polkadot;
pub mod signed_extensions;
pub mod substrate;
use crate::macros::cfg_substrate_compat;
use codec::{Decode, Encode};
use core::fmt::Debug;
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use serde::{de::DeserializeOwned, Serialize};
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use signed_extensions::SignedExtension;
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
/// Runtime types.
// Note: the `Send + Sync + 'static` bound isn't strictly required, but currently deriving
// TypeInfo automatically applies a 'static bound to all generic types (including this one),
// And we want the compiler to infer `Send` and `Sync` OK for things which have `T: Config`
// rather than having to `unsafe impl` them ourselves.
pub trait Config: Sized + Send + Sync + 'static {
/// The output of the `Hasher` function.
type Hash: BlockHash;
/// The account ID type.
type AccountId: Debug + Clone + Encode;
/// The address type.
type Address: Debug + Encode + From<Self::AccountId>;
/// The signature type.
type Signature: Debug + Encode;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type Hasher: Debug + Hasher<Output = Self::Hash>;
/// The block header.
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: ExtrinsicParams<Self>;
/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType;
}
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
pub type OtherParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams;
/// Block hashes must conform to a bunch of things to be used in Subxt.
pub trait BlockHash:
Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ std::hash::Hash
{
}
impl<T> BlockHash for T where
T: Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ std::hash::Hash
{
}
/// This represents the hasher used by a node to hash things like block headers
/// and extrinsics.
pub trait Hasher {
/// The type given back from the hash operation
type Output;
/// Hash some bytes to the given output type.
fn hash(s: &[u8]) -> Self::Output;
/// Hash some SCALE encodable type to the given output type.
fn hash_of<S: Encode>(s: &S) -> Self::Output {
let out = s.encode();
Self::hash(&out)
}
}
/// This represents the block header type used by a node.
pub trait Header: Sized + Encode + Decode {
/// The block number type for this header.
type Number: Into<u64>;
/// The hasher used to hash this header.
type Hasher: Hasher;
/// Return the block number of this header.
fn number(&self) -> Self::Number;
/// Hash this header.
fn hash(&self) -> <Self::Hasher as Hasher>::Output {
Self::Hasher::hash_of(self)
}
}
cfg_substrate_compat! {
/// implement subxt's Hasher and Header traits for some substrate structs
mod substrate_impls {
use super::*;
impl<T: sp_runtime::traits::Header> Header for T
where
<T as sp_runtime::traits::Header>::Number: Into<u64>,
{
type Number = T::Number;
type Hasher = T::Hashing;
fn number(&self) -> Self::Number {
*self.number()
}
}
impl<T: sp_runtime::traits::Hash> Hasher for T {
type Output = T::Output;
fn hash(s: &[u8]) -> Self::Output {
<T as sp_runtime::traits::Hash>::hash(s)
}
}
}
}
-33
View File
@@ -1,33 +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.
//! Polkadot specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
use crate::SubstrateConfig;
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Polkadot nodes.
pub enum PolkadotConfig {}
impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = MultiAddress<Self::AccountId, ()>;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
type AssetId = u32;
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for a polkadot node.
pub type PolkadotExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type PolkadotExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
-496
View File
@@ -1,496 +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.
//! This module contains implementations for common signed extensions, each
//! of which implements [`SignedExtension`], and can be used in conjunction with
//! [`AnyOf`] to configure the set of signed extensions which are known about
//! when interacting with a chain.
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
use crate::utils::Era;
use crate::{client::OfflineClientT, Config};
use codec::{Compact, Encode};
use core::fmt::Debug;
use derivative::Derivative;
use scale_decode::DecodeAsType;
use scale_info::PortableRegistry;
use std::collections::HashMap;
/// A single [`SignedExtension`] has a unique name, but is otherwise the
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
/// additional data.
pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
/// The type representing the `extra` bytes of a signed extension.
/// Decoding from this type should be symmetrical to the respective
/// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension.
type Decoded: DecodeAsType;
/// This should return true if the signed extension matches the details given.
/// Often, this will involve just checking that the identifier given matches that of the
/// extension in question.
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool;
}
/// The [`CheckSpecVersion`] signed extension.
pub struct CheckSpecVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
type OtherParams = ();
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckSpecVersion(client.runtime_version().spec_version))
}
}
impl ExtrinsicParamsEncoder for CheckSpecVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckSpecVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckSpecVersion"
}
}
/// The [`CheckNonce`] signed extension.
pub struct CheckNonce(Compact<u64>);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type OtherParams = ();
fn new<Client: OfflineClientT<T>>(
nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckNonce(Compact(nonce)))
}
}
impl ExtrinsicParamsEncoder for CheckNonce {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckNonce {
type Decoded = u64;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckNonce"
}
}
/// The [`CheckTxVersion`] signed extension.
pub struct CheckTxVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
type OtherParams = ();
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckTxVersion(client.runtime_version().transaction_version))
}
}
impl ExtrinsicParamsEncoder for CheckTxVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckTxVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckTxVersion"
}
}
/// The [`CheckGenesis`] signed extension.
pub struct CheckGenesis<T: Config>(T::Hash);
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type OtherParams = ();
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckGenesis(client.genesis_hash()))
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckGenesis"
}
}
/// The [`CheckMortality`] signed extension.
pub struct CheckMortality<T: Config> {
era: Era,
checkpoint: T::Hash,
}
/// Parameters to configure the [`CheckMortality`] signed extension.
pub struct CheckMortalityParams<T: Config> {
era: Era,
checkpoint: Option<T::Hash>,
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
Self {
era: Default::default(),
checkpoint: Default::default(),
}
}
}
impl<T: Config> CheckMortalityParams<T> {
/// Configure a mortal transaction. The `period` is (roughly) how many
/// blocks the transaction will be valid for. The `block_number` and
/// `block_hash` should both point to the same block, and are the block that
/// the transaction is mortal from.
pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self {
CheckMortalityParams {
era: Era::mortal(period, block_number),
checkpoint: Some(block_hash),
}
}
/// An immortal transaction.
pub fn immortal() -> Self {
CheckMortalityParams {
era: Era::Immortal,
checkpoint: None,
}
}
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type OtherParams = CheckMortalityParams<T>;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckMortality {
era: other_params.era,
checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()),
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.era.encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
type Decoded = Era;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMortality"
}
}
/// The [`ChargeAssetTxPayment`] signed extension.
#[derive(Derivative, DecodeAsType)]
#[derivative(Clone(bound = "T::AssetId: Clone"), Debug(bound = "T::AssetId: Debug"))]
#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")]
pub struct ChargeAssetTxPayment<T: Config> {
tip: Compact<u128>,
asset_id: Option<T::AssetId>,
}
impl<T: Config> ChargeAssetTxPayment<T> {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}
/// Tip to the extrinsic author using the asset ID given.
pub fn asset_id(&self) -> Option<&T::AssetId> {
self.asset_id.as_ref()
}
}
/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
pub struct ChargeAssetTxPaymentParams<T: Config> {
tip: u128,
asset_id: Option<T::AssetId>,
}
impl<T: Config> Default for ChargeAssetTxPaymentParams<T> {
fn default() -> Self {
ChargeAssetTxPaymentParams {
tip: Default::default(),
asset_id: Default::default(),
}
}
}
impl<T: Config> ChargeAssetTxPaymentParams<T> {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeAssetTxPaymentParams {
tip: 0,
asset_id: None,
}
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: None,
}
}
/// Tip the extrinsic author using the asset ID given.
pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: Some(asset_id),
}
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
type OtherParams = ChargeAssetTxPaymentParams<T>;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeAssetTxPayment {
tip: Compact(other_params.tip),
asset_id: other_params.asset_id,
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
(self.tip, &self.asset_id).encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeAssetTxPayment"
}
}
/// The [`ChargeTransactionPayment`] signed extension.
#[derive(Clone, Debug, DecodeAsType)]
pub struct ChargeTransactionPayment {
tip: Compact<u128>,
}
impl ChargeTransactionPayment {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}
}
/// Parameters to configure the [`ChargeTransactionPayment`] signed extension.
#[derive(Default)]
pub struct ChargeTransactionPaymentParams {
tip: u128,
}
impl ChargeTransactionPaymentParams {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeTransactionPaymentParams { tip: 0 }
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeTransactionPaymentParams { tip }
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type OtherParams = ChargeTransactionPaymentParams;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeTransactionPayment {
tip: Compact(other_params.tip),
})
}
}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeTransactionPayment"
}
}
/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever
/// ones are actually required for the chain in the correct order, ignoring the rest. This
/// is a sensible default, and allows for a single configuration to work across multiple chains.
pub struct AnyOf<T, Params> {
params: Vec<Box<dyn ExtrinsicParamsEncoder>>,
_marker: std::marker::PhantomData<(T, Params)>,
}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
// We do some magic when the tuple is wrapped in AnyOf. We
// look at the metadata, and use this to select and make use of only the extensions
// that we actually need for the chain we're dealing with.
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
{
type OtherParams = ($($ident::OtherParams,)+);
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, ExtrinsicParamsError> {
let metadata = client.metadata();
let types = metadata.types();
// For each signed extension in the tuple, find the matching index in the metadata, if
// there is one, and add it to a map with that index as the key.
let mut exts_by_index = HashMap::new();
$({
for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() {
// Skip over any exts that have a match already:
if exts_by_index.contains_key(&idx) {
continue
}
// Break and record as soon as we find a match:
if $ident::matches(e.identifier(), e.extra_ty(), types) {
let ext = $ident::new(nonce, client.clone(), other_params.$index)?;
let boxed_ext: Box<dyn ExtrinsicParamsEncoder> = Box::new(ext);
exts_by_index.insert(idx, boxed_ext);
break
}
}
})+
// Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet.
let mut params = Vec::new();
for (idx, e) in metadata.extrinsic().signed_extensions().iter().enumerate() {
let Some(ext) = exts_by_index.remove(&idx) else {
if is_type_empty(e.extra_ty(), types) {
continue
} else {
return Err(ExtrinsicParamsError::UnknownSignedExtension(e.identifier().to_owned()));
}
};
params.push(ext);
}
Ok(AnyOf {
params,
_marker: std::marker::PhantomData
})
}
}
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
{
fn encode_extra_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_extra_to(v);
}
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_additional_to(v);
}
}
}
}
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0);
impl_tuples!(A 0, B 1);
impl_tuples!(A 0, B 1, C 2);
impl_tuples!(A 0, B 1, C 2, D 3);
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20);
};
/// Checks to see whether the type being given is empty, ie would require
/// 0 bytes to encode.
fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
let Some(ty) = types.resolve(type_id) else {
// Can't resolve; type may not be empty. Not expected to hit this.
return false;
};
use scale_info::TypeDef;
match &ty.type_def {
TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
// Explicitly list these in case any additions are made in the future.
TypeDef::BitSequence(_)
| TypeDef::Variant(_)
| TypeDef::Sequence(_)
| TypeDef::Compact(_)
| TypeDef::Primitive(_) => false,
}
}
-278
View File
@@ -1,278 +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.
//! Substrate specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header};
use codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
// create an instance of it.
pub enum SubstrateConfig {}
impl Config for SubstrateConfig {
type Hash = H256;
type AccountId = AccountId32;
type Address = MultiAddress<Self::AccountId, u32>;
type Signature = MultiSignature;
type Hasher = BlakeTwo256;
type Header = SubstrateHeader<u32, BlakeTwo256>;
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type AssetId = u32;
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for the default substrate node.
pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
/// A type that can hash values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
pub struct BlakeTwo256;
impl Hasher for BlakeTwo256 {
type Output = H256;
fn hash(s: &[u8]) -> Self::Output {
sp_core_hashing::blake2_256(s).into()
}
}
/// A generic Substrate header type, adapted from `sp_runtime::generic::Header`.
/// The block number and hasher can be configured to adapt this for other nodes.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubstrateHeader<N: Copy + Into<U256> + TryFrom<U256>, H: Hasher> {
/// The parent hash.
pub parent_hash: H::Output,
/// The block number.
#[serde(
serialize_with = "serialize_number",
deserialize_with = "deserialize_number"
)]
#[codec(compact)]
pub number: N,
/// The state trie merkle root
pub state_root: H::Output,
/// The merkle root of the extrinsics.
pub extrinsics_root: H::Output,
/// A chain-specific digest of data useful for light clients or referencing auxiliary data.
pub digest: Digest,
}
impl<N, H> Header for SubstrateHeader<N, H>
where
N: Copy + Into<u64> + Into<U256> + TryFrom<U256> + Encode,
H: Hasher + Encode,
SubstrateHeader<N, H>: Encode + Decode,
{
type Number = N;
type Hasher = H;
fn number(&self) -> Self::Number {
self.number
}
}
/// Generic header digest. From `sp_runtime::generic::digest`.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
pub struct Digest {
/// A list of digest items.
pub logs: Vec<DigestItem>,
}
/// Digest item that is able to encode/decode 'system' digest items and
/// provide opaque access to other items. From `sp_runtime::generic::digest`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DigestItem {
/// A pre-runtime digest.
///
/// These are messages from the consensus engine to the runtime, although
/// the consensus engine can (and should) read them itself to avoid
/// code and state duplication. It is erroneous for a runtime to produce
/// these, but this is not (yet) checked.
///
/// NOTE: the runtime is not allowed to panic or fail in an `on_initialize`
/// call if an expected `PreRuntime` digest is not present. It is the
/// responsibility of a external block verifier to check this. Runtime API calls
/// will initialize the block without pre-runtime digests, so initialization
/// cannot fail when they are missing.
PreRuntime(ConsensusEngineId, Vec<u8>),
/// A message from the runtime to the consensus engine. This should *never*
/// be generated by the native code of any consensus engine, but this is not
/// checked (yet).
Consensus(ConsensusEngineId, Vec<u8>),
/// Put a Seal on it. This is only used by native code, and is never seen
/// by runtimes.
Seal(ConsensusEngineId, Vec<u8>),
/// Some other thing. Unsupported and experimental.
Other(Vec<u8>),
/// An indication for the light clients that the runtime execution
/// environment is updated.
///
/// Currently this is triggered when:
/// 1. Runtime code blob is changed or
/// 2. `heap_pages` value is changed.
RuntimeEnvironmentUpdated,
}
// From sp_runtime::generic, DigestItem enum indexes are encoded using this:
#[repr(u32)]
#[derive(Encode, Decode)]
enum DigestItemType {
Other = 0u32,
Consensus = 4u32,
Seal = 5u32,
PreRuntime = 6u32,
RuntimeEnvironmentUpdated = 8u32,
}
impl Encode for DigestItem {
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
match self {
Self::Consensus(val, data) => {
DigestItemType::Consensus.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Seal(val, sig) => {
DigestItemType::Seal.encode_to(&mut v);
(val, sig).encode_to(&mut v);
}
Self::PreRuntime(val, data) => {
DigestItemType::PreRuntime.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Other(val) => {
DigestItemType::Other.encode_to(&mut v);
val.encode_to(&mut v);
}
Self::RuntimeEnvironmentUpdated => {
DigestItemType::RuntimeEnvironmentUpdated.encode_to(&mut v);
}
}
v
}
}
impl Decode for DigestItem {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let item_type: DigestItemType = Decode::decode(input)?;
match item_type {
DigestItemType::PreRuntime => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::PreRuntime(vals.0, vals.1))
}
DigestItemType::Consensus => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Consensus(vals.0, vals.1))
}
DigestItemType::Seal => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Seal(vals.0, vals.1))
}
DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)),
DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated),
}
}
}
/// Consensus engine unique ID. From `sp_runtime::ConsensusEngineId`.
pub type ConsensusEngineId = [u8; 4];
impl serde::Serialize for DigestItem {
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.using_encoded(|bytes| impl_serde::serialize::serialize(bytes, seq))
}
}
impl<'a> serde::Deserialize<'a> for DigestItem {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let r = impl_serde::serialize::deserialize(de)?;
Decode::decode(&mut &r[..])
.map_err(|e| serde::de::Error::custom(format!("Decode error: {e}")))
}
}
fn serialize_number<S, T: Copy + Into<U256>>(val: &T, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let u256: U256 = (*val).into();
serde::Serialize::serialize(&u256, s)
}
fn deserialize_number<'a, D, T: TryFrom<U256>>(d: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'a>,
{
// At the time of writing, Smoldot gives back block numbers in numeric rather
// than hex format. So let's support deserializing from both here:
use crate::backend::legacy::rpc_methods::NumberOrHex;
let number_or_hex = NumberOrHex::deserialize(d)?;
let u256 = number_or_hex.into_u256();
TryFrom::try_from(u256).map_err(|_| serde::de::Error::custom("Try from failed"))
}
#[cfg(test)]
mod test {
use super::*;
// Smoldot returns numeric block numbers in the header at the time of writing;
// ensure we can deserialize them properly.
#[test]
fn can_deserialize_numeric_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": 4,
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<u32, BlakeTwo256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
// Substrate returns hex block numbers; ensure we can also deserialize those OK.
#[test]
fn can_deserialize_hex_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": "0x04",
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<u32, BlakeTwo256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
}
-100
View File
@@ -1,100 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::dynamic::DecodedValueThunk;
use derivative::Derivative;
use std::borrow::Cow;
use subxt_core::metadata::DecodeWithMetadata;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents the address of a constant.
#[derive(Derivative)]
#[derivative(Clone(bound = ""), Debug(bound = ""))]
pub struct Address<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<ReturnTy>,
}
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = Address<DecodedValueThunk>;
impl<ReturnTy> Address<ReturnTy> {
/// Create a new [`Address`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
constant_name: Cow::Owned(constant_name.into()),
constant_hash: None,
_marker: std::marker::PhantomData,
}
}
/// Create a new [`Address`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant_name),
constant_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
DynamicAddress::new(pallet_name, constant_name)
}
+2 -30
View File
@@ -36,19 +36,7 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// 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> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = self
.client
.metadata()
.pallet_by_name_err(address.pallet_name())?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
subxt_core::constants::validate_constant(&self.client.metadata(), address)?;
Ok(())
}
@@ -59,23 +47,7 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
&self,
address: &Address,
) -> Result<Address::Target, Error> {
let metadata = self.client.metadata();
// 1. Validate constant shape if hash given:
self.validate(address)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_by_name(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut constant.value(),
constant.ty(),
&metadata,
)?;
let value = subxt_core::constants::get_constant(&self.client.metadata(), address)?;
Ok(value)
}
}
+1 -2
View File
@@ -4,8 +4,7 @@
//! Types associated with accessing constants.
mod constant_address;
mod constants_client;
pub use constant_address::{dynamic, Address, ConstantAddress, DynamicAddress};
pub use constants_client::ConstantsClient;
pub use subxt_core::constants::{dynamic, Address, ConstantAddress, DynamicAddress};
@@ -1,8 +1,7 @@
use derivative::Derivative;
use std::marker::PhantomData;
use crate::dynamic::DecodedValueThunk;
use subxt_core::metadata::DecodeWithMetadata;
use subxt_core::{dynamic::DecodedValueThunk, metadata::DecodeWithMetadata};
/// This represents the address of a custom value in in the metadata.
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch
@@ -93,7 +93,6 @@ impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
#[cfg(test)]
mod tests {
use crate::backend::RuntimeVersion;
use crate::custom_values::CustomValuesClient;
use crate::{Metadata, OfflineClient, SubstrateConfig};
use codec::Encode;
@@ -101,6 +100,7 @@ mod tests {
use scale_info::form::PortableForm;
use scale_info::TypeInfo;
use std::collections::BTreeMap;
use subxt_core::RuntimeVersion;
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
pub struct Person {
+1 -1
View File
@@ -8,7 +8,7 @@
use core::fmt::Debug;
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType};
use std::borrow::Cow;
use subxt_core::metadata::{DecodeWithMetadata, Metadata};
use subxt_core::metadata::{DecodeWithMetadata, MetadatExt, Metadata};
use super::{Error, MetadataError};
+17 -31
View File
@@ -17,13 +17,12 @@ pub use dispatch_error::{
ArithmeticError, DispatchError, ModuleError, TokenError, TransactionalError,
};
pub use subxt_core::metadata::MetadataError;
pub use subxt_core::MetadataError;
// Re-expose the errors we use from other crates here:
pub use crate::config::ExtrinsicParamsError;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
pub use subxt_core::metadata::Metadata;
pub use subxt_core::{ExtrinsicParamsError, Metadata};
pub use subxt_metadata::TryFromError as MetadataTryFromError;
/// The underlying error enum, generic over the type held by the `Runtime`
@@ -46,7 +45,7 @@ pub enum Error {
Serialization(#[from] serde_json::error::Error),
/// Error working with metadata.
#[error("Metadata error: {0}")]
Metadata(#[from] MetadataError),
Metadata(#[from] subxt_core::MetadataError),
/// Error decoding metadata.
#[error("Metadata Decoding error: {0}")]
MetadataDecoding(#[from] MetadataTryFromError),
@@ -64,13 +63,13 @@ pub enum Error {
Transaction(#[from] TransactionError),
/// Error constructing the appropriate extrinsic params.
#[error("Extrinsic params error: {0}")]
ExtrinsicParams(#[from] ExtrinsicParamsError),
ExtrinsicParams(#[from] subxt_core::ExtrinsicParamsError),
/// Block related error.
#[error("Block error: {0}")]
Block(#[from] BlockError),
/// An error encoding a storage address.
#[error("Error encoding storage address: {0}")]
StorageAddress(#[from] StorageAddressError),
StorageAddress(#[from] subxt_core::StorageAddressError),
/// The bytes representing an error that we were unable to decode.
#[error("An error occurred but it could not be decoded: {0:?}")]
Unknown(Vec<u8>),
@@ -84,6 +83,18 @@ pub enum Error {
Other(String),
}
impl From<subxt_core::Error> for Error {
fn from(value: subxt_core::Error) -> Self {
match value {
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),
}
}
}
impl<'a> From<&'a str> for Error {
fn from(error: &'a str) -> Self {
Error::Other(error.into())
@@ -176,28 +187,3 @@ pub enum TransactionError {
#[error("The transaction was dropped: {0}")]
Dropped(String),
}
/// Something went wrong trying to encode a storage address.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageAddressError {
/// Storage map type must be a composite type.
#[error("Storage map type must be a composite type")]
MapTypeMustBeTuple,
/// Storage lookup does not have the expected number of keys.
#[error("Storage lookup requires {expected} keys but got {actual} keys")]
WrongNumberOfKeys {
/// The actual number of keys needed, based on the metadata.
actual: usize,
/// The number of keys provided in the storage address.
expected: usize,
},
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[error("Storage entry in metadata does not have the correct number of hashers to fields")]
WrongNumberOfHashers {
/// The number of hashers in the metadata for this storage entry.
hashers: usize,
/// The number of fields in the metadata for this storage entry.
fields: usize,
},
}
+1 -1
View File
@@ -15,7 +15,7 @@ use codec::{Compact, Decode};
use derivative::Derivative;
use scale_decode::DecodeAsType;
use std::sync::Arc;
use subxt_core::metadata::types::PalletMetadata;
use subxt_core::metadata::{types::PalletMetadata, MetadatExt};
/// A collection of events obtained from a block, bundled with the necessary
/// information needed to decode and iterate over them.
+3 -2
View File
@@ -45,7 +45,6 @@ pub use getrandom as _;
pub mod backend;
pub mod blocks;
pub mod client;
pub mod config;
pub mod constants;
pub mod custom_values;
pub mod dynamic;
@@ -64,10 +63,12 @@ mod macros;
// but leave most types behind their respective modules.
pub use crate::{
client::{OfflineClient, OnlineClient},
config::{Config, PolkadotConfig, SubstrateConfig},
error::Error,
};
pub use subxt_core::config;
pub use subxt_core::config::{Config, PolkadotConfig, SubstrateConfig};
use subxt_core::metadata::Metadata;
/// Re-export external crates that are made use of in the subxt API.
+1 -2
View File
@@ -5,9 +5,8 @@
//! Types associated with executing runtime API calls.
mod runtime_client;
mod runtime_payload;
mod runtime_types;
pub use runtime_client::RuntimeApiClient;
pub use runtime_payload::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
pub use runtime_types::RuntimeApi;
pub use subxt_core::runtime_api::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
-186
View File
@@ -1,186 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use core::marker::PhantomData;
use derivative::Derivative;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use std::borrow::Cow;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::Error;
use subxt_core::metadata::{DecodeWithMetadata, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait RuntimeApiPayload {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Derivative)]
#[derivative(
Clone(bound = "ArgsData: Clone"),
Debug(bound = "ArgsData: std::fmt::Debug")
)]
pub struct Payload<ArgsData, ReturnTy> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A dynamic runtime API payload.
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
) -> Self {
Payload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload {
Payload::new(trait_name, method_name, args_data.into())
}
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::{
use codec::Decode;
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
use subxt_core::metadata::DecodeWithMetadata;
use subxt_core::metadata::{DecodeWithMetadata, MetadatExt};
use super::RuntimeApiPayload;
+4 -7
View File
@@ -4,25 +4,22 @@
//! Types associated with accessing and working with storage items.
mod storage_address;
mod storage_client;
mod storage_type;
pub mod utils;
pub use storage_client::StorageClient;
pub use storage_type::Storage;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::storage_address::{
pub use subxt_core::storage::storage_address::{
dynamic, make_static_storage_map_key, Address, DynamicAddress, StaticStorageMapKey,
StorageAddress, Yes,
StorageAddress,
};
pub use subxt_core::Yes;
}
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
pub use subxt_core::storage::storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
-272
View File
@@ -1,272 +0,0 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::DecodedValueThunk,
error::{Error, MetadataError, StorageAddressError},
utils::{Encoded, Static},
};
use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
use derivative::Derivative;
use scale_info::TypeDef;
use std::borrow::Cow;
use subxt_metadata::{StorageEntryType, StorageHasher};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// Can an entry be fetched from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsFetchable;
/// Can a default entry be obtained from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsDefaultable;
/// Can this address be iterated over?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsIterable;
/// The name of the pallet that the entry lives under.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
fn entry_name(&self) -> &str;
/// Output the non-prefix bytes; that is, any additional bytes that need
/// to be appended to the key to dig into maps.
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error>;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// Used to signal whether a [`StorageAddress`] can be iterated,
/// fetched and returned with a default value in the type system.
pub struct Yes;
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Derivative)]
#[derivative(
Clone(bound = "StorageKey: Clone"),
Debug(bound = "StorageKey: std::fmt::Debug")
)]
pub struct Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
storage_entry_keys: Vec<StorageKey>,
validation_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
}
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
/// has no restriction on what it can be used for (since we don't statically know).
pub type DynamicAddress<StorageKey> = Address<StorageKey, DecodedValueThunk, Yes, Yes, Yes>;
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
where
StorageKey: EncodeWithMetadata,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`Address`] to use to access a storage entry.
pub fn new(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
entry_name: Cow::Owned(entry_name.into()),
storage_entry_keys: storage_entry_keys.into_iter().collect(),
validation_hash: None,
_marker: std::marker::PhantomData,
}
}
/// Create a new [`Address`] 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,
storage_entry_keys: Vec<StorageKey>,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
storage_entry_keys: storage_entry_keys.into_iter().collect(),
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Return bytes representing the root of this storage entry (ie a hash of
/// the pallet and entry name). Use [`crate::storage::StorageClient::address_bytes()`]
/// to obtain the bytes representing the entire address.
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
}
}
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
where
StorageKey: EncodeWithMetadata,
ReturnTy: DecodeWithMetadata,
{
type Target = ReturnTy;
type IsFetchable = Fetchable;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
match entry.entry_type() {
StorageEntryType::Plain(_) => {
if !self.storage_entry_keys.is_empty() {
Err(StorageAddressError::WrongNumberOfKeys {
expected: 0,
actual: self.storage_entry_keys.len(),
}
.into())
} else {
Ok(())
}
}
StorageEntryType::Map {
hashers, key_ty, ..
} => {
let ty = metadata
.types()
.resolve(*key_ty)
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
// If the provided keys are empty, the storage address must be
// equal to the storage root address.
if self.storage_entry_keys.is_empty() {
return Ok(());
}
// If the key is a tuple, we encode each value to the corresponding tuple type.
// If the key is not a tuple, encode a single value to the key type.
let type_ids = match &ty.type_def {
TypeDef::Tuple(tuple) => {
either::Either::Left(tuple.fields.iter().map(|f| f.id))
}
_other => either::Either::Right(std::iter::once(*key_ty)),
};
if type_ids.len() < self.storage_entry_keys.len() {
// Provided more keys than fields.
return Err(StorageAddressError::WrongNumberOfKeys {
expected: type_ids.len(),
actual: self.storage_entry_keys.len(),
}
.into());
}
if hashers.len() == 1 {
// One hasher; hash a tuple of all SCALE encoded bytes with the one hash function.
let mut input = Vec::new();
let iter = self.storage_entry_keys.iter().zip(type_ids);
for (key, type_id) in iter {
key.encode_with_metadata(type_id, metadata, &mut input)?;
}
hash_bytes(&input, &hashers[0], bytes);
Ok(())
} else if hashers.len() >= type_ids.len() {
let iter = self.storage_entry_keys.iter().zip(type_ids).zip(hashers);
// A hasher per field; encode and hash each field independently.
for ((key, type_id), hasher) in iter {
let mut input = Vec::new();
key.encode_with_metadata(type_id, metadata, &mut input)?;
hash_bytes(&input, hasher, bytes);
}
Ok(())
} else {
// Provided more fields than hashers.
Err(StorageAddressError::WrongNumberOfHashers {
hashers: hashers.len(),
fields: type_ids.len(),
}
.into())
}
}
}
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A static storage key; this is some pre-encoded bytes
/// likely provided by the generated interface.
pub type StaticStorageMapKey = Static<Encoded>;
// Used in codegen to construct the above.
#[doc(hidden)]
pub fn make_static_storage_map_key<T: codec::Encode>(t: T) -> StaticStorageMapKey {
Static(Encoded(t.encode()))
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<StorageKey: EncodeWithMetadata>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> DynamicAddress<StorageKey> {
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_core_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_core_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_core_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_core_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_core_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_core_hashing::twox_64(input));
bytes.extend(input);
}
}
}
+10 -7
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},
utils, StorageAddress,
};
use super::{storage_type::Storage, StorageAddress};
use crate::{
backend::BlockRef,
client::{OfflineClientT, OnlineClientT},
@@ -14,6 +11,10 @@ use crate::{
};
use derivative::Derivative;
use std::{future::Future, marker::PhantomData};
use subxt_core::{
metadata::MetadatExt,
storage::utils::{storage_address_bytes, storage_address_root_bytes, validate_storage_address},
};
/// Query the runtime storage.
#[derive(Derivative)]
@@ -45,13 +46,14 @@ where
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)
validate_storage_address(address, pallet_metadata)?;
Ok(())
}
/// 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> {
utils::storage_address_root_bytes(address)
storage_address_root_bytes(address)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
@@ -63,7 +65,8 @@ where
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
utils::storage_address_bytes(address, &self.client.metadata())
let bytes = storage_address_bytes(address, &self.client.metadata())?;
Ok(bytes)
}
}
+12 -7
View File
@@ -2,7 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_address::{StorageAddress, Yes};
use subxt_core::{
storage::utils::{
decode_storage_with_metadata, lookup_entry_details, storage_address_bytes,
validate_storage_address,
},
StorageAddress, Yes,
};
use crate::{
backend::{BackendExt, BlockRef},
@@ -14,8 +20,7 @@ use codec::Decode;
use derivative::Derivative;
use futures::StreamExt;
use std::{future::Future, marker::PhantomData};
use subxt_core::metadata::{DecodeWithMetadata, Metadata};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
use subxt_core::metadata::DecodeWithMetadata;
/// This is returned from a couple of storage functions.
pub use crate::backend::StreamOfResults;
@@ -132,7 +137,7 @@ where
validate_storage_address(address, pallet)?;
// Look up the return type ID to enable DecodeWithMetadata:
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
let lookup_bytes = storage_address_bytes(address, &metadata)?;
if let Some(data) = client.fetch_raw(lookup_bytes).await? {
let val =
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
@@ -163,7 +168,7 @@ where
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 return_ty_id = storage_entry.entry_type().value_ty();
let bytes = &mut storage_entry.default_bytes();
let val = Address::Target::decode_with_metadata(bytes, return_ty_id, &metadata)?;
@@ -226,10 +231,10 @@ where
// Look up the return type for flexible decoding. Do this once here to avoid
// potentially doing it every iteration if we used `decode_storage_with_metadata`
// in the iterator.
let return_type_id = return_type_from_storage_entry_type(entry.entry_type());
let return_type_id = entry.entry_type().value_ty();
// The address bytes of this entry:
let address_bytes = super::utils::storage_address_bytes(&address, &metadata)?;
let address_bytes = storage_address_bytes(&address, &metadata)?;
let s = client
.backend()
-41
View File
@@ -1,41 +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 super::StorageAddress;
use crate::error::Error;
use subxt_core::metadata::Metadata;
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub(crate) fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_core_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_core_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(crate) 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(crate) 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
}
+4 -10
View File
@@ -11,23 +11,17 @@
use crate::macros::cfg_substrate_compat;
mod signer;
mod tx_client;
mod tx_payload;
mod tx_progress;
// 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 self::signer::PairSigner;
}
pub use self::{
signer::Signer,
tx_client::{
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
ValidationResult,
},
tx_payload::{dynamic, BoxedPayload, DynamicPayload, Payload, TxPayload},
tx_progress::{TxInBlock, TxProgress, TxStatus},
};
pub use subxt_core::{
signer::Signer,
tx::{dynamic, BoxedPayload, DynamicPayload, Payload, TxPayload},
};
+4 -2
View File
@@ -9,12 +9,14 @@ use crate::{
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher},
error::{Error, MetadataError},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
};
use codec::{Compact, Decode, Encode};
use derivative::Derivative;
use sp_core_hashing::blake2_256;
use subxt_core::{metadata::MetadatExt, tx::TxPayload, Signer as SignerT};
use super::TxProgress;
/// A client for working with transactions.
#[derive(Derivative)]
@@ -122,7 +124,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = <T::ExtrinsicParams as ExtrinsicParams<T>>::new(
account_nonce,
self.client.clone(),
&self.client.base(),
other_params,
)?;
+6 -2
View File
@@ -337,7 +337,7 @@ mod test {
struct MockClient;
impl OfflineClientT<SubstrateConfig> for MockClient {
fn metadata(&self) -> subxt_core::metadata {
fn metadata(&self) -> subxt_core::Metadata {
unimplemented!("just a mock impl to satisfy trait bounds")
}
@@ -345,7 +345,11 @@ mod test {
unimplemented!("just a mock impl to satisfy trait bounds")
}
fn runtime_version(&self) -> crate::backend::RuntimeVersion {
fn runtime_version(&self) -> subxt_core::RuntimeVersion {
unimplemented!("just a mock impl to satisfy trait bounds")
}
fn base(&self) -> subxt_core::ClientBase<SubstrateConfig> {
unimplemented!("just a mock impl to satisfy trait bounds")
}
}