mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-16 20:11:03 +00:00
WIP: Add support in config for transaction creation and begin porting/implementing tx flows
This commit is contained in:
+14
-1
@@ -2,6 +2,7 @@ mod offline_client;
|
|||||||
mod online_client;
|
mod online_client;
|
||||||
|
|
||||||
use crate::config::{Config, HashFor};
|
use crate::config::{Config, HashFor};
|
||||||
|
use crate::transactions::Transactions;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use subxt_metadata::Metadata;
|
use subxt_metadata::Metadata;
|
||||||
|
|
||||||
@@ -33,11 +34,22 @@ impl<Client, T> ClientAtBlock<Client, T> {
|
|||||||
impl<Client, T> ClientAtBlock<Client, T>
|
impl<Client, T> ClientAtBlock<Client, T>
|
||||||
where
|
where
|
||||||
T: Config,
|
T: Config,
|
||||||
Client: OfflineClientAtBlockT,
|
Client: OfflineClientAtBlockT<T>,
|
||||||
{
|
{
|
||||||
|
/// Construct transactions.
|
||||||
|
pub fn tx(&self) -> Transactions<'_, T, Client> {
|
||||||
|
Transactions::new(&self.client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain a reference to the metadata.
|
||||||
pub fn metadata_ref(&self) -> &Metadata {
|
pub fn metadata_ref(&self) -> &Metadata {
|
||||||
self.client.metadata_ref()
|
self.client.metadata_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current block number.
|
||||||
|
pub fn block_number(&self) -> u64 {
|
||||||
|
self.client.block_number()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Client, T> ClientAtBlock<Client, T>
|
impl<Client, T> ClientAtBlock<Client, T>
|
||||||
@@ -45,6 +57,7 @@ where
|
|||||||
T: Config,
|
T: Config,
|
||||||
Client: OnlineClientAtBlockT<T>,
|
Client: OnlineClientAtBlockT<T>,
|
||||||
{
|
{
|
||||||
|
/// The current block hash.
|
||||||
pub fn block_hash(&self) -> HashFor<T> {
|
pub fn block_hash(&self) -> HashFor<T> {
|
||||||
self.client.block_hash()
|
self.client.block_hash()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::client::ClientAtBlock;
|
use crate::client::ClientAtBlock;
|
||||||
use crate::config::Config;
|
use crate::config::{Config, HashFor};
|
||||||
use crate::error::OfflineClientAtBlockError;
|
use crate::error::OfflineClientAtBlockError;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use subxt_metadata::Metadata;
|
use subxt_metadata::Metadata;
|
||||||
@@ -21,11 +21,11 @@ impl<T: Config> OfflineClient<T> {
|
|||||||
pub fn at_block(
|
pub fn at_block(
|
||||||
&self,
|
&self,
|
||||||
block_number: impl Into<u64>,
|
block_number: impl Into<u64>,
|
||||||
) -> Result<ClientAtBlock<OfflineClientAtBlock, T>, OfflineClientAtBlockError> {
|
) -> Result<ClientAtBlock<OfflineClientAtBlock<T>, T>, OfflineClientAtBlockError> {
|
||||||
let block_number = block_number.into();
|
let block_number = block_number.into();
|
||||||
let spec_version = self
|
let (spec_version, transaction_version) = self
|
||||||
.config
|
.config
|
||||||
.spec_version_for_block_number(block_number)
|
.spec_and_transaction_version_for_block_number(block_number)
|
||||||
.ok_or(OfflineClientAtBlockError::SpecVersionNotFound { block_number })?;
|
.ok_or(OfflineClientAtBlockError::SpecVersionNotFound { block_number })?;
|
||||||
|
|
||||||
let metadata = self
|
let metadata = self
|
||||||
@@ -33,29 +33,66 @@ impl<T: Config> OfflineClient<T> {
|
|||||||
.metadata_for_spec_version(spec_version)
|
.metadata_for_spec_version(spec_version)
|
||||||
.ok_or(OfflineClientAtBlockError::MetadataNotFound { spec_version })?;
|
.ok_or(OfflineClientAtBlockError::MetadataNotFound { spec_version })?;
|
||||||
|
|
||||||
Ok(ClientAtBlock::new(OfflineClientAtBlock { metadata }))
|
let genesis_hash = self.config.genesis_hash();
|
||||||
|
|
||||||
|
let offline_client_at_block = OfflineClientAtBlock {
|
||||||
|
metadata,
|
||||||
|
block_number,
|
||||||
|
genesis_hash,
|
||||||
|
spec_version,
|
||||||
|
transaction_version,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ClientAtBlock::new(offline_client_at_block))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OfflineClientAtBlock {
|
pub struct OfflineClientAtBlock<T: Config> {
|
||||||
metadata: Arc<Metadata>,
|
metadata: Arc<Metadata>,
|
||||||
|
block_number: u64,
|
||||||
|
genesis_hash: Option<HashFor<T>>,
|
||||||
|
spec_version: u32,
|
||||||
|
transaction_version: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents an offline-only client at a specific block.
|
/// This represents an offline-only client at a specific block.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait OfflineClientAtBlockT: Clone {
|
pub trait OfflineClientAtBlockT<T: Config>: Clone {
|
||||||
/// Get a reference to the metadata appropriate for this block.
|
/// Get a reference to the metadata appropriate for this block.
|
||||||
fn metadata_ref(&self) -> &Metadata;
|
fn metadata_ref(&self) -> &Metadata;
|
||||||
/// Get a clone of the metadata appropriate for this block.
|
/// Get a clone of the metadata appropriate for this block.
|
||||||
fn metadata(&self) -> Arc<Metadata>;
|
fn metadata(&self) -> Arc<Metadata>;
|
||||||
|
/// The block number we're operating at.
|
||||||
|
fn block_number(&self) -> u64;
|
||||||
|
/// Return the genesis hash for the chain if it is known.
|
||||||
|
fn genesis_hash(&self) -> Option<HashFor<T>>;
|
||||||
|
/// The spec version at the current block.
|
||||||
|
fn spec_version(&self) -> u32;
|
||||||
|
/// The transaction version at the current block.
|
||||||
|
///
|
||||||
|
/// Note: This is _not_ the same as the transaction version that
|
||||||
|
/// is encoded at the beginning of transactions (ie 4 or 5).
|
||||||
|
fn transaction_version(&self) -> u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OfflineClientAtBlockT for OfflineClientAtBlock {
|
impl<T: Config> OfflineClientAtBlockT<T> for OfflineClientAtBlock<T> {
|
||||||
fn metadata_ref(&self) -> &Metadata {
|
fn metadata_ref(&self) -> &Metadata {
|
||||||
&self.metadata
|
&self.metadata
|
||||||
}
|
}
|
||||||
fn metadata(&self) -> Arc<Metadata> {
|
fn metadata(&self) -> Arc<Metadata> {
|
||||||
self.metadata.clone()
|
self.metadata.clone()
|
||||||
}
|
}
|
||||||
|
fn block_number(&self) -> u64 {
|
||||||
|
self.block_number
|
||||||
|
}
|
||||||
|
fn genesis_hash(&self) -> Option<HashFor<T>> {
|
||||||
|
self.genesis_hash
|
||||||
|
}
|
||||||
|
fn spec_version(&self) -> u32 {
|
||||||
|
self.spec_version
|
||||||
|
}
|
||||||
|
fn transaction_version(&self) -> u32 {
|
||||||
|
self.transaction_version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ pub struct OnlineClient<T: Config> {
|
|||||||
struct OnlineClientInner<T: Config> {
|
struct OnlineClientInner<T: Config> {
|
||||||
/// The configuration for this client.
|
/// The configuration for this client.
|
||||||
config: T,
|
config: T,
|
||||||
|
/// Chain genesis hash. Needed to construct transactions,
|
||||||
|
/// so we obtain it up front on constructing this.
|
||||||
|
genesis_hash: HashFor<T>,
|
||||||
/// The RPC methods used to communicate with the node.
|
/// The RPC methods used to communicate with the node.
|
||||||
backend: Arc<dyn Backend<T>>,
|
backend: Arc<dyn Backend<T>>,
|
||||||
}
|
}
|
||||||
@@ -107,18 +110,31 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
.await
|
.await
|
||||||
.map_err(OnlineClientError::CannotBuildCombinedBackend)?;
|
.map_err(OnlineClientError::CannotBuildCombinedBackend)?;
|
||||||
let backend: Arc<dyn Backend<T>> = Arc::new(backend);
|
let backend: Arc<dyn Backend<T>> = Arc::new(backend);
|
||||||
Ok(OnlineClient::from_backend(config, backend))
|
OnlineClient::from_backend(config, backend).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new [`OnlineClient`] by providing an underlying [`Backend`]
|
/// Construct a new [`OnlineClient`] by providing an underlying [`Backend`]
|
||||||
/// implementation to power it.
|
/// implementation to power it.
|
||||||
pub fn from_backend(config: T, backend: impl Into<Arc<dyn Backend<T>>>) -> OnlineClient<T> {
|
pub async fn from_backend(
|
||||||
OnlineClient {
|
config: T,
|
||||||
|
backend: impl Into<Arc<dyn Backend<T>>>,
|
||||||
|
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||||
|
let backend = backend.into();
|
||||||
|
let genesis_hash = match config.genesis_hash() {
|
||||||
|
Some(hash) => hash,
|
||||||
|
None => backend
|
||||||
|
.genesis_hash()
|
||||||
|
.await
|
||||||
|
.map_err(OnlineClientError::CannotGetGenesisHash)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(OnlineClient {
|
||||||
inner: Arc::new(OnlineClientInner {
|
inner: Arc::new(OnlineClientInner {
|
||||||
config,
|
config,
|
||||||
|
genesis_hash,
|
||||||
backend: backend.into(),
|
backend: backend.into(),
|
||||||
}),
|
}),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a stream of all blocks imported by the node.
|
/// Obtain a stream of all blocks imported by the node.
|
||||||
@@ -212,7 +228,7 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
let number_or_hash = number_or_hash.into();
|
let number_or_hash = number_or_hash.into();
|
||||||
|
|
||||||
// We are given either a block hash or number. We need both.
|
// We are given either a block hash or number. We need both.
|
||||||
let (block_ref, block_num) = match number_or_hash {
|
let (block_ref, block_number) = match number_or_hash {
|
||||||
BlockNumberOrRef::BlockRef(block_ref) => {
|
BlockNumberOrRef::BlockRef(block_ref) => {
|
||||||
let block_hash = block_ref.hash();
|
let block_hash = block_ref.hash();
|
||||||
let block_header = self
|
let block_header = self
|
||||||
@@ -229,26 +245,29 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
})?;
|
})?;
|
||||||
(block_ref, block_header.number())
|
(block_ref, block_header.number())
|
||||||
}
|
}
|
||||||
BlockNumberOrRef::Number(block_num) => {
|
BlockNumberOrRef::Number(block_number) => {
|
||||||
let block_ref = self
|
let block_ref = self
|
||||||
.inner
|
.inner
|
||||||
.backend
|
.backend
|
||||||
.block_number_to_hash(block_num)
|
.block_number_to_hash(block_number)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| OnlineClientAtBlockError::CannotGetBlockHash {
|
.map_err(|e| OnlineClientAtBlockError::CannotGetBlockHash {
|
||||||
block_number: block_num,
|
block_number,
|
||||||
reason: e,
|
reason: e,
|
||||||
})?
|
})?
|
||||||
.ok_or(OnlineClientAtBlockError::BlockNotFound {
|
.ok_or(OnlineClientAtBlockError::BlockNotFound { block_number })?;
|
||||||
block_number: block_num,
|
(block_ref, block_number)
|
||||||
})?;
|
|
||||||
(block_ref, block_num)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let block_hash = block_ref.hash();
|
let block_hash = block_ref.hash();
|
||||||
|
|
||||||
// Obtain the spec version so that we know which metadata to use at this block.
|
// Obtain the spec version so that we know which metadata to use at this block.
|
||||||
let spec_version = match self.inner.config.spec_version_for_block_number(block_num) {
|
// Obtain the transaction version because it's required for constructing extrinsics.
|
||||||
|
let (spec_version, transaction_version) = match self
|
||||||
|
.inner
|
||||||
|
.config
|
||||||
|
.spec_and_transaction_version_for_block_number(block_number)
|
||||||
|
{
|
||||||
Some(version) => version,
|
Some(version) => version,
|
||||||
None => {
|
None => {
|
||||||
let spec_version_bytes = self
|
let spec_version_bytes = self
|
||||||
@@ -267,13 +286,18 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
_impl_name: String,
|
_impl_name: String,
|
||||||
_authoring_version: u32,
|
_authoring_version: u32,
|
||||||
spec_version: u32,
|
spec_version: u32,
|
||||||
|
_impl_version: u32,
|
||||||
|
_apis: Vec<([u8; 8], u32)>,
|
||||||
|
transaction_version: u32,
|
||||||
}
|
}
|
||||||
SpecVersionHeader::decode(&mut &spec_version_bytes[..])
|
let version =
|
||||||
.map_err(|e| OnlineClientAtBlockError::CannotDecodeSpecVersion {
|
SpecVersionHeader::decode(&mut &spec_version_bytes[..]).map_err(|e| {
|
||||||
block_hash: block_hash.into(),
|
OnlineClientAtBlockError::CannotDecodeSpecVersion {
|
||||||
reason: e,
|
block_hash: block_hash.into(),
|
||||||
})?
|
reason: e,
|
||||||
.spec_version
|
}
|
||||||
|
})?;
|
||||||
|
(version.spec_version, version.transaction_version)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -391,6 +415,10 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
metadata,
|
metadata,
|
||||||
backend: self.inner.backend.clone(),
|
backend: self.inner.backend.clone(),
|
||||||
block_ref,
|
block_ref,
|
||||||
|
block_number,
|
||||||
|
spec_version,
|
||||||
|
genesis_hash: self.inner.genesis_hash,
|
||||||
|
transaction_version,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ClientAtBlock {
|
Ok(ClientAtBlock {
|
||||||
@@ -402,7 +430,7 @@ impl<T: Config> OnlineClient<T> {
|
|||||||
|
|
||||||
/// This represents an online client at a specific block.
|
/// This represents an online client at a specific block.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait OnlineClientAtBlockT<T: Config>: OfflineClientAtBlockT {
|
pub trait OnlineClientAtBlockT<T: Config>: OfflineClientAtBlockT<T> {
|
||||||
/// Return the RPC methods we'll use to interact with the node.
|
/// Return the RPC methods we'll use to interact with the node.
|
||||||
fn backend(&self) -> &dyn Backend<T>;
|
fn backend(&self) -> &dyn Backend<T>;
|
||||||
/// Return the block hash for the current block.
|
/// Return the block hash for the current block.
|
||||||
@@ -418,6 +446,10 @@ pub struct OnlineClientAtBlock<T: Config> {
|
|||||||
backend: Arc<dyn Backend<T>>,
|
backend: Arc<dyn Backend<T>>,
|
||||||
hasher: T::Hasher,
|
hasher: T::Hasher,
|
||||||
block_ref: BlockRef<HashFor<T>>,
|
block_ref: BlockRef<HashFor<T>>,
|
||||||
|
block_number: u64,
|
||||||
|
spec_version: u32,
|
||||||
|
genesis_hash: HashFor<T>,
|
||||||
|
transaction_version: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> OnlineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
impl<T: Config> OnlineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
||||||
@@ -432,13 +464,25 @@ impl<T: Config> OnlineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> OfflineClientAtBlockT for OnlineClientAtBlock<T> {
|
impl<T: Config> OfflineClientAtBlockT<T> for OnlineClientAtBlock<T> {
|
||||||
fn metadata_ref(&self) -> &Metadata {
|
fn metadata_ref(&self) -> &Metadata {
|
||||||
&self.metadata
|
&self.metadata
|
||||||
}
|
}
|
||||||
fn metadata(&self) -> Arc<Metadata> {
|
fn metadata(&self) -> Arc<Metadata> {
|
||||||
self.metadata.clone()
|
self.metadata.clone()
|
||||||
}
|
}
|
||||||
|
fn block_number(&self) -> u64 {
|
||||||
|
self.block_number
|
||||||
|
}
|
||||||
|
fn genesis_hash(&self) -> Option<HashFor<T>> {
|
||||||
|
Some(self.genesis_hash)
|
||||||
|
}
|
||||||
|
fn spec_version(&self) -> u32 {
|
||||||
|
self.spec_version
|
||||||
|
}
|
||||||
|
fn transaction_version(&self) -> u32 {
|
||||||
|
self.transaction_version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_legacy_types<T: Config>(
|
fn get_legacy_types<T: Config>(
|
||||||
|
|||||||
+15
-3
@@ -26,7 +26,7 @@ use subxt_metadata::Metadata;
|
|||||||
use subxt_rpcs::RpcConfig;
|
use subxt_rpcs::RpcConfig;
|
||||||
|
|
||||||
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
|
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
|
||||||
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
|
pub use extrinsic_params::{ClientState, ExtrinsicParams, ExtrinsicParamsEncoder};
|
||||||
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
|
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
|
||||||
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
|
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
|
||||||
pub use transaction_extensions::TransactionExtension;
|
pub use transaction_extensions::TransactionExtension;
|
||||||
@@ -59,11 +59,23 @@ pub trait Config: Clone + Debug + Sized + Send + Sync + 'static {
|
|||||||
/// can then be used to hash things at that block.
|
/// can then be used to hash things at that block.
|
||||||
type Hasher: Hasher;
|
type Hasher: Hasher;
|
||||||
|
|
||||||
/// Return the spec version for a given block number, if available.
|
/// The starting hash for the chain we're connecting to. This is required for constructing transactions.
|
||||||
|
///
|
||||||
|
/// If not provided by the config implementation, it will be obtained from the chain in the case of the
|
||||||
|
/// [`crate::client::OnlineClient`]. It must be provided to construct transactions via the
|
||||||
|
/// [`crate::client::OfflineClient`], else an error will be returned.
|
||||||
|
fn genesis_hash(&self) -> Option<HashFor<Self>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a tuple of the spec version and then transaction version for a given block number, if available.
|
||||||
///
|
///
|
||||||
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here,
|
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here,
|
||||||
/// but the [`crate::client::OfflineClient`] will error if this is not available for the required block number.
|
/// but the [`crate::client::OfflineClient`] will error if this is not available for the required block number.
|
||||||
fn spec_version_for_block_number(&self, _block_number: u64) -> Option<u32> {
|
fn spec_and_transaction_version_for_block_number(
|
||||||
|
&self,
|
||||||
|
_block_number: u64,
|
||||||
|
) -> Option<(u32, u32)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,31 +21,14 @@ use subxt_metadata::Metadata;
|
|||||||
pub struct ClientState<T: Config> {
|
pub struct ClientState<T: Config> {
|
||||||
/// Genesis hash.
|
/// Genesis hash.
|
||||||
pub genesis_hash: HashFor<T>,
|
pub genesis_hash: HashFor<T>,
|
||||||
/// Runtime version.
|
/// Spec version.
|
||||||
pub runtime_version: RuntimeVersion,
|
pub spec_version: u32,
|
||||||
|
/// Transaction version.
|
||||||
|
pub transaction_version: u32,
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
pub metadata: Arc<Metadata>,
|
pub metadata: Arc<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runtime version information needed to submit transactions.
|
|
||||||
#[derive(Debug, Clone, Copy, 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This trait allows you to configure the "signed extra" and
|
/// This trait allows you to configure the "signed extra" and
|
||||||
/// "additional" parameters that are a part of the transaction payload
|
/// "additional" parameters that are a part of the transaction payload
|
||||||
/// or the signer payload respectively.
|
/// or the signer payload respectively.
|
||||||
|
|||||||
@@ -71,12 +71,20 @@ impl Config for PolkadotConfig {
|
|||||||
// because we need to pass the PolkadotConfig trait as a param.
|
// because we need to pass the PolkadotConfig trait as a param.
|
||||||
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
|
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
|
||||||
|
|
||||||
|
fn genesis_hash(&self) -> Option<super::HashFor<Self>> {
|
||||||
|
self.0.genesis_hash()
|
||||||
|
}
|
||||||
|
|
||||||
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> Option<TypeRegistrySet<'_>> {
|
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> Option<TypeRegistrySet<'_>> {
|
||||||
self.0.legacy_types_for_spec_version(spec_version)
|
self.0.legacy_types_for_spec_version(spec_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
|
fn spec_and_transaction_version_for_block_number(
|
||||||
self.0.spec_version_for_block_number(block_number)
|
&self,
|
||||||
|
block_number: u64,
|
||||||
|
) -> Option<(u32, u32)> {
|
||||||
|
self.0
|
||||||
|
.spec_and_transaction_version_for_block_number(block_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata_for_spec_version(&self, spec_version: u32) -> Option<Arc<Metadata>> {
|
fn metadata_for_spec_version(&self, spec_version: u32) -> Option<Arc<Metadata>> {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ use subxt_metadata::Metadata;
|
|||||||
/// Construct a [`SubstrateConfig`] using this.
|
/// Construct a [`SubstrateConfig`] using this.
|
||||||
pub struct SubstrateConfigBuilder {
|
pub struct SubstrateConfigBuilder {
|
||||||
legacy_types: Option<ChainTypeRegistry>,
|
legacy_types: Option<ChainTypeRegistry>,
|
||||||
spec_version_for_block_number: RangeMap<u64, u32>,
|
spec_and_transaction_version_for_block_number: RangeMap<u64, (u32, u32)>,
|
||||||
|
genesis_hash: Option<H256>,
|
||||||
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
|
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
|
||||||
use_old_v9_hashers_before_spec_version: u32,
|
use_old_v9_hashers_before_spec_version: u32,
|
||||||
}
|
}
|
||||||
@@ -36,12 +37,19 @@ impl SubstrateConfigBuilder {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
SubstrateConfigBuilder {
|
SubstrateConfigBuilder {
|
||||||
legacy_types: None,
|
legacy_types: None,
|
||||||
spec_version_for_block_number: RangeMap::empty(),
|
genesis_hash: None,
|
||||||
|
spec_and_transaction_version_for_block_number: RangeMap::empty(),
|
||||||
metadata_for_spec_version: Mutex::new(HashMap::new()),
|
metadata_for_spec_version: Mutex::new(HashMap::new()),
|
||||||
use_old_v9_hashers_before_spec_version: 0,
|
use_old_v9_hashers_before_spec_version: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the genesis hash for this chain.
|
||||||
|
pub fn set_genesis_hash(mut self, genesis_hash: H256) -> Self {
|
||||||
|
self.genesis_hash = Some(genesis_hash);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the legacy types to use for this configuration. This enables support for
|
/// Set the legacy types to use for this configuration. This enables support for
|
||||||
/// blocks produced by Runtimes that emit metadata older than V14.
|
/// blocks produced by Runtimes that emit metadata older than V14.
|
||||||
pub fn set_legacy_types(mut self, legacy_types: ChainTypeRegistry) -> Self {
|
pub fn set_legacy_types(mut self, legacy_types: ChainTypeRegistry) -> Self {
|
||||||
@@ -73,9 +81,10 @@ impl SubstrateConfigBuilder {
|
|||||||
let start = version_for_range.block_range.start;
|
let start = version_for_range.block_range.start;
|
||||||
let end = version_for_range.block_range.end;
|
let end = version_for_range.block_range.end;
|
||||||
let spec_version = version_for_range.spec_version;
|
let spec_version = version_for_range.spec_version;
|
||||||
m = m.add_range(start, end, spec_version);
|
let transaction_version = version_for_range.transaction_version;
|
||||||
|
m = m.add_range(start, end, (spec_version, transaction_version));
|
||||||
}
|
}
|
||||||
self.spec_version_for_block_number = m.build();
|
self.spec_and_transaction_version_for_block_number = m.build();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +100,8 @@ impl SubstrateConfigBuilder {
|
|||||||
SubstrateConfig {
|
SubstrateConfig {
|
||||||
inner: Arc::new(SubstrateConfigInner {
|
inner: Arc::new(SubstrateConfigInner {
|
||||||
legacy_types: self.legacy_types,
|
legacy_types: self.legacy_types,
|
||||||
spec_version_for_block_number: self.spec_version_for_block_number,
|
spec_and_transaction_version_for_block_number: self
|
||||||
|
.spec_and_transaction_version_for_block_number,
|
||||||
metadata_for_spec_version: self.metadata_for_spec_version,
|
metadata_for_spec_version: self.metadata_for_spec_version,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -107,6 +117,8 @@ pub struct SpecVersionForRange {
|
|||||||
pub block_range: std::ops::Range<u64>,
|
pub block_range: std::ops::Range<u64>,
|
||||||
/// The spec version at this block range.
|
/// The spec version at this block range.
|
||||||
pub spec_version: u32,
|
pub spec_version: u32,
|
||||||
|
/// The transaction version at this block range.
|
||||||
|
pub transaction_version: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration that's suitable for standard Substrate chains (ie those
|
/// Configuration that's suitable for standard Substrate chains (ie those
|
||||||
@@ -119,7 +131,7 @@ pub struct SubstrateConfig {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SubstrateConfigInner {
|
struct SubstrateConfigInner {
|
||||||
legacy_types: Option<ChainTypeRegistry>,
|
legacy_types: Option<ChainTypeRegistry>,
|
||||||
spec_version_for_block_number: RangeMap<u64, u32>,
|
spec_and_transaction_version_for_block_number: RangeMap<u64, (u32, u32)>,
|
||||||
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
|
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,9 +158,12 @@ impl Config for SubstrateConfig {
|
|||||||
.map(|types| types.for_spec_version(spec_version as u64))
|
.map(|types| types.for_spec_version(spec_version as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
|
fn spec_and_transaction_version_for_block_number(
|
||||||
|
&self,
|
||||||
|
block_number: u64,
|
||||||
|
) -> Option<(u32, u32)> {
|
||||||
self.inner
|
self.inner
|
||||||
.spec_version_for_block_number
|
.spec_and_transaction_version_for_block_number
|
||||||
.get(block_number)
|
.get(block_number)
|
||||||
.copied()
|
.copied()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
|
|||||||
type Params = ();
|
type Params = ();
|
||||||
|
|
||||||
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
||||||
Ok(CheckSpecVersion(client.runtime_version.spec_version))
|
Ok(CheckSpecVersion(client.spec_version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
|
|||||||
type Params = ();
|
type Params = ();
|
||||||
|
|
||||||
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
||||||
Ok(CheckTxVersion(client.runtime_version.transaction_version))
|
Ok(CheckTxVersion(client.transaction_version))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub mod backend;
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod transactions;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
// pub mod book;
|
// pub mod book;
|
||||||
// pub mod blocks;
|
// pub mod blocks;
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
mod payload;
|
||||||
|
mod signer;
|
||||||
|
|
||||||
|
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||||
|
use crate::config::{ClientState, Config};
|
||||||
|
use crate::error::ExtrinsicError;
|
||||||
|
use codec::Compact;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
pub use payload::Payload;
|
||||||
|
pub use signer::Signer;
|
||||||
|
|
||||||
|
/// A client for working with transactions.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Transactions<'atblock, T, Client> {
|
||||||
|
client: &'atblock Client,
|
||||||
|
marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'atblock, T, Client> Transactions<'atblock, T, Client> {
|
||||||
|
pub(crate) fn new(client: &'atblock Client) -> Self {
|
||||||
|
Transactions {
|
||||||
|
client,
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'atblock, T: Config, C: OfflineClientAtBlockT<T>> Transactions<'atblock, T, C> {
|
||||||
|
/// Run the validation logic against some transaction you'd like to submit. Returns `Ok(())`
|
||||||
|
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
|
||||||
|
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
|
||||||
|
/// the pallet or call in question do not exist at all).
|
||||||
|
pub fn validate<Call>(&self, call: &Call) -> Result<(), ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let Some(details) = call.validation_details() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let pallet_name = details.pallet_name;
|
||||||
|
let call_name = details.call_name;
|
||||||
|
|
||||||
|
let expected_hash = self
|
||||||
|
.client
|
||||||
|
.metadata_ref()
|
||||||
|
.pallet_by_name(pallet_name)
|
||||||
|
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(pallet_name.to_string()))?
|
||||||
|
.call_hash(call_name)
|
||||||
|
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
|
||||||
|
pallet_name: pallet_name.to_string(),
|
||||||
|
call_name: call_name.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if details.hash != expected_hash {
|
||||||
|
Err(ExtrinsicError::IncompatibleCodegen)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the SCALE encoded bytes representing the call data of the transaction.
|
||||||
|
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let metadata = self.client.metadata_ref();
|
||||||
|
call.encode_call_data_to(metadata, &mut bytes)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an unsigned transaction without submitting it. Depending on the metadata, we might end
|
||||||
|
/// up constructing either a v4 or v5 transaction. See [`Self::create_v4_unsigned`] or
|
||||||
|
/// [`Self::create_v5_bare`] if you'd like to explicitly create an unsigned transaction of a certain version.
|
||||||
|
pub fn create_unsigned<Call>(
|
||||||
|
&self,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let tx = match self.default_transaction_version()? {
|
||||||
|
TransactionVersion::V4 => self.create_v4_unsigned(call),
|
||||||
|
TransactionVersion::V5 => self.create_v5_bare(call),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(SubmittableTransaction {
|
||||||
|
client: self.client.clone(),
|
||||||
|
inner: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a V4 "unsigned" transaction without submitting it.
|
||||||
|
pub fn create_v4_unsigned<Call>(&self, call: &Call) -> Result<Transaction<T>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
self.create_unsigned_at_version(call, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a V5 "bare" transaction without submitting it.
|
||||||
|
pub fn create_v5_bare<Call>(&self, call: &Call) -> Result<Transaction<T>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
self.create_unsigned_at_version(call, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a partial transaction. Depending on the metadata, we might end up constructing either a v4 or
|
||||||
|
/// v5 transaction. See [`subxt_core::tx`] if you'd like to manually pick the version to construct
|
||||||
|
///
|
||||||
|
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
|
||||||
|
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
|
||||||
|
pub fn create_partial_offline<Call>(
|
||||||
|
&self,
|
||||||
|
call: &Call,
|
||||||
|
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||||
|
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let metadata = self.client.metadata();
|
||||||
|
let client_state = ClientState {
|
||||||
|
genesis_hash: self.client.genesis_hash(),
|
||||||
|
spec_version: self.client.spec_version(),
|
||||||
|
transaction_version: self.client.transaction_version(),
|
||||||
|
metadata: self.client.metadata(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx = match self.default_transaction_version(&metadata)? {
|
||||||
|
TransactionVersion::V4 => {
|
||||||
|
PartialTransactionInner::V4(self.create_v4_signed(call, &client_state, params)?)
|
||||||
|
}
|
||||||
|
TransactionVersion::V5 => {
|
||||||
|
PartialTransactionInner::V5(self.create_v5_general(call, &client_state, params)?)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PartialTransaction {
|
||||||
|
client: self.client.clone(),
|
||||||
|
inner: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a v4 partial transaction, ready to sign.
|
||||||
|
///
|
||||||
|
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
|
||||||
|
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
|
||||||
|
///
|
||||||
|
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
|
||||||
|
/// most suitable one for the given chain.
|
||||||
|
pub fn create_v4_partial_offline<Call>(
|
||||||
|
&self,
|
||||||
|
call: &Call,
|
||||||
|
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||||
|
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let tx = PartialTransactionInner::V4(subxt_core::tx::create_v4_signed(
|
||||||
|
call,
|
||||||
|
&self.client.client_state(),
|
||||||
|
params,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
Ok(PartialTransaction {
|
||||||
|
client: self.client.clone(),
|
||||||
|
inner: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a v5 partial transaction, ready to sign.
|
||||||
|
///
|
||||||
|
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
|
||||||
|
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
|
||||||
|
///
|
||||||
|
/// Prefer [`Self::create_partial_offline()`] if you don't know which version to create; this will pick the
|
||||||
|
/// most suitable one for the given chain.
|
||||||
|
pub fn create_v5_partial_offline<Call>(
|
||||||
|
&self,
|
||||||
|
call: &Call,
|
||||||
|
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||||
|
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||||
|
where
|
||||||
|
Call: Payload,
|
||||||
|
{
|
||||||
|
let tx = PartialTransactionInner::V5(subxt_core::tx::create_v5_general(
|
||||||
|
call,
|
||||||
|
&self.client.client_state(),
|
||||||
|
params,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
Ok(PartialTransaction {
|
||||||
|
client: self.client.clone(),
|
||||||
|
inner: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a V4 "unsigned" transaction or V5 "bare" transaction.
|
||||||
|
fn create_unsigned_at_version<Call: Payload>(
|
||||||
|
&self,
|
||||||
|
call: &Call,
|
||||||
|
tx_version: u8,
|
||||||
|
) -> Result<Transaction<T>, ExtrinsicError> {
|
||||||
|
let metadata = self.client.metadata_ref();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// encode the transaction version first.
|
||||||
|
tx_version.encode_to(&mut encoded_inner);
|
||||||
|
// encode call data after this byte.
|
||||||
|
call.encode_call_data_to(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(Transaction::from_bytes(extrinsic))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the suggested transaction versions to build for a given chain, or an error
|
||||||
|
/// if Subxt doesn't support any version expected by the chain.
|
||||||
|
///
|
||||||
|
/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
|
||||||
|
/// [`TransactionVersion::V5`], use the `v5` ones.
|
||||||
|
pub fn default_transaction_version(&self) -> Result<TransactionVersion, ExtrinsicError> {
|
||||||
|
let metadata = self.client.metadata_ref();
|
||||||
|
let versions = metadata.extrinsic().supported_versions();
|
||||||
|
|
||||||
|
if versions.contains(&4) {
|
||||||
|
Ok(TransactionVersion::V4)
|
||||||
|
} else if versions.contains(&5) {
|
||||||
|
Ok(TransactionVersion::V5)
|
||||||
|
} else {
|
||||||
|
Err(ExtrinsicError::UnsupportedVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The transaction versions supported by Subxt.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum TransactionVersion {
|
||||||
|
/// v4 transactions (signed and unsigned transactions)
|
||||||
|
V4,
|
||||||
|
/// v5 transactions (bare and general transactions)
|
||||||
|
V5,
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
// Copyright 2019-2024 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 the trait and types used to represent
|
||||||
|
//! transactions that can be submitted.
|
||||||
|
|
||||||
|
use crate::error::ExtrinsicError;
|
||||||
|
use codec::Encode;
|
||||||
|
use scale_encode::EncodeAsFields;
|
||||||
|
use scale_value::{Composite, Value, ValueDef, Variant};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use subxt_metadata::Metadata;
|
||||||
|
|
||||||
|
/// This represents a transaction payload that can be submitted
|
||||||
|
/// to a node.
|
||||||
|
pub trait Payload {
|
||||||
|
/// Encode call data to the provided output.
|
||||||
|
fn encode_call_data_to(
|
||||||
|
&self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
out: &mut Vec<u8>,
|
||||||
|
) -> Result<(), ExtrinsicError>;
|
||||||
|
|
||||||
|
/// Encode call data and return the output. This is a convenience
|
||||||
|
/// wrapper around [`Payload::encode_call_data_to`].
|
||||||
|
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
self.encode_call_data_to(metadata, &mut v)?;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the details needed to validate the call, which
|
||||||
|
/// include a statically generated hash, the pallet name,
|
||||||
|
/// and the call name.
|
||||||
|
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! boxed_payload {
|
||||||
|
($ty:path) => {
|
||||||
|
impl<T: Payload + ?Sized> Payload for $ty {
|
||||||
|
fn encode_call_data_to(
|
||||||
|
&self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
out: &mut Vec<u8>,
|
||||||
|
) -> Result<(), ExtrinsicError> {
|
||||||
|
self.as_ref().encode_call_data_to(metadata, out)
|
||||||
|
}
|
||||||
|
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
|
||||||
|
self.as_ref().encode_call_data(metadata)
|
||||||
|
}
|
||||||
|
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||||
|
self.as_ref().validation_details()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
boxed_payload!(Box<T>);
|
||||||
|
boxed_payload!(std::sync::Arc<T>);
|
||||||
|
boxed_payload!(std::rc::Rc<T>);
|
||||||
|
|
||||||
|
/// Details required to validate the shape of a transaction payload against some metadata.
|
||||||
|
pub struct ValidationDetails<'a> {
|
||||||
|
/// The pallet name.
|
||||||
|
pub pallet_name: &'a str,
|
||||||
|
/// The call name.
|
||||||
|
pub call_name: &'a str,
|
||||||
|
/// A hash (this is generated at compile time in our codegen)
|
||||||
|
/// to compare against the runtime code.
|
||||||
|
pub hash: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transaction payload containing some generic `CallData`.
|
||||||
|
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct DefaultPayload<CallData> {
|
||||||
|
pallet_name: Cow<'static, str>,
|
||||||
|
call_name: Cow<'static, str>,
|
||||||
|
call_data: CallData,
|
||||||
|
validation_hash: Option<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The payload type used by static codegen.
|
||||||
|
pub type StaticPayload<Calldata> = DefaultPayload<Calldata>;
|
||||||
|
/// The type of a payload typically used for dynamic transaction payloads.
|
||||||
|
pub type DynamicPayload = DefaultPayload<Composite<()>>;
|
||||||
|
|
||||||
|
impl<CallData> DefaultPayload<CallData> {
|
||||||
|
/// Create a new [`DefaultPayload`].
|
||||||
|
pub fn new(
|
||||||
|
pallet_name: impl Into<String>,
|
||||||
|
call_name: impl Into<String>,
|
||||||
|
call_data: CallData,
|
||||||
|
) -> Self {
|
||||||
|
DefaultPayload {
|
||||||
|
pallet_name: Cow::Owned(pallet_name.into()),
|
||||||
|
call_name: Cow::Owned(call_name.into()),
|
||||||
|
call_data,
|
||||||
|
validation_hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`DefaultPayload`] 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,
|
||||||
|
call_name: &'static str,
|
||||||
|
call_data: CallData,
|
||||||
|
validation_hash: [u8; 32],
|
||||||
|
) -> Self {
|
||||||
|
DefaultPayload {
|
||||||
|
pallet_name: Cow::Borrowed(pallet_name),
|
||||||
|
call_name: Cow::Borrowed(call_name),
|
||||||
|
call_data,
|
||||||
|
validation_hash: Some(validation_hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do not validate this call prior to submitting it.
|
||||||
|
pub fn unvalidated(self) -> Self {
|
||||||
|
Self {
|
||||||
|
validation_hash: None,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the call data.
|
||||||
|
pub fn call_data(&self) -> &CallData {
|
||||||
|
&self.call_data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the pallet name.
|
||||||
|
pub fn pallet_name(&self) -> &str {
|
||||||
|
&self.pallet_name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the call name.
|
||||||
|
pub fn call_name(&self) -> &str {
|
||||||
|
&self.call_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultPayload<Composite<()>> {
|
||||||
|
/// Convert the dynamic `Composite` payload into a [`Value`].
|
||||||
|
/// This is useful if you want to use this as an argument for a
|
||||||
|
/// larger dynamic call that wants to use this as a nested call.
|
||||||
|
pub fn into_value(self) -> Value<()> {
|
||||||
|
let call = Value {
|
||||||
|
context: (),
|
||||||
|
value: ValueDef::Variant(Variant {
|
||||||
|
name: self.call_name.into_owned(),
|
||||||
|
values: self.call_data,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Value::unnamed_variant(self.pallet_name, [call])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
|
||||||
|
fn encode_call_data_to(
|
||||||
|
&self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
out: &mut Vec<u8>,
|
||||||
|
) -> Result<(), ExtrinsicError> {
|
||||||
|
let pallet = metadata
|
||||||
|
.pallet_by_name(&self.pallet_name)
|
||||||
|
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(self.pallet_name.to_string()))?;
|
||||||
|
let call = pallet
|
||||||
|
.call_variant_by_name(&self.call_name)
|
||||||
|
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
|
||||||
|
pallet_name: pallet.name().to_string(),
|
||||||
|
call_name: self.call_name.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pallet_index = pallet.call_index();
|
||||||
|
let call_index = call.index;
|
||||||
|
|
||||||
|
pallet_index.encode_to(out);
|
||||||
|
call_index.encode_to(out);
|
||||||
|
|
||||||
|
let mut fields = call
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
|
||||||
|
|
||||||
|
self.call_data
|
||||||
|
.encode_as_fields_to(&mut fields, metadata.types(), out)
|
||||||
|
.map_err(ExtrinsicError::CannotEncodeCallData)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||||
|
self.validation_hash.map(|hash| ValidationDetails {
|
||||||
|
pallet_name: &self.pallet_name,
|
||||||
|
call_name: &self.call_name,
|
||||||
|
hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a transaction at runtime; essentially an alias to [`DefaultPayload::new()`]
|
||||||
|
/// which provides a [`Composite`] value for the call data.
|
||||||
|
pub fn dynamic(
|
||||||
|
pallet_name: impl Into<String>,
|
||||||
|
call_name: impl Into<String>,
|
||||||
|
call_data: impl Into<Composite<()>>,
|
||||||
|
) -> DynamicPayload {
|
||||||
|
DefaultPayload::new(pallet_name, call_name, call_data.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use codec::Decode;
|
||||||
|
use scale_value::Composite;
|
||||||
|
|
||||||
|
fn test_metadata() -> Metadata {
|
||||||
|
let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||||
|
Metadata::decode(&mut &metadata_bytes[..]).expect("Valid metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_call_with_incompatible_types_returns_error() {
|
||||||
|
let metadata = test_metadata();
|
||||||
|
|
||||||
|
let incompatible_data = Composite::named([
|
||||||
|
("dest", scale_value::Value::bool(true)), // Boolean instead of MultiAddress
|
||||||
|
("value", scale_value::Value::string("not_a_number")), // String instead of u128
|
||||||
|
]);
|
||||||
|
|
||||||
|
let payload = DefaultPayload::new("Balances", "transfer_allow_death", incompatible_data);
|
||||||
|
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let result = payload.encode_call_data_to(&metadata, &mut out);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Expected error when encoding with incompatible types"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_call_with_valid_data_succeeds() {
|
||||||
|
let metadata = test_metadata();
|
||||||
|
|
||||||
|
// Create a valid payload to ensure our error handling doesn't break valid cases
|
||||||
|
// For MultiAddress, we'll use the Id variant with a 32-byte account
|
||||||
|
let valid_address =
|
||||||
|
scale_value::Value::unnamed_variant("Id", [scale_value::Value::from_bytes([0u8; 32])]);
|
||||||
|
|
||||||
|
let valid_data = Composite::named([
|
||||||
|
("dest", valid_address),
|
||||||
|
("value", scale_value::Value::u128(1000)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let payload = DefaultPayload::new("Balances", "transfer_allow_death", valid_data);
|
||||||
|
|
||||||
|
// This should succeed
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let result = payload.encode_call_data_to(&metadata, &mut out);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Expected success when encoding with valid data"
|
||||||
|
);
|
||||||
|
assert!(!out.is_empty(), "Expected encoded output to be non-empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
|
/// Signing transactions requires a [`Signer`]. This is responsible for
|
||||||
|
/// providing the "from" account that the transaction is being signed by,
|
||||||
|
/// as well as actually signing a SCALE encoded payload.
|
||||||
|
pub trait Signer<T: Config> {
|
||||||
|
/// Return the "from" account ID.
|
||||||
|
fn account_id(&self) -> T::AccountId;
|
||||||
|
|
||||||
|
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
|
||||||
|
///
|
||||||
|
/// Some signers may fail, for instance because the hardware on which the keys are located has
|
||||||
|
/// refused the operation.
|
||||||
|
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user