mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 18:11:10 +00:00
Merge remote-tracking branch 'origin/master' into lexnv/update-smoldot
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
@@ -22,18 +22,56 @@ use std::task::{Context, Poll};
|
||||
// Expose the RPC methods.
|
||||
pub use rpc_methods::LegacyRpcMethods;
|
||||
|
||||
/// Configure and build an [`LegacyBackend`].
|
||||
pub struct LegacyBackendBuilder<T> {
|
||||
storage_page_size: u32,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for LegacyBackendBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> LegacyBackendBuilder<T> {
|
||||
/// Create a new [`LegacyBackendBuilder`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
storage_page_size: 64,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterating over storage entries using the [`LegacyBackend`] requires
|
||||
/// fetching entries in batches. This configures the number of entries that
|
||||
/// we'll try to obtain in each batch (default: 64).
|
||||
pub fn storage_page_size(mut self, storage_page_size: u32) -> Self {
|
||||
self.storage_page_size = storage_page_size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Given an [`RpcClient`] to use to make requests, this returns a [`LegacyBackend`],
|
||||
/// which implements the [`Backend`] trait.
|
||||
pub fn build(self, client: impl Into<RpcClient>) -> LegacyBackend<T> {
|
||||
LegacyBackend {
|
||||
storage_page_size: self.storage_page_size,
|
||||
methods: LegacyRpcMethods::new(client.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The legacy backend.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LegacyBackend<T> {
|
||||
storage_page_size: u32,
|
||||
methods: LegacyRpcMethods<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> LegacyBackend<T> {
|
||||
/// Instantiate a new backend which uses the legacy API methods.
|
||||
pub fn new(client: RpcClient) -> Self {
|
||||
Self {
|
||||
methods: LegacyRpcMethods::new(client),
|
||||
}
|
||||
/// Configure and construct an [`LegacyBackend`].
|
||||
pub fn builder() -> LegacyBackendBuilder<T> {
|
||||
LegacyBackendBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +112,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
let keys = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
storage_page_size: self.storage_page_size,
|
||||
methods: self.methods.clone(),
|
||||
done: Default::default(),
|
||||
keys_fut: Default::default(),
|
||||
@@ -104,6 +143,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
let keys_stream = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
storage_page_size: self.storage_page_size,
|
||||
methods: self.methods.clone(),
|
||||
done: Default::default(),
|
||||
keys_fut: Default::default(),
|
||||
@@ -141,20 +181,16 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
let details = self.methods.state_get_runtime_version(None).await?;
|
||||
Ok(RuntimeVersion {
|
||||
spec_version: details.spec_version,
|
||||
transaction_version: details.transaction_version,
|
||||
})
|
||||
Ok(RuntimeVersion::new(
|
||||
details.spec_version,
|
||||
details.transaction_version,
|
||||
))
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
let sub = self.methods.state_subscribe_runtime_version().await?;
|
||||
let sub = sub.map(|r| {
|
||||
r.map(|v| RuntimeVersion {
|
||||
spec_version: v.spec_version,
|
||||
transaction_version: v.transaction_version,
|
||||
})
|
||||
});
|
||||
let sub =
|
||||
sub.map(|r| r.map(|v| RuntimeVersion::new(v.spec_version, v.transaction_version)));
|
||||
Ok(StreamOf(Box::pin(sub)))
|
||||
}
|
||||
|
||||
@@ -332,9 +368,6 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// How many keys/values to fetch at once.
|
||||
const STORAGE_PAGE_SIZE: u32 = 32;
|
||||
|
||||
/// This provides a stream of values given some prefix `key`. It
|
||||
/// internally manages pagination and such.
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -342,6 +375,8 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
|
||||
methods: LegacyRpcMethods<T>,
|
||||
key: Vec<u8>,
|
||||
at: T::Hash,
|
||||
// How many entries to ask for each time.
|
||||
storage_page_size: u32,
|
||||
// What key do we start paginating from? None = from the beginning.
|
||||
pagination_start_key: Option<Vec<u8>>,
|
||||
// Keys, future and cached:
|
||||
@@ -392,12 +427,13 @@ impl<T: Config> Stream for StorageFetchDescendantKeysStream<T> {
|
||||
let methods = this.methods.clone();
|
||||
let key = this.key.clone();
|
||||
let at = this.at;
|
||||
let storage_page_size = this.storage_page_size;
|
||||
let pagination_start_key = this.pagination_start_key.take();
|
||||
let keys_fut = async move {
|
||||
methods
|
||||
.state_get_keys_paged(
|
||||
&key,
|
||||
STORAGE_PAGE_SIZE,
|
||||
storage_page_size,
|
||||
pagination_start_key.as_deref(),
|
||||
Some(at),
|
||||
)
|
||||
|
||||
@@ -8,15 +8,14 @@ use crate::backend::rpc::{rpc_params, RpcClient, RpcSubscription};
|
||||
use crate::metadata::Metadata;
|
||||
use crate::{Config, Error};
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use primitive_types::U256;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An interface to call the legacy RPC methods. This interface is instantiated with
|
||||
/// some `T: Config` trait which determines some of the types that the RPC methods will
|
||||
/// take or hand back.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""), Debug(bound = ""))]
|
||||
#[derive_where(Clone, Debug)]
|
||||
pub struct LegacyRpcMethods<T> {
|
||||
client: RpcClient,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
@@ -36,7 +35,7 @@ impl<T: Config> LegacyRpcMethods<T> {
|
||||
&self,
|
||||
key: &[u8],
|
||||
hash: Option<T::Hash>,
|
||||
) -> Result<Option<StorageKey>, Error> {
|
||||
) -> Result<Option<StorageData>, Error> {
|
||||
let params = rpc_params![to_hex(key), hash];
|
||||
let data: Option<Bytes> = self.client.request("state_getStorage", params).await?;
|
||||
Ok(data.map(|b| b.0))
|
||||
@@ -51,7 +50,7 @@ impl<T: Config> LegacyRpcMethods<T> {
|
||||
count: u32,
|
||||
start_key: Option<&[u8]>,
|
||||
at: Option<T::Hash>,
|
||||
) -> Result<Vec<StorageData>, Error> {
|
||||
) -> Result<Vec<StorageKey>, Error> {
|
||||
let start_key = start_key.map(to_hex);
|
||||
let params = rpc_params![to_hex(key), count, start_key, at];
|
||||
let data: Vec<Bytes> = self.client.request("state_getKeysPaged", params).await?;
|
||||
@@ -535,6 +534,13 @@ impl DryRunResultBytes {
|
||||
// dryRun returns an ApplyExtrinsicResult, which is basically a
|
||||
// `Result<Result<(), DispatchError>, TransactionValidityError>`.
|
||||
let bytes = self.0;
|
||||
|
||||
// We expect at least 2 bytes. In case we got a naff response back (or
|
||||
// manually constructed this struct), just error to avoid a panic:
|
||||
if bytes.len() < 2 {
|
||||
return Err(crate::Error::Unknown(bytes));
|
||||
}
|
||||
|
||||
if bytes[0] == 0 && bytes[1] == 0 {
|
||||
// Ok(Ok(())); transaction is valid and executed ok
|
||||
Ok(DryRunResult::Success)
|
||||
|
||||
@@ -10,6 +10,8 @@ pub mod legacy;
|
||||
pub mod rpc;
|
||||
pub mod unstable;
|
||||
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::Config;
|
||||
@@ -184,6 +186,7 @@ impl<H: PartialEq> PartialEq for BlockRef<H> {
|
||||
}
|
||||
impl<H: Eq> Eq for BlockRef<H> {}
|
||||
|
||||
// Manual implementation to work around https://github.com/mcarton/rust-derivative/issues/115.
|
||||
impl<H: PartialOrd> PartialOrd for BlockRef<H> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.hash.partial_cmp(&other.hash)
|
||||
@@ -276,26 +279,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`],
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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::{RawRpcFuture, RawRpcSubscription, RpcClientT};
|
||||
use crate::error::RpcError;
|
||||
use futures::stream::{StreamExt, TryStreamExt};
|
||||
use serde_json::value::RawValue;
|
||||
use subxt_lightclient::{LightClientRpc, LightClientRpcError};
|
||||
|
||||
impl RpcClientT for LightClientRpc {
|
||||
fn request_raw<'a>(
|
||||
&'a self,
|
||||
method: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
) -> RawRpcFuture<'a, Box<RawValue>> {
|
||||
Box::pin(async move {
|
||||
let res = self.request(method.to_owned(), params)
|
||||
.await
|
||||
.map_err(lc_err_to_rpc_err)?;
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
|
||||
fn subscribe_raw<'a>(
|
||||
&'a self,
|
||||
sub: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
unsub: &'a str,
|
||||
) -> RawRpcFuture<'a, RawRpcSubscription> {
|
||||
Box::pin(async move {
|
||||
let sub = self.subscribe(sub.to_owned(), params, unsub.to_owned())
|
||||
.await
|
||||
.map_err(lc_err_to_rpc_err)?;
|
||||
|
||||
let id = Some(sub.id().to_owned());
|
||||
let stream = sub
|
||||
.map_err(|e| RpcError::ClientError(Box::new(e)))
|
||||
.boxed();
|
||||
|
||||
Ok(RawRpcSubscription { id, stream })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn lc_err_to_rpc_err(err: LightClientRpcError) -> RpcError {
|
||||
match err {
|
||||
LightClientRpcError::JsonRpcError(e) => RpcError::ClientError(Box::new(e)),
|
||||
LightClientRpcError::SmoldotError(e) => RpcError::RequestRejected(e),
|
||||
LightClientRpcError::BackgroundTaskDropped => RpcError::SubscriptionDropped,
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,17 @@ crate::macros::cfg_jsonrpsee! {
|
||||
mod jsonrpsee_impl;
|
||||
}
|
||||
|
||||
crate::macros::cfg_unstable_light_client! {
|
||||
mod lightclient_impl;
|
||||
}
|
||||
|
||||
crate::macros::cfg_reconnecting_rpc_client! {
|
||||
mod reconnecting_jsonrpsee_impl;
|
||||
pub use reconnecting_jsonrpsee_ws_client as reconnecting_rpc_client;
|
||||
}
|
||||
|
||||
mod rpc_client;
|
||||
mod rpc_client_t;
|
||||
|
||||
pub use rpc_client_t::{RawRpcFuture, RawRpcSubscription, RawValue, RpcClientT};
|
||||
|
||||
pub use rpc_client::{rpc_params, RpcClient, RpcParams, RpcSubscription};
|
||||
pub use rpc_client_t::{RawRpcFuture, RawRpcSubscription, RawValue, RpcClientT};
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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::{RawRpcFuture, RawRpcSubscription, RpcClientT};
|
||||
use crate::error::RpcError;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use reconnecting_jsonrpsee_ws_client::SubscriptionId;
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
impl RpcClientT for reconnecting_jsonrpsee_ws_client::Client {
|
||||
fn request_raw<'a>(
|
||||
&'a self,
|
||||
method: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
) -> RawRpcFuture<'a, Box<RawValue>> {
|
||||
async {
|
||||
self.request_raw(method.to_string(), params)
|
||||
.await
|
||||
.map_err(|e| RpcError::ClientError(Box::new(e)))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn subscribe_raw<'a>(
|
||||
&'a self,
|
||||
sub: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
unsub: &'a str,
|
||||
) -> RawRpcFuture<'a, RawRpcSubscription> {
|
||||
async {
|
||||
let sub = self
|
||||
.subscribe_raw(sub.to_string(), params, unsub.to_string())
|
||||
.await
|
||||
.map_err(|e| RpcError::ClientError(Box::new(e)))?;
|
||||
|
||||
let id = match sub.id() {
|
||||
SubscriptionId::Num(n) => n.to_string(),
|
||||
SubscriptionId::Str(s) => s.to_string(),
|
||||
};
|
||||
let stream = sub
|
||||
.map_err(|e| RpcError::DisconnectedWillReconnect(e.to_string()))
|
||||
.boxed();
|
||||
|
||||
Ok(RawRpcSubscription {
|
||||
stream,
|
||||
id: Some(id),
|
||||
})
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,12 @@ impl RpcClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: RpcClientT> From<C> for RpcClient {
|
||||
fn from(client: C) -> Self {
|
||||
RpcClient::new(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RpcClient {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("RpcClient").finish()
|
||||
|
||||
@@ -243,7 +243,7 @@ pub(super) mod test_utils {
|
||||
/// An initialized event
|
||||
pub fn ev_initialized(n: u64) -> FollowEvent<H256> {
|
||||
FollowEvent::Initialized(Initialized {
|
||||
finalized_block_hash: H256::from_low_u64_le(n),
|
||||
finalized_block_hashes: vec![H256::from_low_u64_le(n)],
|
||||
finalized_block_runtime: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -267,9 +267,9 @@ impl<Hash: BlockHash> Shared<Hash> {
|
||||
|
||||
shared.seen_runtime_events.clear();
|
||||
|
||||
if let Some(finalized) = finalized_ev.finalized_block_hashes.last() {
|
||||
init_message.finalized_block_hash = finalized.clone();
|
||||
}
|
||||
init_message.finalized_block_hashes =
|
||||
finalized_ev.finalized_block_hashes.clone();
|
||||
|
||||
if let Some(runtime_ev) = newest_runtime {
|
||||
init_message.finalized_block_runtime = Some(runtime_ev);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::backend::unstable::rpc_methods::{
|
||||
use crate::config::{BlockHash, Config};
|
||||
use crate::error::Error;
|
||||
use futures::stream::{FuturesUnordered, Stream, StreamExt};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@@ -34,9 +35,11 @@ pub struct FollowStreamUnpin<Hash: BlockHash> {
|
||||
// Futures for sending unpin events that we'll poll to completion as
|
||||
// part of polling the stream as a whole.
|
||||
unpin_futs: FuturesUnordered<UnpinFut>,
|
||||
// Each new finalized block increments this. Allows us to track
|
||||
// the age of blocks so that we can unpin old ones.
|
||||
rel_block_num: usize,
|
||||
// Each time a new finalized block is seen, we give it an age of `next_rel_block_age`,
|
||||
// and then increment this ready for the next finalized block. So, the first finalized
|
||||
// block will have an age of 0, the next 1, 2, 3 and so on. We can then use `max_block_life`
|
||||
// to say "unpin all blocks with an age < (next_rel_block_age-1) - max_block_life".
|
||||
next_rel_block_age: usize,
|
||||
// The latest ID of the FollowStream subscription, which we can use
|
||||
// to unpin blocks.
|
||||
subscription_id: Option<Arc<str>>,
|
||||
@@ -113,15 +116,23 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
FollowStreamMsg::Ready(subscription_id)
|
||||
}
|
||||
FollowStreamMsg::Event(FollowEvent::Initialized(details)) => {
|
||||
// The first finalized block gets the starting block_num.
|
||||
let rel_block_num = this.rel_block_num;
|
||||
// Pin this block, but note that it can be unpinned any time since it won't show up again (except
|
||||
// as a parent block, which we are ignoring at the moment).
|
||||
let block_ref =
|
||||
this.pin_unpinnable_block_at(rel_block_num, details.finalized_block_hash);
|
||||
let mut finalized_block_hashes =
|
||||
Vec::with_capacity(details.finalized_block_hashes.len());
|
||||
|
||||
// Pin each of the finalized blocks. None of them will show up again (except as a
|
||||
// parent block), and so they can all be unpinned immediately at any time. Increment
|
||||
// the block age for each one, so that older finalized blocks are pruned first.
|
||||
for finalized_block in &details.finalized_block_hashes {
|
||||
let rel_block_age = this.next_rel_block_age;
|
||||
let block_ref =
|
||||
this.pin_unpinnable_block_at(rel_block_age, *finalized_block);
|
||||
|
||||
finalized_block_hashes.push(block_ref);
|
||||
this.next_rel_block_age += 1;
|
||||
}
|
||||
|
||||
FollowStreamMsg::Event(FollowEvent::Initialized(Initialized {
|
||||
finalized_block_hash: block_ref,
|
||||
finalized_block_hashes,
|
||||
finalized_block_runtime: details.finalized_block_runtime,
|
||||
}))
|
||||
}
|
||||
@@ -129,15 +140,15 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
// One bigger than our parent, and if no parent seen (maybe it was
|
||||
// unpinned already), then one bigger than the last finalized block num
|
||||
// as a best guess.
|
||||
let parent_rel_block_num = this
|
||||
let parent_rel_block_age = this
|
||||
.pinned
|
||||
.get(&details.parent_block_hash)
|
||||
.map(|p| p.rel_block_num)
|
||||
.unwrap_or(this.rel_block_num);
|
||||
.map(|p| p.rel_block_age)
|
||||
.unwrap_or(this.next_rel_block_age.saturating_sub(1));
|
||||
|
||||
let block_ref = this.pin_block_at(parent_rel_block_num + 1, details.block_hash);
|
||||
let block_ref = this.pin_block_at(parent_rel_block_age + 1, details.block_hash);
|
||||
let parent_block_ref =
|
||||
this.pin_block_at(parent_rel_block_num, details.parent_block_hash);
|
||||
this.pin_block_at(parent_rel_block_age, details.parent_block_hash);
|
||||
|
||||
FollowStreamMsg::Event(FollowEvent::NewBlock(NewBlock {
|
||||
block_hash: block_ref,
|
||||
@@ -148,8 +159,8 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
FollowStreamMsg::Event(FollowEvent::BestBlockChanged(details)) => {
|
||||
// We expect this block to already exist, so it'll keep its existing block_num,
|
||||
// but worst case it'll just get the current finalized block_num + 1.
|
||||
let rel_block_num = this.rel_block_num + 1;
|
||||
let block_ref = this.pin_block_at(rel_block_num, details.best_block_hash);
|
||||
let rel_block_age = this.next_rel_block_age;
|
||||
let block_ref = this.pin_block_at(rel_block_age, details.best_block_hash);
|
||||
|
||||
FollowStreamMsg::Event(FollowEvent::BestBlockChanged(BestBlockChanged {
|
||||
best_block_hash: block_ref,
|
||||
@@ -167,14 +178,14 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
//
|
||||
// `pin_unpinnable_block_at` indicates that the block will not show up in future events
|
||||
// (They will show up as a parent block, but we don't care about that right now).
|
||||
let rel_block_num = this.rel_block_num + idx + 1;
|
||||
this.pin_unpinnable_block_at(rel_block_num, hash)
|
||||
let rel_block_age = this.next_rel_block_age + idx;
|
||||
this.pin_unpinnable_block_at(rel_block_age, hash)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Our relative block height is increased by however many finalized
|
||||
// blocks we've seen.
|
||||
this.rel_block_num += finalized_block_refs.len();
|
||||
this.next_rel_block_age += finalized_block_refs.len();
|
||||
|
||||
let pruned_block_refs: Vec<_> = details
|
||||
.pruned_block_hashes
|
||||
@@ -183,8 +194,8 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
// We should know about these, too, and if not we set their age to last_finalized + 1.
|
||||
//
|
||||
// `pin_unpinnable_block_at` indicates that the block will not show up in future events.
|
||||
let rel_block_num = this.rel_block_num + 1;
|
||||
this.pin_unpinnable_block_at(rel_block_num, hash)
|
||||
let rel_block_age = this.next_rel_block_age;
|
||||
this.pin_unpinnable_block_at(rel_block_age, hash)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -208,7 +219,7 @@ impl<Hash: BlockHash> Stream for FollowStreamUnpin<Hash> {
|
||||
this.pinned.clear();
|
||||
this.unpin_futs.clear();
|
||||
this.unpin_flags.lock().unwrap().clear();
|
||||
this.rel_block_num = 0;
|
||||
this.next_rel_block_age = 0;
|
||||
|
||||
FollowStreamMsg::Event(FollowEvent::Stop)
|
||||
}
|
||||
@@ -255,7 +266,7 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
|
||||
max_block_life,
|
||||
pinned: Default::default(),
|
||||
subscription_id: None,
|
||||
rel_block_num: 0,
|
||||
next_rel_block_age: 0,
|
||||
unpin_flags: Default::default(),
|
||||
unpin_futs: Default::default(),
|
||||
}
|
||||
@@ -287,21 +298,21 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
|
||||
/// Pin a block, or return the reference to an already-pinned block. If the block has been registered to
|
||||
/// be unpinned, we'll clear those flags, so that it won't be unpinned. If the unpin request has already
|
||||
/// been sent though, then the block will be unpinned.
|
||||
fn pin_block_at(&mut self, rel_block_num: usize, hash: Hash) -> BlockRef<Hash> {
|
||||
self.pin_block_at_setting_unpinnable_flag(rel_block_num, hash, false)
|
||||
fn pin_block_at(&mut self, rel_block_age: usize, hash: Hash) -> BlockRef<Hash> {
|
||||
self.pin_block_at_setting_unpinnable_flag(rel_block_age, hash, false)
|
||||
}
|
||||
|
||||
/// Pin a block, or return the reference to an already-pinned block.
|
||||
///
|
||||
/// This is the same as [`Self::pin_block_at`], except that it also marks the block as being unpinnable now,
|
||||
/// which should be done for any block that will no longer be seen in future events.
|
||||
fn pin_unpinnable_block_at(&mut self, rel_block_num: usize, hash: Hash) -> BlockRef<Hash> {
|
||||
self.pin_block_at_setting_unpinnable_flag(rel_block_num, hash, true)
|
||||
fn pin_unpinnable_block_at(&mut self, rel_block_age: usize, hash: Hash) -> BlockRef<Hash> {
|
||||
self.pin_block_at_setting_unpinnable_flag(rel_block_age, hash, true)
|
||||
}
|
||||
|
||||
fn pin_block_at_setting_unpinnable_flag(
|
||||
&mut self,
|
||||
rel_block_num: usize,
|
||||
rel_block_age: usize,
|
||||
hash: Hash,
|
||||
can_be_unpinned: bool,
|
||||
) -> BlockRef<Hash> {
|
||||
@@ -317,7 +328,7 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
|
||||
})
|
||||
// If there's not an entry already, make one and return it.
|
||||
.or_insert_with(|| PinnedDetails {
|
||||
rel_block_num,
|
||||
rel_block_age,
|
||||
block_ref: BlockRef {
|
||||
inner: Arc::new(BlockRefInner {
|
||||
hash,
|
||||
@@ -333,7 +344,9 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
|
||||
/// Unpin any blocks that are either too old, or have the unpin flag set and are old enough.
|
||||
fn unpin_blocks(&mut self, waker: &Waker) {
|
||||
let mut unpin_flags = self.unpin_flags.lock().unwrap();
|
||||
let rel_block_num = self.rel_block_num;
|
||||
|
||||
// This gets the age of the last finalized block.
|
||||
let rel_block_age = self.next_rel_block_age.saturating_sub(1);
|
||||
|
||||
// If we asked to unpin and there was no subscription_id, then there's nothing we can do,
|
||||
// and nothing will need unpinning now anyway.
|
||||
@@ -343,7 +356,7 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
|
||||
|
||||
let mut blocks_to_unpin = vec![];
|
||||
for (hash, details) in &self.pinned {
|
||||
if rel_block_num.saturating_sub(details.rel_block_num) >= self.max_block_life
|
||||
if rel_block_age.saturating_sub(details.rel_block_age) >= self.max_block_life
|
||||
|| (unpin_flags.contains(hash) && details.can_be_unpinned)
|
||||
{
|
||||
// The block is too old, or it's been flagged to be unpinned and won't be in a future
|
||||
@@ -381,8 +394,10 @@ type UnpinFlags<Hash> = Arc<Mutex<HashSet<Hash>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PinnedDetails<Hash: BlockHash> {
|
||||
/// How old is the block?
|
||||
rel_block_num: usize,
|
||||
/// Realtively speaking, how old is the block? When we start following
|
||||
/// blocks, the first finalized block gets an age of 0, the second an age
|
||||
/// of 1 and so on.
|
||||
rel_block_age: usize,
|
||||
/// A block ref we can hand out to keep blocks pinned.
|
||||
/// Because we store one here until it's unpinned, the live count
|
||||
/// will only drop to 1 when no external refs are left.
|
||||
@@ -502,7 +517,7 @@ pub(super) mod test_utils {
|
||||
/// An initialized event containing a BlockRef (useful for comparisons)
|
||||
pub fn ev_initialized_ref(n: u64) -> FollowEvent<BlockRef<H256>> {
|
||||
FollowEvent::Initialized(Initialized {
|
||||
finalized_block_hash: BlockRef::new(H256::from_low_u64_le(n)),
|
||||
finalized_block_hashes: vec![BlockRef::new(H256::from_low_u64_le(n))],
|
||||
finalized_block_runtime: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,9 +75,12 @@ impl<T: Config> UnstableBackendBuilder<T> {
|
||||
/// Given an [`RpcClient`] to use to make requests, this returns a tuple of an [`UnstableBackend`],
|
||||
/// which implements the [`Backend`] trait, and an [`UnstableBackendDriver`] which must be polled in
|
||||
/// order for the backend to make progress.
|
||||
pub fn build(self, client: RpcClient) -> (UnstableBackend<T>, UnstableBackendDriver<T>) {
|
||||
pub fn build(
|
||||
self,
|
||||
client: impl Into<RpcClient>,
|
||||
) -> (UnstableBackend<T>, UnstableBackendDriver<T>) {
|
||||
// Construct the underlying follow_stream layers:
|
||||
let rpc_methods = UnstableRpcMethods::new(client);
|
||||
let rpc_methods = UnstableRpcMethods::new(client.into());
|
||||
let follow_stream =
|
||||
follow_stream::FollowStream::<T::Hash>::from_methods(rpc_methods.clone());
|
||||
let follow_stream_unpin = follow_stream_unpin::FollowStreamUnpin::<T::Hash>::from_methods(
|
||||
@@ -321,7 +324,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
.events()
|
||||
.filter_map(|ev| {
|
||||
let out = match ev {
|
||||
FollowEvent::Initialized(init) => Some(init.finalized_block_hash.into()),
|
||||
FollowEvent::Initialized(init) => {
|
||||
init.finalized_block_hashes.last().map(|b| b.clone().into())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
std::future::ready(out)
|
||||
@@ -353,7 +358,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
.filter_map(move |ev| {
|
||||
let output = match ev {
|
||||
FollowEvent::Initialized(ev) => {
|
||||
runtimes.remove(&ev.finalized_block_hash.hash());
|
||||
for finalized_block in ev.finalized_block_hashes {
|
||||
runtimes.remove(&finalized_block.hash());
|
||||
}
|
||||
ev.finalized_block_runtime
|
||||
}
|
||||
FollowEvent::NewBlock(ev) => {
|
||||
@@ -409,10 +416,8 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
RuntimeEvent::Valid(ev) => ev,
|
||||
};
|
||||
|
||||
std::future::ready(Some(Ok(RuntimeVersion {
|
||||
spec_version: runtime_details.spec.spec_version,
|
||||
transaction_version: runtime_details.spec.transaction_version,
|
||||
})))
|
||||
let runtime_version = RuntimeVersion::new(runtime_details.spec.spec_version, runtime_details.spec.transaction_version);
|
||||
std::future::ready(Some(Ok(runtime_version)))
|
||||
});
|
||||
|
||||
Ok(StreamOf(Box::pin(runtime_stream)))
|
||||
@@ -422,9 +427,11 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
&self,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<T::Hash>)>, Error> {
|
||||
self.stream_headers(|ev| match ev {
|
||||
FollowEvent::Initialized(ev) => Some(ev.finalized_block_hash),
|
||||
FollowEvent::NewBlock(ev) => Some(ev.block_hash),
|
||||
_ => None,
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes,
|
||||
FollowEvent::NewBlock(ev) => {
|
||||
vec![ev.block_hash]
|
||||
}
|
||||
_ => vec![],
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -433,9 +440,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
&self,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<T::Hash>)>, Error> {
|
||||
self.stream_headers(|ev| match ev {
|
||||
FollowEvent::Initialized(ev) => Some(ev.finalized_block_hash),
|
||||
FollowEvent::BestBlockChanged(ev) => Some(ev.best_block_hash),
|
||||
_ => None,
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes,
|
||||
FollowEvent::BestBlockChanged(ev) => vec![ev.best_block_hash],
|
||||
_ => vec![],
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -444,9 +451,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
&self,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<T::Hash>)>, Error> {
|
||||
self.stream_headers(|ev| match ev {
|
||||
FollowEvent::Initialized(ev) => {
|
||||
vec![ev.finalized_block_hash]
|
||||
}
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes,
|
||||
FollowEvent::Finalized(ev) => ev.finalized_block_hashes,
|
||||
_ => vec![],
|
||||
})
|
||||
@@ -610,7 +615,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
TransactionStatus::Dropped { message: error }
|
||||
}
|
||||
rpc_methods::TransactionStatus::Error { error } => {
|
||||
TransactionStatus::Dropped { message: error }
|
||||
TransactionStatus::Error { message: error }
|
||||
}
|
||||
rpc_methods::TransactionStatus::Invalid { error } => {
|
||||
TransactionStatus::Invalid { message: error }
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use crate::backend::rpc::{rpc_params, RpcClient, RpcSubscription};
|
||||
use crate::config::BlockHash;
|
||||
use crate::{Config, Error};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -18,8 +18,7 @@ use std::task::Poll;
|
||||
/// An interface to call the unstable RPC methods. This interface is instantiated with
|
||||
/// some `T: Config` trait which determines some of the types that the RPC methods will
|
||||
/// take or hand back.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""), Debug(bound = ""))]
|
||||
#[derive_where(Clone, Debug)]
|
||||
pub struct UnstableRpcMethods<T> {
|
||||
client: RpcClient,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
@@ -288,6 +287,28 @@ impl<T: Config> UnstableRpcMethods<T> {
|
||||
|
||||
Ok(TransactionSubscription { sub, done: false })
|
||||
}
|
||||
|
||||
/// Broadcast the transaction on the p2p network until the
|
||||
/// [`Self::transaction_unstable_stop`] is called.
|
||||
///
|
||||
/// Returns an operation ID that can be used to stop the broadcasting process.
|
||||
/// Returns `None` if the server cannot handle the request at the moment.
|
||||
pub async fn transaction_unstable_broadcast(&self, tx: &[u8]) -> Result<Option<String>, Error> {
|
||||
self.client
|
||||
.request("transaction_unstable_broadcast", rpc_params![to_hex(tx)])
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stop the broadcasting process of the transaction.
|
||||
///
|
||||
/// The operation ID is obtained from the [`Self::transaction_unstable_broadcast`] method.
|
||||
///
|
||||
/// Returns an error if the operation ID does not correspond to any active transaction for this connection.
|
||||
pub async fn transaction_unstable_stop(&self, operation_id: &str) -> Result<(), Error> {
|
||||
self.client
|
||||
.request("transaction_unstable_stop", rpc_params![operation_id])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents events generated by the `follow` method.
|
||||
@@ -359,8 +380,8 @@ pub enum FollowEvent<Hash> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Initialized<Hash> {
|
||||
/// The hash of the latest finalized block.
|
||||
pub finalized_block_hash: Hash,
|
||||
/// The hashes of the last finalized blocks.
|
||||
pub finalized_block_hashes: Vec<Hash>,
|
||||
/// The runtime version of the finalized block.
|
||||
///
|
||||
/// # Note
|
||||
|
||||
@@ -150,6 +150,11 @@ impl<T: Config> Stream for StorageItems<T> {
|
||||
self.buffered_responses = items.items;
|
||||
continue;
|
||||
}
|
||||
FollowEvent::OperationError(err) if err.operation_id == *self.operation_id => {
|
||||
// Something went wrong obtaining storage items; mark as done and return the error.
|
||||
self.done = true;
|
||||
return Poll::Ready(Some(Err(Error::Other(err.error))));
|
||||
}
|
||||
_ => {
|
||||
// We don't care about this event; wait for the next.
|
||||
continue;
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
error::{BlockError, Error},
|
||||
utils::PhantomDataSendSync,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
use std::future::Future;
|
||||
|
||||
@@ -18,8 +18,7 @@ type BlockStream<T> = StreamOfResults<T>;
|
||||
type BlockStreamRes<T> = Result<BlockStream<T>, Error>;
|
||||
|
||||
/// A client for working with blocks.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct BlocksClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomDataSendSync<T>,
|
||||
|
||||
@@ -16,31 +16,14 @@ use crate::config::signed_extensions::{
|
||||
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
|
||||
};
|
||||
use crate::config::SignedExtension;
|
||||
use crate::dynamic::DecodedValue;
|
||||
use crate::dynamic::Value;
|
||||
use crate::utils::strip_compact_prefix;
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
|
||||
///
|
||||
/// Generated API structures that represent an extrinsic implement this trait.
|
||||
///
|
||||
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
|
||||
/// form of the `Extrinsic` from the metadata.
|
||||
pub trait StaticExtrinsic: DecodeAsFields {
|
||||
/// Pallet name.
|
||||
const PALLET: &'static str;
|
||||
/// Call name.
|
||||
const CALL: &'static str;
|
||||
|
||||
/// Returns true if the given pallet and call names match this extrinsic.
|
||||
fn is_extrinsic(pallet: &str, call: &str) -> bool {
|
||||
Self::PALLET == pallet && Self::CALL == call
|
||||
}
|
||||
}
|
||||
pub use subxt_core::blocks::StaticExtrinsic;
|
||||
|
||||
/// The body of a block.
|
||||
pub struct Extrinsics<T: Config, C> {
|
||||
@@ -245,27 +228,27 @@ where
|
||||
// Skip over the address, signature and extra fields.
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.address,
|
||||
&ids.address,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let address_end_idx = bytes.len() - cursor.len();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.signature,
|
||||
&ids.signature,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let signature_end_idx = bytes.len() - cursor.len();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ids.extra,
|
||||
&ids.extra,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let extra_end_idx = bytes.len() - cursor.len();
|
||||
@@ -420,9 +403,7 @@ where
|
||||
|
||||
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the extrinsic.
|
||||
pub fn field_values(
|
||||
&self,
|
||||
) -> Result<scale_value::Composite<scale_value::scale::TypeId>, Error> {
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
|
||||
@@ -430,12 +411,9 @@ where
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
|
||||
bytes,
|
||||
&mut fields,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
@@ -451,7 +429,7 @@ where
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
|
||||
Ok(Some(decoded))
|
||||
@@ -466,7 +444,7 @@ where
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &self.call_bytes()[..],
|
||||
self.metadata.outer_enums().call_enum_ty(),
|
||||
&self.metadata.outer_enums().call_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
|
||||
@@ -529,8 +507,7 @@ impl ExtrinsicPartTypeIds {
|
||||
}
|
||||
|
||||
/// The events associated with a given extrinsic.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
#[derive_where(Debug)]
|
||||
pub struct ExtrinsicEvents<T: Config> {
|
||||
// The hash of the extrinsic (handy to expose here because
|
||||
// this type is returned from TxProgress things in the most
|
||||
@@ -552,11 +529,6 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the hash of the block that the extrinsic is in.
|
||||
pub fn block_hash(&self) -> T::Hash {
|
||||
self.events.block_hash()
|
||||
}
|
||||
|
||||
/// The index of the extrinsic that these events are produced from.
|
||||
pub fn extrinsic_index(&self) -> u32 {
|
||||
self.idx
|
||||
@@ -577,11 +549,14 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, Error>> + '_ {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
.unwrap_or(true) // Keep any errors.
|
||||
})
|
||||
self.events
|
||||
.iter()
|
||||
.filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
.unwrap_or(true) // Keep any errors.
|
||||
})
|
||||
.map(|e| e.map_err(Error::from))
|
||||
}
|
||||
|
||||
/// Find all of the transaction events matching the event type provided as a generic parameter.
|
||||
@@ -651,9 +626,9 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
|
||||
let cursor = &mut &bytes[byte_start_idx..];
|
||||
if let Err(err) = scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
ty_id,
|
||||
&ty_id,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(|e| Error::Decode(e.into()))
|
||||
{
|
||||
@@ -747,8 +722,13 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
|
||||
}
|
||||
|
||||
/// Signed Extension as a [`scale_value::Value`]
|
||||
pub fn value(&self) -> Result<DecodedValue, Error> {
|
||||
self.as_type()
|
||||
pub fn value(&self) -> Result<Value<u32>, Error> {
|
||||
let value = scale_value::scale::decode_as_type(
|
||||
&mut &self.bytes[..],
|
||||
&self.ty_id,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Decodes the bytes of this Signed Extension into its associated `Decoded` type.
|
||||
@@ -762,7 +742,7 @@ impl<'a, T: Config> ExtrinsicSignedExtension<'a, T> {
|
||||
}
|
||||
|
||||
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
|
||||
let value = E::decode_as_type(&mut &self.bytes[..], &self.ty_id, self.metadata.types())?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -770,7 +750,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 +761,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.
|
||||
@@ -902,10 +883,7 @@ mod tests {
|
||||
/// Build an offline client to work with the test metadata.
|
||||
fn client(metadata: Metadata) -> OfflineClient<PolkadotConfig> {
|
||||
// Create the encoded extrinsic bytes.
|
||||
let rt_version = RuntimeVersion {
|
||||
spec_version: 1,
|
||||
transaction_version: 4,
|
||||
};
|
||||
let rt_version = RuntimeVersion::new(1, 4);
|
||||
let block_hash = H256::random();
|
||||
OfflineClient::new(block_hash, rt_version, metadata)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
//!
|
||||
//! The `ExtrinsicParams` config type expects to be given an implementation of the [`crate::config::ExtrinsicParams`] trait.
|
||||
//! Implementations of the [`crate::config::ExtrinsicParams`] trait are handed some parameters from Subxt itself, and can
|
||||
//! accept arbitrary `OtherParams` from users, and are then expected to provide this "extra" and "additional" data when asked
|
||||
//! accept arbitrary other `Params` from users, and are then expected to provide this "extra" and "additional" data when asked
|
||||
//! via the required [`crate::config::ExtrinsicParamsEncoder`] impl.
|
||||
//!
|
||||
//! **In most cases, the default [crate::config::DefaultExtrinsicParams] type will work**: it understands the "standard"
|
||||
|
||||
@@ -8,25 +8,33 @@
|
||||
//! node. This means that you don't have to trust a specific node when interacting with some chain.
|
||||
//!
|
||||
//! This feature is currently unstable. Use the `unstable-light-client` feature flag to enable it.
|
||||
//! To use this in WASM environments, also enable the `web` feature flag.
|
||||
//! To use this in WASM environments, enable the `web` feature flag and disable the "native" one.
|
||||
//!
|
||||
//! To connect to a blockchain network, the Light Client requires a trusted sync state of the network,
|
||||
//! known as a _chain spec_. One way to obtain this is by making a `sync_state_genSyncSpec` RPC call to a
|
||||
//! trusted node belonging to the chain that you wish to interact with.
|
||||
//!
|
||||
//! The following is an example of fetching the chain spec from a local running node on port 9933:
|
||||
//! Subxt exposes a utility method to obtain the chain spec: [`crate::utils::fetch_chainspec_from_rpc_node()`].
|
||||
//! Alternately, you can manually make an RPC call to `sync_state_genSyncSpec` like do (assuming a node running
|
||||
//! locally on port 9933):
|
||||
//!
|
||||
//! ```bash
|
||||
//! curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "sync_state_genSyncSpec", "params":[true]}' http://localhost:9933/ | jq .result > chain_spec.json
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, you can have the `LightClient` download the chain spec from a trusted node when it
|
||||
//! initializes, which is not recommended in production but is useful for examples and testing, as below.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### Basic Example
|
||||
//!
|
||||
//! This basic example uses some already-known chain specs to connect to a relay chain and parachain
|
||||
//! and stream information about their finalized blocks:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/light_client_basic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Connecting to a local node
|
||||
//!
|
||||
//! This example connects to a local chain and submits a transaction. To run this, you first need
|
||||
//! to have a local polkadot node running using the following command:
|
||||
//!
|
||||
@@ -34,23 +42,10 @@
|
||||
//! polkadot --dev --node-key 0000000000000000000000000000000000000000000000000000000000000001
|
||||
//! ```
|
||||
//!
|
||||
//! Leave that running for a minute, and then you can run the example using the following command
|
||||
//! in the `subxt` crate:
|
||||
//!
|
||||
//! ```bash
|
||||
//! cargo run --example light_client_tx_basic --features=unstable-light-client
|
||||
//! ```
|
||||
//!
|
||||
//! This is the code that will be executed:
|
||||
//! Then, the following code will download a chain spec from this local node, alter the bootnodes
|
||||
//! to point only to the local node, and then submit a transaction through it.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/light_client_tx_basic.rs")]
|
||||
#![doc = include_str!("../../../examples/light_client_local_node.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Connecting to a parachain
|
||||
//!
|
||||
//! This example connects to a parachain using the light client. Currently, it's quite verbose to do this.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/light_client_parachains.rs")]
|
||||
//! ```
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
//! // A static query capable of iterating over accounts:
|
||||
//! let storage_query = polkadot::storage().system().account_iter();
|
||||
//! // A dynamic query to do the same:
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", Vec::<u8>::new());
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", ());
|
||||
//! ```
|
||||
//!
|
||||
//! Some storage entries are maps with multiple keys. As an example, we might end up with
|
||||
|
||||
@@ -137,11 +137,10 @@
|
||||
//! Value::from_bytes("Hello there")
|
||||
//! ]);
|
||||
//!
|
||||
//! // Construct the tx but don't sign it. You need to provide the nonce
|
||||
//! // here, or can use `create_partial_signed` to fetch the correct nonce.
|
||||
//! let partial_tx = client.tx().create_partial_signed_with_nonce(
|
||||
//! // Construct the tx but don't sign it. The account nonce here defaults to 0.
|
||||
//! // You can use `create_partial_signed` to fetch the correct nonce.
|
||||
//! let partial_tx = client.tx().create_partial_signed_offline(
|
||||
//! &payload,
|
||||
//! 0u64,
|
||||
//! Default::default()
|
||||
//! )?;
|
||||
//!
|
||||
|
||||
@@ -1,163 +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::{smoldot, LightClientError};
|
||||
use crate::{
|
||||
backend::rpc::{RawRpcFuture, RawRpcSubscription, RpcClientT},
|
||||
error::{Error, RpcError},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use serde_json::value::RawValue;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
pub const LOG_TARGET: &str = "subxt-rpc-light-client";
|
||||
|
||||
/// The raw light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
pub struct RawLightClientRpc(subxt_lightclient::RawLightClientRpc);
|
||||
|
||||
impl RawLightClientRpc {
|
||||
/// Constructs a new [`RawLightClientRpc`] from a low level [`subxt_lightclient::RawLightClientRpc`].
|
||||
pub fn from_inner(client: subxt_lightclient::RawLightClientRpc) -> RawLightClientRpc {
|
||||
RawLightClientRpc(client)
|
||||
}
|
||||
|
||||
/// Constructs a new [`LightClientRpc`] that communicates with the provided chain.
|
||||
pub fn for_chain(&self, chain_id: smoldot::ChainId) -> LightClientRpc {
|
||||
LightClientRpc(self.0.for_chain(chain_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// The light-client RPC implementation that is used to connect with the chain.
|
||||
#[derive(Clone)]
|
||||
pub struct LightClientRpc(subxt_lightclient::LightClientRpc);
|
||||
|
||||
impl LightClientRpc {
|
||||
/// Constructs a new [`LightClientRpc`], providing the chain specification.
|
||||
///
|
||||
/// The chain specification can be downloaded from a trusted network via
|
||||
/// the `sync_state_genSyncSpec` RPC method. This parameter expects the
|
||||
/// chain spec in text format (ie not in hex-encoded scale-encoded as RPC methods
|
||||
/// will provide).
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// The panic behaviour depends on the feature flag being used:
|
||||
///
|
||||
/// ### Native
|
||||
///
|
||||
/// Panics when called outside of a `tokio` runtime context.
|
||||
///
|
||||
/// ### Web
|
||||
///
|
||||
/// If smoldot panics, then the promise created will be leaked. For more details, see
|
||||
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html.
|
||||
pub fn new(
|
||||
config: smoldot::AddChainConfig<'_, (), impl Iterator<Item = smoldot::ChainId>>,
|
||||
) -> Result<LightClientRpc, Error> {
|
||||
let rpc = subxt_lightclient::LightClientRpc::new(config)
|
||||
.map_err(|err| LightClientError::Rpc(err))?;
|
||||
|
||||
Ok(LightClientRpc(rpc))
|
||||
}
|
||||
|
||||
/// Returns the chain ID of the current light-client.
|
||||
pub fn chain_id(&self) -> smoldot::ChainId {
|
||||
self.0.chain_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcClientT for LightClientRpc {
|
||||
fn request_raw<'a>(
|
||||
&'a self,
|
||||
method: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
) -> RawRpcFuture<'a, Box<RawValue>> {
|
||||
let client = self.clone();
|
||||
let chain_id = self.chain_id();
|
||||
|
||||
Box::pin(async move {
|
||||
let params = match params {
|
||||
Some(params) => serde_json::to_string(¶ms).map_err(|_| {
|
||||
RpcError::ClientError(Box::new(LightClientError::InvalidParams))
|
||||
})?,
|
||||
None => "[]".into(),
|
||||
};
|
||||
|
||||
// Fails if the background is closed.
|
||||
let rx = client
|
||||
.0
|
||||
.method_request(method.to_string(), params)
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?;
|
||||
|
||||
// Fails if the background is closed.
|
||||
let response = rx
|
||||
.await
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?;
|
||||
|
||||
tracing::trace!(target: LOG_TARGET, "RPC response={:?} chain={:?}", response, chain_id);
|
||||
|
||||
response.map_err(|err| RpcError::ClientError(Box::new(err)))
|
||||
})
|
||||
}
|
||||
|
||||
fn subscribe_raw<'a>(
|
||||
&'a self,
|
||||
sub: &'a str,
|
||||
params: Option<Box<RawValue>>,
|
||||
unsub: &'a str,
|
||||
) -> RawRpcFuture<'a, RawRpcSubscription> {
|
||||
let client = self.clone();
|
||||
let chain_id = self.chain_id();
|
||||
|
||||
Box::pin(async move {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Subscribe to {:?} with params {:?} chain={:?}",
|
||||
sub,
|
||||
params,
|
||||
chain_id,
|
||||
);
|
||||
|
||||
let params = match params {
|
||||
Some(params) => serde_json::to_string(¶ms).map_err(|_| {
|
||||
RpcError::ClientError(Box::new(LightClientError::InvalidParams))
|
||||
})?,
|
||||
None => "[]".into(),
|
||||
};
|
||||
|
||||
// Fails if the background is closed.
|
||||
let (sub_id, notif) = client
|
||||
.0
|
||||
.subscription_request(sub.to_string(), params, unsub.to_string())
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?;
|
||||
|
||||
// Fails if the background is closed.
|
||||
let result = sub_id
|
||||
.await
|
||||
.map_err(|_| RpcError::ClientError(Box::new(LightClientError::BackgroundClosed)))?
|
||||
.map_err(|err| {
|
||||
RpcError::ClientError(Box::new(LightClientError::Rpc(
|
||||
subxt_lightclient::LightClientRpcError::Request(err.to_string()),
|
||||
)))
|
||||
})?;
|
||||
|
||||
let sub_id = result
|
||||
.get()
|
||||
.trim_start_matches('"')
|
||||
.trim_end_matches('"')
|
||||
.to_string();
|
||||
tracing::trace!(target: LOG_TARGET, "Received subscription={} chain={:?}", sub_id, chain_id);
|
||||
|
||||
let stream = UnboundedReceiverStream::new(notif);
|
||||
|
||||
let rpc_subscription = RawRpcSubscription {
|
||||
stream: Box::pin(stream.map(Ok)),
|
||||
id: Some(sub_id),
|
||||
};
|
||||
|
||||
Ok(rpc_subscription)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,8 @@
|
||||
mod offline_client;
|
||||
mod online_client;
|
||||
|
||||
crate::macros::cfg_unstable_light_client! {
|
||||
mod light_client;
|
||||
|
||||
pub use light_client::{
|
||||
LightClient, LightClientBuilder, LightClientError, RawLightClient, RawLightClientBuilder,
|
||||
};
|
||||
}
|
||||
|
||||
pub use offline_client::{OfflineClient, OfflineClientT};
|
||||
pub use online_client::{
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
|
||||
};
|
||||
pub use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
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 derive_where::derive_where;
|
||||
use std::sync::Arc;
|
||||
use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
|
||||
/// A trait representing a client that can perform
|
||||
/// offline-only actions.
|
||||
@@ -21,6 +21,10 @@ 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 [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
ClientState::new(self.genesis_hash(), self.runtime_version(), self.metadata())
|
||||
}
|
||||
|
||||
/// Work with transactions.
|
||||
fn tx(&self) -> TxClient<T, Self> {
|
||||
@@ -60,18 +64,9 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
|
||||
|
||||
/// A client that is capable of performing offline-only operations.
|
||||
/// Can be constructed as long as you can populate the required fields.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""), Clone(bound = ""))]
|
||||
#[derive_where(Debug, Clone)]
|
||||
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<ClientState<T>>,
|
||||
}
|
||||
|
||||
impl<T: Config> OfflineClient<T> {
|
||||
@@ -83,27 +78,36 @@ impl<T: Config> OfflineClient<T> {
|
||||
metadata: impl Into<Metadata>,
|
||||
) -> OfflineClient<T> {
|
||||
OfflineClient {
|
||||
inner: Arc::new(Inner {
|
||||
inner: Arc::new(ClientState::new(
|
||||
genesis_hash,
|
||||
runtime_version,
|
||||
metadata: metadata.into(),
|
||||
}),
|
||||
metadata.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the genesis hash.
|
||||
pub fn genesis_hash(&self) -> T::Hash {
|
||||
self.inner.genesis_hash
|
||||
self.inner.genesis_hash()
|
||||
}
|
||||
|
||||
/// Return the runtime version.
|
||||
pub fn runtime_version(&self) -> RuntimeVersion {
|
||||
self.inner.runtime_version.clone()
|
||||
self.inner.runtime_version()
|
||||
}
|
||||
|
||||
/// Return the [`Metadata`] used in this client.
|
||||
pub fn metadata(&self) -> Metadata {
|
||||
self.inner.metadata.clone()
|
||||
self.inner.metadata()
|
||||
}
|
||||
|
||||
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
pub fn client_state(&self) -> ClientState<T> {
|
||||
ClientState::new(
|
||||
self.inner.genesis_hash(),
|
||||
self.inner.runtime_version(),
|
||||
self.inner.metadata(),
|
||||
)
|
||||
}
|
||||
|
||||
// Just a copy of the most important trait methods so that people
|
||||
@@ -145,6 +149,9 @@ impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.metadata()
|
||||
}
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
self.client_state()
|
||||
}
|
||||
}
|
||||
|
||||
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
|
||||
|
||||
@@ -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,
|
||||
@@ -17,9 +15,10 @@ use crate::{
|
||||
tx::TxClient,
|
||||
Config, Metadata,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use futures::future;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
|
||||
/// A trait representing a client that can perform
|
||||
/// online actions.
|
||||
@@ -30,15 +29,13 @@ pub trait OnlineClientT<T: Config>: OfflineClientT<T> {
|
||||
|
||||
/// A client that can be used to perform API calls (that is, either those
|
||||
/// requiring an [`OfflineClientT`] or those requiring an [`OnlineClientT`]).
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
#[derive_where(Clone)]
|
||||
pub struct OnlineClient<T: Config> {
|
||||
inner: Arc<RwLock<Inner<T>>>,
|
||||
backend: Arc<dyn Backend<T>>,
|
||||
}
|
||||
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
#[derive_where(Debug)]
|
||||
struct Inner<T: Config> {
|
||||
genesis_hash: T::Hash,
|
||||
runtime_version: RuntimeVersion,
|
||||
@@ -76,7 +73,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
|
||||
pub async fn from_insecure_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
|
||||
let client = RpcClient::from_insecure_url(url).await?;
|
||||
let backend = LegacyBackend::new(client);
|
||||
let backend = LegacyBackend::builder().build(client);
|
||||
OnlineClient::from_backend(Arc::new(backend)).await
|
||||
}
|
||||
}
|
||||
@@ -84,8 +81,11 @@ impl<T: Config> OnlineClient<T> {
|
||||
impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`] by providing an [`RpcClient`] to drive the connection.
|
||||
/// This will use the current default [`Backend`], which may change in future releases.
|
||||
pub async fn from_rpc_client(rpc_client: RpcClient) -> Result<OnlineClient<T>, Error> {
|
||||
let backend = Arc::new(LegacyBackend::new(rpc_client));
|
||||
pub async fn from_rpc_client(
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend(backend).await
|
||||
}
|
||||
|
||||
@@ -106,9 +106,10 @@ impl<T: Config> OnlineClient<T> {
|
||||
genesis_hash: T::Hash,
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
rpc_client: RpcClient,
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
let backend = Arc::new(LegacyBackend::new(rpc_client));
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend)
|
||||
}
|
||||
|
||||
@@ -227,7 +228,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// let mut update_stream = updater.runtime_updates().await.unwrap();
|
||||
///
|
||||
/// while let Some(Ok(update)) = update_stream.next().await {
|
||||
/// let version = update.runtime_version().spec_version;
|
||||
/// let version = update.runtime_version().spec_version();
|
||||
///
|
||||
/// match updater.apply_update(update) {
|
||||
/// Ok(()) => {
|
||||
@@ -282,7 +283,17 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// Return the runtime version.
|
||||
pub fn runtime_version(&self) -> RuntimeVersion {
|
||||
let inner = self.inner.read().expect("shouldn't be poisoned");
|
||||
inner.runtime_version.clone()
|
||||
inner.runtime_version
|
||||
}
|
||||
|
||||
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
pub fn client_state(&self) -> ClientState<T> {
|
||||
let inner = self.inner.read().expect("shouldn't be poisoned");
|
||||
ClientState::new(
|
||||
inner.genesis_hash,
|
||||
inner.runtime_version,
|
||||
inner.metadata.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Change the [`RuntimeVersion`] used in this client.
|
||||
@@ -306,7 +317,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
let inner = self.inner.read().expect("shouldn't be poisoned");
|
||||
OfflineClient::new(
|
||||
inner.genesis_hash,
|
||||
inner.runtime_version.clone(),
|
||||
inner.runtime_version,
|
||||
inner.metadata.clone(),
|
||||
)
|
||||
}
|
||||
@@ -360,6 +371,10 @@ impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
|
||||
fn runtime_version(&self) -> RuntimeVersion {
|
||||
self.runtime_version()
|
||||
}
|
||||
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
self.client_state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OnlineClientT<T> for OnlineClient<T> {
|
||||
@@ -521,7 +536,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
|
||||
@@ -536,7 +551,7 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
|
||||
|
||||
// We are waiting for the chain to have the same spec version
|
||||
// as sent out via the runtime subscription.
|
||||
if spec_version == runtime_version.spec_version {
|
||||
if spec_version == runtime_version.spec_version() {
|
||||
break block_ref;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>) {}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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, metadata::DecodeWithMetadata};
|
||||
use derivative::Derivative;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
@@ -3,17 +3,11 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::ConstantAddress;
|
||||
use crate::{
|
||||
client::OfflineClientT,
|
||||
error::{Error, MetadataError},
|
||||
metadata::DecodeWithMetadata,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use crate::{client::OfflineClientT, error::Error, Config};
|
||||
use derive_where::derive_where;
|
||||
|
||||
/// A client for accessing constants.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct ConstantsClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
@@ -35,20 +29,8 @@ 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());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::validate_constant(&metadata, address).map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Access the constant at the address given, returning the type defined by this address.
|
||||
@@ -59,22 +41,6 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
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,
|
||||
)?;
|
||||
Ok(value)
|
||||
subxt_core::constants::get_constant(&metadata, address).map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,80 +0,0 @@
|
||||
use derivative::Derivative;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::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
|
||||
/// custom values from the metadata.
|
||||
/// The trait is implemented by [str] for dynamic loopup and [StaticAddress] for static queries.
|
||||
pub trait CustomValueAddress {
|
||||
/// The type of the custom value.
|
||||
type Target: DecodeWithMetadata;
|
||||
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
|
||||
/// Should be `()` for custom values, that have an invalid type id.
|
||||
type IsDecodable;
|
||||
|
||||
/// the name (key) by which the custom value can be accessed in the metadata.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// An optional hash which, if present, can be checked against node metadata.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValueAddress for str {
|
||||
type Target = DecodedValueThunk;
|
||||
type IsDecodable = Yes;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to signal whether a [`CustomValueAddress`] can be decoded.
|
||||
pub struct Yes;
|
||||
|
||||
/// A static address to a custom value.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""), Debug(bound = ""))]
|
||||
pub struct StaticAddress<ReturnTy, IsDecodable> {
|
||||
name: &'static str,
|
||||
hash: Option<[u8; 32]>,
|
||||
phantom: PhantomData<(ReturnTy, IsDecodable)>,
|
||||
}
|
||||
|
||||
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
|
||||
#[doc(hidden)]
|
||||
/// Creates a new StaticAddress.
|
||||
pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress<ReturnTy, IsDecodable> {
|
||||
StaticAddress::<ReturnTy, IsDecodable> {
|
||||
name,
|
||||
hash: Some(hash),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this custom value prior to accessing it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
name: self.name,
|
||||
hash: None,
|
||||
phantom: self.phantom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: DecodeWithMetadata, Y> CustomValueAddress for StaticAddress<R, Y> {
|
||||
type Target = R;
|
||||
type IsDecodable = Y;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::client::OfflineClientT;
|
||||
use crate::custom_values::custom_value_address::{CustomValueAddress, Yes};
|
||||
use crate::error::MetadataError;
|
||||
use crate::metadata::DecodeWithMetadata;
|
||||
use crate::{Config, Error};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
|
||||
use subxt_core::custom_values::{
|
||||
get_custom_value, get_custom_value_bytes, validate_custom_value, CustomValueAddress,
|
||||
};
|
||||
use subxt_core::utils::Yes;
|
||||
|
||||
/// A client for accessing custom values stored in the metadata.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct CustomValuesClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
@@ -30,22 +31,7 @@ impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Address::Target, Error> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
self.validate(address)?;
|
||||
|
||||
// 2. Attempt to decode custom value:
|
||||
let metadata = self.client.metadata();
|
||||
let custom = metadata.custom();
|
||||
let custom_value = custom
|
||||
.get(address.name())
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().to_string()))?;
|
||||
|
||||
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut custom_value.bytes(),
|
||||
custom_value.type_id(),
|
||||
&metadata,
|
||||
)?;
|
||||
Ok(value)
|
||||
get_custom_value(&self.client.metadata(), address).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
@@ -53,17 +39,7 @@ impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
self.validate(address)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let metadata = self.client.metadata();
|
||||
let custom = metadata.custom();
|
||||
let custom_value = custom
|
||||
.get(address.name())
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().to_string()))?;
|
||||
|
||||
Ok(custom_value.bytes().to_vec())
|
||||
get_custom_value_bytes(&self.client.metadata(), address).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
|
||||
@@ -73,27 +49,12 @@ impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<(), Error> {
|
||||
let metadata = self.client.metadata();
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let custom = metadata.custom();
|
||||
let custom_value = custom
|
||||
.get(address.name())
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?;
|
||||
let expected_hash = custom_value.hash();
|
||||
if actual_hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
}
|
||||
if metadata.custom().get(address.name()).is_none() {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
validate_custom_value(&self.client.metadata(), address).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::backend::RuntimeVersion;
|
||||
use crate::custom_values::CustomValuesClient;
|
||||
use crate::{Metadata, OfflineClient, SubstrateConfig};
|
||||
use codec::Encode;
|
||||
@@ -101,6 +62,7 @@ mod tests {
|
||||
use scale_info::form::PortableForm;
|
||||
use scale_info::TypeInfo;
|
||||
use std::collections::BTreeMap;
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
|
||||
pub struct Person {
|
||||
@@ -158,10 +120,7 @@ mod tests {
|
||||
fn test_decoding() {
|
||||
let client = OfflineClient::<SubstrateConfig>::new(
|
||||
Default::default(),
|
||||
RuntimeVersion {
|
||||
spec_version: 0,
|
||||
transaction_version: 0,
|
||||
},
|
||||
RuntimeVersion::new(0, 0),
|
||||
mock_metadata(),
|
||||
);
|
||||
let custom_value_client = CustomValuesClient::new(client);
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
//! Types associated with accessing custom types
|
||||
|
||||
mod custom_value_address;
|
||||
mod custom_values_client;
|
||||
|
||||
pub use custom_value_address::{CustomValueAddress, StaticAddress, Yes};
|
||||
pub use custom_values_client::CustomValuesClient;
|
||||
pub use subxt_core::custom_values::{CustomValueAddress, StaticAddress};
|
||||
|
||||
@@ -1,86 +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 the entry points to create dynamic
|
||||
//! transactions, storage and constant lookups.
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
metadata::{DecodeWithMetadata, Metadata},
|
||||
};
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
pub use scale_value::{At, Value};
|
||||
|
||||
/// A [`scale_value::Value`] type endowed with contextual information
|
||||
/// regarding what type was used to decode each part of it. This implements
|
||||
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
|
||||
/// for dynamic requests.
|
||||
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
|
||||
|
||||
// Submit dynamic transactions.
|
||||
pub use crate::tx::dynamic as tx;
|
||||
|
||||
// Lookup constants dynamically.
|
||||
pub use crate::constants::dynamic as constant;
|
||||
|
||||
// Lookup storage values dynamically.
|
||||
pub use crate::storage::dynamic as storage;
|
||||
|
||||
// Execute runtime API function call dynamically.
|
||||
pub use crate::runtime_api::dynamic as runtime_api_call;
|
||||
|
||||
/// This is the result of making a dynamic request to a node. From this,
|
||||
/// we can return the raw SCALE bytes that we were handed back, or we can
|
||||
/// complete the decoding of the bytes into a [`DecodedValue`] type.
|
||||
pub struct DecodedValueThunk {
|
||||
type_id: u32,
|
||||
metadata: Metadata,
|
||||
scale_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DecodeWithMetadata for DecodedValueThunk {
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, Error> {
|
||||
let mut v = Vec::with_capacity(bytes.len());
|
||||
v.extend_from_slice(bytes);
|
||||
*bytes = &[];
|
||||
Ok(DecodedValueThunk {
|
||||
type_id,
|
||||
metadata: metadata.clone(),
|
||||
scale_bytes: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodedValueThunk {
|
||||
/// Return the SCALE encoded bytes handed back from the node.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
self.scale_bytes
|
||||
}
|
||||
/// Return the SCALE encoded bytes handed back from the node without taking ownership of them.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.scale_bytes
|
||||
}
|
||||
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
|
||||
pub fn to_value(&self) -> Result<DecodedValue, Error> {
|
||||
let val = DecodedValue::decode_as_type(
|
||||
&mut &*self.scale_bytes,
|
||||
self.type_id,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
Ok(val)
|
||||
}
|
||||
/// decode the `DecodedValueThunk` into a concrete type.
|
||||
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
|
||||
T::decode_as_type(
|
||||
&mut &self.scale_bytes[..],
|
||||
self.type_id,
|
||||
self.metadata.types(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use core::fmt::Debug;
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType};
|
||||
use std::borrow::Cow;
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, TypeResolver};
|
||||
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
use super::{Error, MetadataError};
|
||||
|
||||
@@ -59,6 +60,9 @@ pub enum DispatchError {
|
||||
"Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
|
||||
)]
|
||||
Unavailable,
|
||||
/// Root origin is not allowed.
|
||||
#[error("Root origin is not allowed.")]
|
||||
RootNotAllowed,
|
||||
}
|
||||
|
||||
/// An error relating to tokens when dispatching a transaction.
|
||||
@@ -92,6 +96,9 @@ pub enum TokenError {
|
||||
/// Withdrawal would cause unwanted loss of account.
|
||||
#[error("Withdrawal would cause unwanted loss of account.")]
|
||||
NotExpendable,
|
||||
/// Account cannot receive the assets.
|
||||
#[error("Account cannot receive the assets.")]
|
||||
Blocked,
|
||||
}
|
||||
|
||||
/// An error relating to arithmetic when dispatching a transaction.
|
||||
@@ -203,7 +210,7 @@ impl ModuleError {
|
||||
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &self.bytes[..],
|
||||
self.metadata.outer_enums().error_enum_ty(),
|
||||
&self.metadata.outer_enums().error_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
|
||||
@@ -249,30 +256,34 @@ impl DispatchError {
|
||||
Exhausted,
|
||||
Corruption,
|
||||
Unavailable,
|
||||
RootNotAllowed,
|
||||
}
|
||||
|
||||
// ModuleError is a bit special; we want to support being decoded from either
|
||||
// a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes
|
||||
// out when decoding to manually work with them.
|
||||
struct DecodedModuleErrorBytes(Vec<u8>);
|
||||
struct DecodedModuleErrorBytesVisitor;
|
||||
impl scale_decode::Visitor for DecodedModuleErrorBytesVisitor {
|
||||
struct DecodedModuleErrorBytesVisitor<R: TypeResolver>(PhantomData<R>);
|
||||
impl<R: TypeResolver> scale_decode::Visitor for DecodedModuleErrorBytesVisitor<R> {
|
||||
type Error = scale_decode::Error;
|
||||
type Value<'scale, 'info> = DecodedModuleErrorBytes;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
_type_id: scale_decode::visitor::TypeId,
|
||||
_types: &'info scale_info::PortableRegistry,
|
||||
_type_id: &R::TypeId,
|
||||
_types: &'info R,
|
||||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>>
|
||||
{
|
||||
DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec())))
|
||||
}
|
||||
}
|
||||
|
||||
impl scale_decode::IntoVisitor for DecodedModuleErrorBytes {
|
||||
type Visitor = DecodedModuleErrorBytesVisitor;
|
||||
fn into_visitor() -> Self::Visitor {
|
||||
DecodedModuleErrorBytesVisitor
|
||||
type AnyVisitor<R: TypeResolver> = DecodedModuleErrorBytesVisitor<R>;
|
||||
fn into_visitor<R: TypeResolver>() -> DecodedModuleErrorBytesVisitor<R> {
|
||||
DecodedModuleErrorBytesVisitor(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +309,7 @@ impl DispatchError {
|
||||
DecodedDispatchError::Exhausted => DispatchError::Exhausted,
|
||||
DecodedDispatchError::Corruption => DispatchError::Corruption,
|
||||
DecodedDispatchError::Unavailable => DispatchError::Unavailable,
|
||||
DecodedDispatchError::RootNotAllowed => DispatchError::RootNotAllowed,
|
||||
// But we apply custom logic to transform the module error into the outward facing version:
|
||||
DecodedDispatchError::Module(module_bytes) => {
|
||||
let module_bytes = module_bytes.0;
|
||||
|
||||
+31
-80
@@ -6,10 +6,8 @@
|
||||
|
||||
mod dispatch_error;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
crate::macros::cfg_unstable_light_client! {
|
||||
pub use crate::client::LightClientError;
|
||||
pub use subxt_lightclient::LightClientError;
|
||||
}
|
||||
|
||||
// Re-export dispatch error types:
|
||||
@@ -18,10 +16,10 @@ pub use dispatch_error::{
|
||||
};
|
||||
|
||||
// Re-expose the errors we use from other crates here:
|
||||
pub use crate::config::ExtrinsicParamsError;
|
||||
pub use crate::metadata::Metadata;
|
||||
pub use scale_decode::Error as DecodeError;
|
||||
pub use scale_encode::Error as EncodeError;
|
||||
pub use subxt_core::error::{ExtrinsicParamsError, MetadataError, StorageAddressError};
|
||||
pub use subxt_metadata::TryFromError as MetadataTryFromError;
|
||||
|
||||
/// The underlying error enum, generic over the type held by the `Runtime`
|
||||
@@ -82,6 +80,19 @@ pub enum Error {
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<subxt_core::Error> for Error {
|
||||
fn from(value: subxt_core::Error) -> Self {
|
||||
match value {
|
||||
subxt_core::Error::Codec(e) => Error::Codec(e),
|
||||
subxt_core::Error::Metadata(e) => Error::Metadata(e),
|
||||
subxt_core::Error::StorageAddress(e) => Error::StorageAddress(e),
|
||||
subxt_core::Error::Decode(e) => Error::Decode(e),
|
||||
subxt_core::Error::Encode(e) => Error::Encode(e),
|
||||
subxt_core::Error::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Error {
|
||||
fn from(error: &'a str) -> Self {
|
||||
Error::Other(error.into())
|
||||
@@ -100,6 +111,19 @@ impl From<std::convert::Infallible> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<scale_decode::visitor::DecodeError> for Error {
|
||||
fn from(value: scale_decode::visitor::DecodeError) -> Self {
|
||||
Error::Decode(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Checks whether the error was caused by a RPC re-connection.
|
||||
pub fn is_disconnected_will_reconnect(&self) -> bool {
|
||||
matches!(self, Error::Rpc(RpcError::DisconnectedWillReconnect(_)))
|
||||
}
|
||||
}
|
||||
|
||||
/// An RPC error. Since we are generic over the RPC client that is used,
|
||||
/// the error is boxed and could be casted.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -120,6 +144,9 @@ pub enum RpcError {
|
||||
/// The requested URL is insecure.
|
||||
#[error("RPC error: insecure URL: {0}")]
|
||||
InsecureUrl(String),
|
||||
/// The connection was lost and automatically reconnected.
|
||||
#[error("RPC error: the connection was lost `{0}`; reconnect automatically initiated")]
|
||||
DisconnectedWillReconnect(String),
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
@@ -174,79 +201,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,
|
||||
},
|
||||
}
|
||||
|
||||
/// Something went wrong trying to access details in the metadata.
|
||||
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum MetadataError {
|
||||
/// The DispatchError type isn't available in the metadata
|
||||
#[error("The DispatchError type isn't available")]
|
||||
DispatchErrorNotFound,
|
||||
/// Type not found in metadata.
|
||||
#[error("Type with ID {0} not found")]
|
||||
TypeNotFound(u32),
|
||||
/// Pallet not found (index).
|
||||
#[error("Pallet with index {0} not found")]
|
||||
PalletIndexNotFound(u8),
|
||||
/// Pallet not found (name).
|
||||
#[error("Pallet with name {0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
/// Variant not found.
|
||||
#[error("Variant with index {0} not found")]
|
||||
VariantIndexNotFound(u8),
|
||||
/// Constant not found.
|
||||
#[error("Constant with name {0} not found")]
|
||||
ConstantNameNotFound(String),
|
||||
/// Call not found.
|
||||
#[error("Call with name {0} not found")]
|
||||
CallNameNotFound(String),
|
||||
/// Runtime trait not found.
|
||||
#[error("Runtime trait with name {0} not found")]
|
||||
RuntimeTraitNotFound(String),
|
||||
/// Runtime method not found.
|
||||
#[error("Runtime method with name {0} not found")]
|
||||
RuntimeMethodNotFound(String),
|
||||
/// Call type not found in metadata.
|
||||
#[error("Call type not found in pallet with index {0}")]
|
||||
CallTypeNotFoundInPallet(u8),
|
||||
/// Event type not found in metadata.
|
||||
#[error("Event type not found in pallet with index {0}")]
|
||||
EventTypeNotFoundInPallet(u8),
|
||||
/// Storage details not found in metadata.
|
||||
#[error("Storage details not found in pallet with name {0}")]
|
||||
StorageNotFoundInPallet(String),
|
||||
/// Storage entry not found.
|
||||
#[error("Storage entry {0} not found")]
|
||||
StorageEntryNotFound(String),
|
||||
/// The generated interface used is not compatible with the node.
|
||||
#[error("The generated code is not compatible with the node")]
|
||||
IncompatibleCodegen,
|
||||
/// Custom value not found.
|
||||
#[error("Custom value with name {0} not found")]
|
||||
CustomValueNameNotFound(String),
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
|
||||
use crate::backend::{Backend, BackendExt, BlockRef};
|
||||
use crate::{client::OnlineClientT, error::Error, events::Events, Config};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use std::future::Future;
|
||||
|
||||
/// A client for working with events.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct EventsClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
@@ -65,19 +64,15 @@ where
|
||||
};
|
||||
|
||||
let event_bytes = get_event_bytes(client.backend(), block_ref.hash()).await?;
|
||||
Ok(Events::new(
|
||||
client.metadata(),
|
||||
block_ref.hash(),
|
||||
event_bytes,
|
||||
))
|
||||
Ok(Events::decode_from(client.metadata(), event_bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The storage key needed to access events.
|
||||
fn system_events_key() -> [u8; 32] {
|
||||
let a = sp_core_hashing::twox_128(b"System");
|
||||
let b = sp_core_hashing::twox_128(b"Events");
|
||||
let a = sp_crypto_hashing::twox_128(b"System");
|
||||
let b = sp_crypto_hashing::twox_128(b"Events");
|
||||
let mut res = [0; 32];
|
||||
res[0..16].clone_from_slice(&a);
|
||||
res[16..32].clone_from_slice(&b);
|
||||
|
||||
@@ -1,946 +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.
|
||||
|
||||
//! A representation of a block of events.
|
||||
|
||||
use super::{Phase, StaticEvent};
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
error::{Error, MetadataError},
|
||||
events::events_client::get_event_bytes,
|
||||
metadata::types::PalletMetadata,
|
||||
Config, Metadata,
|
||||
};
|
||||
use codec::{Compact, Decode};
|
||||
use derivative::Derivative;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A collection of events obtained from a block, bundled with the necessary
|
||||
/// information needed to decode and iterate over them.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct Events<T: Config> {
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
// Note; raw event bytes are prefixed with a Compact<u32> containing
|
||||
// the number of events to be decoded. The start_idx reflects that, so
|
||||
// that we can skip over those bytes when decoding them
|
||||
event_bytes: Arc<[u8]>,
|
||||
start_idx: usize,
|
||||
num_events: u32,
|
||||
}
|
||||
|
||||
// Ignore the Metadata when debug-logging events; it's big and distracting.
|
||||
impl<T: Config> std::fmt::Debug for Events<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Events")
|
||||
.field("block_hash", &self.block_hash)
|
||||
.field("event_bytes", &self.event_bytes)
|
||||
.field("start_idx", &self.start_idx)
|
||||
.field("num_events", &self.num_events)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Events<T> {
|
||||
pub(crate) fn new(metadata: Metadata, block_hash: T::Hash, event_bytes: Vec<u8>) -> Self {
|
||||
// event_bytes is a SCALE encoded vector of events. So, pluck the
|
||||
// compact encoded length from the front, leaving the remaining bytes
|
||||
// for our iterating to decode.
|
||||
//
|
||||
// Note: if we get no bytes back, avoid an error reading vec length
|
||||
// and default to 0 events.
|
||||
let cursor = &mut &*event_bytes;
|
||||
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
|
||||
|
||||
// Start decoding after the compact encoded bytes.
|
||||
let start_idx = event_bytes.len() - cursor.len();
|
||||
|
||||
Self {
|
||||
metadata,
|
||||
block_hash,
|
||||
event_bytes: event_bytes.into(),
|
||||
start_idx,
|
||||
num_events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the events from a block hash given custom metadata and a client.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// - Prefer to use [`crate::events::EventsClient::at`] to obtain the events.
|
||||
/// - Subxt may fail to decode things that aren't from a runtime using the
|
||||
/// latest metadata version.
|
||||
/// - The client may not be able to obtain the block at the given hash. Only
|
||||
/// archive nodes keep hold of all past block information.
|
||||
pub async fn new_from_client<Client>(
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
client: Client,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
let event_bytes = get_event_bytes(client.backend(), block_hash).await?;
|
||||
Ok(Events::new(metadata, block_hash, event_bytes))
|
||||
}
|
||||
|
||||
/// The number of events.
|
||||
pub fn len(&self) -> u32 {
|
||||
self.num_events
|
||||
}
|
||||
|
||||
/// Are there no events in this block?
|
||||
// Note: mainly here to satisfy clippy.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.num_events == 0
|
||||
}
|
||||
|
||||
/// Return the block hash that these events are from.
|
||||
pub fn block_hash(&self) -> T::Hash {
|
||||
self.block_hash
|
||||
}
|
||||
|
||||
/// Iterate over all of the events, using metadata to dynamically
|
||||
/// decode them as we go, and returning the raw bytes and other associated
|
||||
/// details. If an error occurs, all subsequent iterations return `None`.
|
||||
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
|
||||
// use of it with our `FilterEvents` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
|
||||
// The event bytes ignoring the compact encoded length on the front:
|
||||
let event_bytes = self.event_bytes.clone();
|
||||
let metadata = self.metadata.clone();
|
||||
let num_events = self.num_events;
|
||||
|
||||
let mut pos = self.start_idx;
|
||||
let mut index = 0;
|
||||
std::iter::from_fn(move || {
|
||||
if event_bytes.len() <= pos || num_events == index {
|
||||
None
|
||||
} else {
|
||||
match EventDetails::decode_from(metadata.clone(), event_bytes.clone(), pos, index) {
|
||||
Ok(event_details) => {
|
||||
// Skip over decoded bytes in next iteration:
|
||||
pos += event_details.bytes().len();
|
||||
// Increment the index:
|
||||
index += 1;
|
||||
// Return the event details:
|
||||
Some(Ok(event_details))
|
||||
}
|
||||
Err(e) => {
|
||||
// By setting the position to the "end" of the event bytes,
|
||||
// the cursor len will become 0 and the iterator will return `None`
|
||||
// from now on:
|
||||
pos = event_bytes.len();
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return only those which should decode to the provided `Ev` type.
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
|
||||
self.iter().filter_map(|ev| {
|
||||
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
|
||||
.transpose()
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the last event found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an event that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
/// The event details.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventDetails<T: Config> {
|
||||
phase: Phase,
|
||||
/// The index of the event in the list of events in a given block.
|
||||
index: u32,
|
||||
all_bytes: Arc<[u8]>,
|
||||
// start of the bytes (phase, pallet/variant index and then fields and then topic to follow).
|
||||
start_idx: usize,
|
||||
// start of the event (ie pallet/variant index and then the fields and topic after).
|
||||
event_start_idx: usize,
|
||||
// start of the fields (ie after phase and pallet/variant index).
|
||||
event_fields_start_idx: usize,
|
||||
// end of the fields.
|
||||
event_fields_end_idx: usize,
|
||||
// end of everything (fields + topics)
|
||||
end_idx: usize,
|
||||
metadata: Metadata,
|
||||
topics: Vec<T::Hash>,
|
||||
}
|
||||
|
||||
impl<T: Config> EventDetails<T> {
|
||||
// Attempt to dynamically decode a single event from our events input.
|
||||
fn decode_from(
|
||||
metadata: Metadata,
|
||||
all_bytes: Arc<[u8]>,
|
||||
start_idx: usize,
|
||||
index: u32,
|
||||
) -> Result<EventDetails<T>, Error> {
|
||||
let input = &mut &all_bytes[start_idx..];
|
||||
|
||||
let phase = Phase::decode(input)?;
|
||||
|
||||
let event_start_idx = all_bytes.len() - input.len();
|
||||
|
||||
let pallet_index = u8::decode(input)?;
|
||||
let variant_index = u8::decode(input)?;
|
||||
|
||||
let event_fields_start_idx = all_bytes.len() - input.len();
|
||||
|
||||
// Get metadata for the event:
|
||||
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
|
||||
let event_variant = event_pallet
|
||||
.event_variant_by_index(variant_index)
|
||||
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
|
||||
tracing::debug!(
|
||||
"Decoding Event '{}::{}'",
|
||||
event_pallet.name(),
|
||||
&event_variant.name
|
||||
);
|
||||
|
||||
// Skip over the bytes belonging to this event.
|
||||
for field_metadata in &event_variant.fields {
|
||||
// Skip over the bytes for this field:
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
input,
|
||||
field_metadata.ty.id,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor,
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
}
|
||||
|
||||
// the end of the field bytes.
|
||||
let event_fields_end_idx = all_bytes.len() - input.len();
|
||||
|
||||
// topics come after the event data in EventRecord.
|
||||
let topics = Vec::<T::Hash>::decode(input)?;
|
||||
|
||||
// what bytes did we skip over in total, including topics.
|
||||
let end_idx = all_bytes.len() - input.len();
|
||||
|
||||
Ok(EventDetails {
|
||||
phase,
|
||||
index,
|
||||
start_idx,
|
||||
event_start_idx,
|
||||
event_fields_start_idx,
|
||||
event_fields_end_idx,
|
||||
end_idx,
|
||||
all_bytes,
|
||||
metadata,
|
||||
topics,
|
||||
})
|
||||
}
|
||||
|
||||
/// When was the event produced?
|
||||
pub fn phase(&self) -> Phase {
|
||||
self.phase
|
||||
}
|
||||
|
||||
/// What index is this event in the stored events for this block.
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// The index of the pallet that the event originated from.
|
||||
pub fn pallet_index(&self) -> u8 {
|
||||
// Note: never panics; we expect these bytes to exist
|
||||
// in order that the EventDetails could be created.
|
||||
self.all_bytes[self.event_fields_start_idx - 2]
|
||||
}
|
||||
|
||||
/// The index of the event variant that the event originated from.
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
// Note: never panics; we expect these bytes to exist
|
||||
// in order that the EventDetails could be created.
|
||||
self.all_bytes[self.event_fields_start_idx - 1]
|
||||
}
|
||||
|
||||
/// The name of the pallet from whence the Event originated.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.event_metadata().pallet.name()
|
||||
}
|
||||
|
||||
/// The name of the event (ie the name of the variant that it corresponds to).
|
||||
pub fn variant_name(&self) -> &str {
|
||||
&self.event_metadata().variant.name
|
||||
}
|
||||
|
||||
/// Fetch details from the metadata for this event.
|
||||
pub fn event_metadata(&self) -> EventMetadataDetails {
|
||||
let pallet = self
|
||||
.metadata
|
||||
.pallet_by_index(self.pallet_index())
|
||||
.expect("event pallet to be found; we did this already during decoding");
|
||||
let variant = pallet
|
||||
.event_variant_by_index(self.variant_index())
|
||||
.expect("event variant to be found; we did this already during decoding");
|
||||
|
||||
EventMetadataDetails { pallet, variant }
|
||||
}
|
||||
|
||||
/// Return _all_ of the bytes representing this event, which include, in order:
|
||||
/// - The phase.
|
||||
/// - Pallet and event index.
|
||||
/// - Event fields.
|
||||
/// - Event Topics.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.all_bytes[self.start_idx..self.end_idx]
|
||||
}
|
||||
|
||||
/// Return the bytes representing the fields stored in this event.
|
||||
pub fn field_bytes(&self) -> &[u8] {
|
||||
&self.all_bytes[self.event_fields_start_idx..self.event_fields_end_idx]
|
||||
}
|
||||
|
||||
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the event.
|
||||
pub fn field_values(
|
||||
&self,
|
||||
) -> Result<scale_value::Composite<scale_value::scale::TypeId>, Error> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let event_metadata = self.event_metadata();
|
||||
|
||||
let mut fields = event_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
|
||||
use scale_decode::DecodeAsFields;
|
||||
let decoded = <scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
|
||||
bytes,
|
||||
&mut fields,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
|
||||
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
|
||||
let ev_metadata = self.event_metadata();
|
||||
if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT {
|
||||
let mut fields = ev_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
|
||||
Ok(Some(decoded))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
|
||||
/// the pallet and event enum variants as well as the event fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Event` type.
|
||||
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
|
||||
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &bytes[..],
|
||||
self.metadata.outer_enums().event_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Return the topics associated with this event.
|
||||
pub fn topics(&self) -> &[T::Hash] {
|
||||
&self.topics
|
||||
}
|
||||
}
|
||||
|
||||
/// Details for the given event plucked from the metadata.
|
||||
pub struct EventMetadataDetails<'a> {
|
||||
pub pallet: PalletMetadata<'a>,
|
||||
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
|
||||
/// Event related test utilities used outside this module.
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_utils {
|
||||
use super::*;
|
||||
use crate::{Config, SubstrateConfig};
|
||||
use codec::Encode;
|
||||
use frame_metadata::{
|
||||
v15::{
|
||||
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletEventMetadata, PalletMetadata,
|
||||
RuntimeMetadataV15,
|
||||
},
|
||||
RuntimeMetadataPrefixed,
|
||||
};
|
||||
use scale_info::{meta_type, TypeInfo};
|
||||
|
||||
/// An "outer" events enum containing exactly one event.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
)]
|
||||
pub enum AllEvents<Ev> {
|
||||
Test(Ev),
|
||||
}
|
||||
|
||||
/// This encodes to the same format an event is expected to encode to
|
||||
/// in node System.Events storage.
|
||||
#[derive(Encode)]
|
||||
pub struct EventRecord<E: Encode> {
|
||||
phase: Phase,
|
||||
event: AllEvents<E>,
|
||||
topics: Vec<<SubstrateConfig as Config>::Hash>,
|
||||
}
|
||||
|
||||
impl<E: Encode> EventRecord<E> {
|
||||
/// Create a new event record with the given phase, event, and topics.
|
||||
pub fn new(phase: Phase, event: E, topics: Vec<<SubstrateConfig as Config>::Hash>) -> Self {
|
||||
Self {
|
||||
phase,
|
||||
event: AllEvents::Test(event),
|
||||
topics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an EventRecord, which encoded events in the format expected
|
||||
/// to be handed back from storage queries to System.Events.
|
||||
pub fn event_record<E: Encode>(phase: Phase, event: E) -> EventRecord<E> {
|
||||
EventRecord::new(phase, event, vec![])
|
||||
}
|
||||
|
||||
/// Build fake metadata consisting of a single pallet that knows
|
||||
/// about the event type provided.
|
||||
pub fn metadata<E: TypeInfo + 'static>() -> Metadata {
|
||||
// Extrinsic needs to contain at least the generic type parameter "Call"
|
||||
// for the metadata to be valid.
|
||||
// The "Call" type from the metadata is used to decode extrinsics.
|
||||
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicType<Call> {
|
||||
call: Call,
|
||||
}
|
||||
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
|
||||
// Each pallet must contain one single variant.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
enum RuntimeCall {
|
||||
PalletName(Pallet),
|
||||
}
|
||||
// The calls of the pallet.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
enum Pallet {
|
||||
#[allow(unused)]
|
||||
SomeCall,
|
||||
}
|
||||
|
||||
let pallets = vec![PalletMetadata {
|
||||
name: "Test",
|
||||
storage: None,
|
||||
calls: None,
|
||||
event: Some(PalletEventMetadata {
|
||||
ty: meta_type::<E>(),
|
||||
}),
|
||||
constants: vec![],
|
||||
error: None,
|
||||
index: 0,
|
||||
docs: vec![],
|
||||
}];
|
||||
|
||||
let extrinsic = ExtrinsicMetadata {
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
address_ty: meta_type::<()>(),
|
||||
call_ty: meta_type::<RuntimeCall>(),
|
||||
signature_ty: meta_type::<()>(),
|
||||
extra_ty: meta_type::<()>(),
|
||||
};
|
||||
|
||||
let meta = RuntimeMetadataV15::new(
|
||||
pallets,
|
||||
extrinsic,
|
||||
meta_type::<()>(),
|
||||
vec![],
|
||||
OuterEnums {
|
||||
call_enum_ty: meta_type::<()>(),
|
||||
event_enum_ty: meta_type::<AllEvents<E>>(),
|
||||
error_enum_ty: meta_type::<()>(),
|
||||
},
|
||||
CustomMetadata {
|
||||
map: Default::default(),
|
||||
},
|
||||
);
|
||||
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
|
||||
|
||||
Metadata::new(runtime_metadata.try_into().unwrap())
|
||||
}
|
||||
|
||||
/// Build an `Events` object for test purposes, based on the details provided,
|
||||
/// and with a default block hash.
|
||||
pub fn events<E: Decode + Encode>(
|
||||
metadata: Metadata,
|
||||
event_records: Vec<EventRecord<E>>,
|
||||
) -> Events<SubstrateConfig> {
|
||||
let num_events = event_records.len() as u32;
|
||||
let mut event_bytes = Vec::new();
|
||||
for ev in event_records {
|
||||
ev.encode_to(&mut event_bytes);
|
||||
}
|
||||
events_raw(metadata, event_bytes, num_events)
|
||||
}
|
||||
|
||||
/// Much like [`events`], but takes pre-encoded events and event count, so that we can
|
||||
/// mess with the bytes in tests if we need to.
|
||||
pub fn events_raw(
|
||||
metadata: Metadata,
|
||||
event_bytes: Vec<u8>,
|
||||
num_events: u32,
|
||||
) -> Events<SubstrateConfig> {
|
||||
// Prepend compact encoded length to event bytes:
|
||||
let mut all_event_bytes = Compact(num_events).encode();
|
||||
all_event_bytes.extend(event_bytes);
|
||||
Events::new(
|
||||
metadata,
|
||||
<SubstrateConfig as Config>::Hash::default(),
|
||||
all_event_bytes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
test_utils::{event_record, events, events_raw, AllEvents, EventRecord},
|
||||
*,
|
||||
};
|
||||
use crate::SubstrateConfig;
|
||||
use codec::Encode;
|
||||
use primitive_types::H256;
|
||||
use scale_info::TypeInfo;
|
||||
use scale_value::Value;
|
||||
|
||||
/// Build a fake wrapped metadata.
|
||||
fn metadata<E: TypeInfo + 'static>() -> Metadata {
|
||||
test_utils::metadata::<E>()
|
||||
}
|
||||
|
||||
/// [`RawEventDetails`] can be annoying to test, because it contains
|
||||
/// type info in the decoded field Values. Strip that here so that
|
||||
/// we can compare fields more easily.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TestRawEventDetails {
|
||||
pub phase: Phase,
|
||||
pub index: u32,
|
||||
pub pallet: String,
|
||||
pub pallet_index: u8,
|
||||
pub variant: String,
|
||||
pub variant_index: u8,
|
||||
pub fields: Vec<scale_value::Value>,
|
||||
}
|
||||
|
||||
/// Compare some actual [`RawEventDetails`] with a hand-constructed
|
||||
/// (probably) [`TestRawEventDetails`].
|
||||
pub fn assert_raw_events_match(
|
||||
actual: EventDetails<SubstrateConfig>,
|
||||
expected: TestRawEventDetails,
|
||||
) {
|
||||
let actual_fields_no_context: Vec<_> = actual
|
||||
.field_values()
|
||||
.expect("can decode field values (2)")
|
||||
.into_values()
|
||||
.map(|value| value.remove_context())
|
||||
.collect();
|
||||
|
||||
// Check each of the other fields:
|
||||
assert_eq!(actual.phase(), expected.phase);
|
||||
assert_eq!(actual.index(), expected.index);
|
||||
assert_eq!(actual.pallet_name(), expected.pallet);
|
||||
assert_eq!(actual.pallet_index(), expected.pallet_index);
|
||||
assert_eq!(actual.variant_name(), expected.variant);
|
||||
assert_eq!(actual.variant_index(), expected.variant_index);
|
||||
assert_eq!(actual_fields_no_context, expected.fields);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_single_root_event() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType)]
|
||||
enum Event {
|
||||
A(u8, bool, Vec<String>),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let event = Event::A(1, true, vec!["Hi".into()]);
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(Phase::ApplyExtrinsic(123), event.clone())],
|
||||
);
|
||||
|
||||
let ev = events
|
||||
.iter()
|
||||
.next()
|
||||
.expect("one event expected")
|
||||
.expect("event should be extracted OK");
|
||||
|
||||
// This is the line we're testing:
|
||||
let decoded_event = ev
|
||||
.as_root_event::<AllEvents<Event>>()
|
||||
.expect("can decode event into root enum again");
|
||||
|
||||
// It should equal the event we put in:
|
||||
assert_eq!(decoded_event, AllEvents::Test(event));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamically_decode_single_event() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8, bool, Vec<String>),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let event = Event::A(1, true, vec!["Hi".into()]);
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(Phase::ApplyExtrinsic(123), event)],
|
||||
);
|
||||
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
index: 0,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![
|
||||
Value::u128(1),
|
||||
Value::bool(true),
|
||||
Value::unnamed_composite(vec![Value::string("Hi")]),
|
||||
],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamically_decode_multiple_events() {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
B(bool),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let event1 = Event::A(1);
|
||||
let event2 = Event::B(true);
|
||||
let event3 = Event::A(234);
|
||||
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![
|
||||
event_record(Phase::Initialization, event1),
|
||||
event_record(Phase::ApplyExtrinsic(123), event2),
|
||||
event_record(Phase::Finalization, event3),
|
||||
],
|
||||
);
|
||||
|
||||
let mut event_details = events.iter();
|
||||
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "B".to_string(),
|
||||
variant_index: 1,
|
||||
fields: vec![Value::bool(true)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 2,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::u128(234)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamically_decode_multiple_events_until_error() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(u8),
|
||||
B(bool),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode 2 events:
|
||||
let mut event_bytes = vec![];
|
||||
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
|
||||
event_record(Phase::ApplyExtrinsic(123), Event::B(true)).encode_to(&mut event_bytes);
|
||||
|
||||
// Push a few naff bytes to the end (a broken third event):
|
||||
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events_raw(
|
||||
metadata,
|
||||
event_bytes,
|
||||
3, // 2 "good" events, and then it'll hit the naff bytes.
|
||||
);
|
||||
|
||||
let mut events_iter = events.iter();
|
||||
assert_raw_events_match(
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Initialization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert_raw_events_match(
|
||||
events_iter.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 1,
|
||||
phase: Phase::ApplyExtrinsic(123),
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "B".to_string(),
|
||||
variant_index: 1,
|
||||
fields: vec![Value::bool(true)],
|
||||
},
|
||||
);
|
||||
|
||||
// We'll hit an error trying to decode the third event:
|
||||
assert!(events_iter.next().unwrap().is_err());
|
||||
// ... and then "None" from then on.
|
||||
assert!(events_iter.next().is_none());
|
||||
assert!(events_iter.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_event_field() {
|
||||
#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
|
||||
enum Event {
|
||||
A(#[codec(compact)] u32),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construst an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(Phase::Finalization, Event::A(1))],
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::u128(1)],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_wrapper_struct_field() {
|
||||
#[derive(Clone, Decode, Debug, PartialEq, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(#[codec(compact)] CompactWrapper),
|
||||
}
|
||||
|
||||
#[derive(Clone, Decode, Debug, PartialEq, codec::CompactAs, Encode, TypeInfo)]
|
||||
struct CompactWrapper(u64);
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(
|
||||
Phase::Finalization,
|
||||
Event::A(CompactWrapper(1)),
|
||||
)],
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_containing_explicit_index() {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Decode, Encode, TypeInfo)]
|
||||
#[repr(u8)]
|
||||
#[allow(trivial_numeric_casts, clippy::unnecessary_cast)] // required because the Encode derive produces a warning otherwise
|
||||
pub enum MyType {
|
||||
B = 10u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
|
||||
enum Event {
|
||||
A(MyType),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
|
||||
);
|
||||
|
||||
// Dynamically decode:
|
||||
let mut event_details = events.iter();
|
||||
assert_raw_events_match(
|
||||
event_details.next().unwrap().unwrap(),
|
||||
TestRawEventDetails {
|
||||
index: 0,
|
||||
phase: Phase::Finalization,
|
||||
pallet: "Test".to_string(),
|
||||
pallet_index: 0,
|
||||
variant: "A".to_string(),
|
||||
variant_index: 0,
|
||||
fields: vec![Value::unnamed_variant("B", vec![])],
|
||||
},
|
||||
);
|
||||
assert!(event_details.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn topics() {
|
||||
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType)]
|
||||
enum Event {
|
||||
A(u8, bool, Vec<String>),
|
||||
}
|
||||
|
||||
// Create fake metadata that knows about our single event, above:
|
||||
let metadata = metadata::<Event>();
|
||||
|
||||
// Encode our events in the format we expect back from a node, and
|
||||
// construct an Events object to iterate them:
|
||||
let event = Event::A(1, true, vec!["Hi".into()]);
|
||||
let topics = vec![H256::from_low_u64_le(123), H256::from_low_u64_le(456)];
|
||||
let events = events::<Event>(
|
||||
metadata,
|
||||
vec![EventRecord::new(
|
||||
Phase::ApplyExtrinsic(123),
|
||||
event,
|
||||
topics.clone(),
|
||||
)],
|
||||
);
|
||||
|
||||
let ev = events
|
||||
.iter()
|
||||
.next()
|
||||
.expect("one event expected")
|
||||
.expect("event should be extracted OK");
|
||||
|
||||
assert_eq!(topics, ev.topics());
|
||||
}
|
||||
}
|
||||
+16
-32
@@ -5,40 +5,24 @@
|
||||
//! This module exposes the types and such necessary for working with events.
|
||||
//! The two main entry points into events are [`crate::OnlineClient::events()`]
|
||||
//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()].
|
||||
use crate::client::OnlineClientT;
|
||||
use crate::Error;
|
||||
use subxt_core::{Config, Metadata};
|
||||
|
||||
mod events_client;
|
||||
mod events_type;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
pub use events_client::EventsClient;
|
||||
pub use events_type::{EventDetails, Events};
|
||||
use scale_decode::DecodeAsFields;
|
||||
pub use subxt_core::events::{EventDetails, Events, Phase, StaticEvent};
|
||||
|
||||
/// Trait to uniquely identify the events's identity from the runtime metadata.
|
||||
///
|
||||
/// Generated API structures that represent an event implement this trait.
|
||||
///
|
||||
/// The trait is utilized to decode emitted events from a block, via obtaining the
|
||||
/// form of the `Event` from the metadata.
|
||||
pub trait StaticEvent: DecodeAsFields {
|
||||
/// Pallet name.
|
||||
const PALLET: &'static str;
|
||||
/// Event name.
|
||||
const EVENT: &'static str;
|
||||
|
||||
/// Returns true if the given pallet and event names match this event.
|
||||
fn is_event(pallet: &str, event: &str) -> bool {
|
||||
Self::PALLET == pallet && Self::EVENT == event
|
||||
}
|
||||
}
|
||||
|
||||
/// A phase of a block's execution.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Decode, Encode)]
|
||||
pub enum Phase {
|
||||
/// Applying an extrinsic.
|
||||
ApplyExtrinsic(u32),
|
||||
/// Finalizing the block.
|
||||
Finalization,
|
||||
/// Initializing the block.
|
||||
Initialization,
|
||||
/// Creates a new [`Events`] instance by fetching the corresponding bytes at `block_hash` from the client.
|
||||
pub async fn new_events_from_client<T, C>(
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
client: C,
|
||||
) -> Result<Events<T>, Error>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
let event_bytes = events_client::get_event_bytes(client.backend(), block_hash).await?;
|
||||
Ok(Events::<T>::decode_from(metadata, event_bytes))
|
||||
}
|
||||
|
||||
+40
-9
@@ -45,22 +45,53 @@ 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;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod metadata;
|
||||
pub mod runtime_api;
|
||||
pub mod storage;
|
||||
pub mod tx;
|
||||
pub mod utils;
|
||||
|
||||
/// 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.
|
||||
pub mod config {
|
||||
pub use subxt_core::config::{
|
||||
polkadot, signed_extensions, substrate, BlockHash, Config, DefaultExtrinsicParams,
|
||||
DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header,
|
||||
PolkadotConfig, PolkadotExtrinsicParams, RefineParams, RefineParamsData, SignedExtension,
|
||||
SubstrateConfig, SubstrateExtrinsicParams,
|
||||
};
|
||||
pub use subxt_core::error::ExtrinsicParamsError;
|
||||
}
|
||||
|
||||
/// Types representing the metadata obtained from a node.
|
||||
pub mod metadata {
|
||||
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata, MetadataExt};
|
||||
// Expose metadata types under a sub module in case somebody needs to reference them:
|
||||
pub use subxt_metadata as types;
|
||||
}
|
||||
|
||||
/// Submit dynamic transactions.
|
||||
pub mod dynamic {
|
||||
pub use subxt_core::dynamic::{
|
||||
constant, runtime_api_call, storage, tx, At, DecodedValue, DecodedValueThunk, Value,
|
||||
};
|
||||
}
|
||||
|
||||
// Internal helper macros
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
// Expose light client bits
|
||||
cfg_unstable_light_client! {
|
||||
pub use subxt_lightclient as lightclient;
|
||||
}
|
||||
|
||||
// Expose a few of the most common types at root,
|
||||
// but leave most types behind their respective modules.
|
||||
pub use crate::{
|
||||
@@ -79,10 +110,10 @@ pub mod ext {
|
||||
pub use scale_decode;
|
||||
pub use scale_encode;
|
||||
pub use scale_value;
|
||||
pub use subxt_core;
|
||||
|
||||
cfg_substrate_compat! {
|
||||
pub use sp_runtime;
|
||||
pub use sp_core;
|
||||
pub use subxt_core::ext::{sp_runtime, sp_core};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,15 +153,15 @@ pub mod ext {
|
||||
///
|
||||
/// ## `crate = "..."`
|
||||
///
|
||||
/// Use this attribute to specify a custom path to the `subxt` crate:
|
||||
/// Use this attribute to specify a custom path to the `subxt_core` crate:
|
||||
///
|
||||
/// ```rust
|
||||
/// # pub extern crate subxt;
|
||||
/// # pub mod path { pub mod to { pub use subxt; } }
|
||||
/// # pub extern crate subxt_core;
|
||||
/// # pub mod path { pub mod to { pub use subxt_core; } }
|
||||
/// # fn main() {}
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
|
||||
/// crate = "crate::path::to::subxt"
|
||||
/// crate = "crate::path::to::subxt_core"
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
|
||||
+15
-1
@@ -52,7 +52,21 @@ macro_rules! cfg_jsonrpsee_web {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use {cfg_feature, cfg_jsonrpsee, cfg_substrate_compat, cfg_unstable_light_client};
|
||||
#[allow(unused)]
|
||||
macro_rules! cfg_reconnecting_rpc_client {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "unstable-reconnecting-rpc-client")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-reconnecting-rpc-client")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use {
|
||||
cfg_feature, cfg_jsonrpsee, cfg_reconnecting_rpc_client, cfg_substrate_compat,
|
||||
cfg_unstable_light_client,
|
||||
};
|
||||
|
||||
// Only used by light-client.
|
||||
#[allow(unused)]
|
||||
|
||||
@@ -1,51 +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::Metadata;
|
||||
use crate::error::Error;
|
||||
|
||||
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
|
||||
pub trait DecodeWithMetadata: Sized {
|
||||
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<T, Error> {
|
||||
let val = T::decode_as_type(bytes, type_id, metadata.types())?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
|
||||
pub trait EncodeWithMetadata {
|
||||
/// SCALE encode this type to bytes, possibly with the help of metadata.
|
||||
fn encode_with_metadata(
|
||||
&self,
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
bytes: &mut Vec<u8>,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
|
||||
/// SCALE encode this type to bytes, possibly with the help of metadata.
|
||||
fn encode_with_metadata(
|
||||
&self,
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
bytes: &mut Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
self.encode_as_type_to(type_id, metadata.types(), bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,74 +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::error::MetadataError;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A cheaply clone-able representation of the runtime metadata received from a node.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Metadata {
|
||||
inner: Arc<subxt_metadata::Metadata>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Metadata {
|
||||
type Target = subxt_metadata::Metadata;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub(crate) fn new(md: subxt_metadata::Metadata) -> Self {
|
||||
Metadata {
|
||||
inner: Arc::new(md),
|
||||
}
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_name(name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_index_err(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_index(index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(index))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
|
||||
pub fn runtime_api_trait_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
|
||||
self.runtime_api_trait_by_name(name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_metadata::Metadata> for Metadata {
|
||||
fn from(md: subxt_metadata::Metadata) -> Self {
|
||||
Metadata::new(md)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
|
||||
type Error = subxt_metadata::TryFromError;
|
||||
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
|
||||
subxt_metadata::Metadata::try_from(value).map(Metadata::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl codec::Decode for Metadata {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
subxt_metadata::Metadata::decode(input).map(Metadata::new)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +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.
|
||||
|
||||
//! Types representing the metadata obtained from a node.
|
||||
|
||||
mod decode_encode_traits;
|
||||
mod metadata_type;
|
||||
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
pub use metadata_type::Metadata;
|
||||
|
||||
// Expose metadata types under a sub module in case somebody needs to reference them:
|
||||
pub use subxt_metadata as types;
|
||||
@@ -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};
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
use super::runtime_types::RuntimeApi;
|
||||
|
||||
use crate::{backend::BlockRef, client::OnlineClientT, error::Error, Config};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
/// Execute runtime API calls.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct RuntimeApiClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomData<T>,
|
||||
|
||||
@@ -1,184 +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::{metadata::DecodeWithMetadata, Error, 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())
|
||||
}
|
||||
@@ -10,14 +10,13 @@ use crate::{
|
||||
Config,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
use super::RuntimeApiPayload;
|
||||
|
||||
/// Execute runtime API calls.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct RuntimeApi<T: Config, Client> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<T::Hash>,
|
||||
|
||||
@@ -4,25 +4,23 @@
|
||||
|
||||
//! 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;
|
||||
pub use storage_type::{Storage, StorageKeyValuePair};
|
||||
|
||||
/// Types representing an address which describes where a storage
|
||||
/// entry lives and how to properly decode it.
|
||||
pub mod address {
|
||||
pub use super::storage_address::{
|
||||
dynamic, make_static_storage_map_key, Address, DynamicAddress, StaticStorageMapKey,
|
||||
StorageAddress, Yes,
|
||||
pub use subxt_core::storage::address::{
|
||||
dynamic, Address, DynamicAddress, StaticStorageKey, StorageAddress, StorageKey,
|
||||
};
|
||||
}
|
||||
|
||||
pub use subxt_core::storage::StorageKey;
|
||||
|
||||
// For consistency with other modules, also expose
|
||||
// the basic address stuff at the root of the module.
|
||||
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
|
||||
pub use address::{dynamic, Address, DynamicAddress, StorageAddress};
|
||||
|
||||
@@ -1,270 +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},
|
||||
metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata},
|
||||
utils::{Encoded, Static},
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use super::{
|
||||
storage_type::{validate_storage_address, Storage},
|
||||
utils, StorageAddress,
|
||||
StorageAddress,
|
||||
};
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
@@ -12,12 +12,11 @@ use crate::{
|
||||
error::Error,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct StorageClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomData<T>,
|
||||
@@ -51,7 +50,7 @@ where
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve the entries at the root of the associated address.
|
||||
pub fn address_root_bytes<Address: StorageAddress>(&self, address: &Address) -> Vec<u8> {
|
||||
utils::storage_address_root_bytes(address)
|
||||
subxt_core::storage::utils::storage_address_root_bytes(address)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
@@ -63,7 +62,8 @@ where
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
utils::storage_address_bytes(address, &self.client.metadata())
|
||||
subxt_core::storage::utils::storage_address_bytes(address, &self.client.metadata())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,27 +2,29 @@
|
||||
// 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::address::{StorageAddress, StorageHashers, StorageKey};
|
||||
use subxt_core::utils::Yes;
|
||||
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef},
|
||||
client::OnlineClientT,
|
||||
error::{Error, MetadataError},
|
||||
error::{Error, MetadataError, StorageAddressError},
|
||||
metadata::{DecodeWithMetadata, Metadata},
|
||||
Config,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
|
||||
|
||||
/// This is returned from a couple of storage functions.
|
||||
pub use crate::backend::StreamOfResults;
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct Storage<T: Config, Client> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<T::Hash>,
|
||||
@@ -132,7 +134,8 @@ 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 =
|
||||
subxt_core::storage::utils::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)?;
|
||||
@@ -197,18 +200,19 @@ where
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// while let Some(Ok((key, value))) = iter.next().await {
|
||||
/// println!("Key: 0x{}", hex::encode(&key));
|
||||
/// println!("Value: {}", value);
|
||||
/// while let Some(Ok(kv)) = iter.next().await {
|
||||
/// println!("Key bytes: 0x{}", hex::encode(&kv.key_bytes));
|
||||
/// println!("Value: {}", kv.value);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn iter<Address>(
|
||||
&self,
|
||||
address: Address,
|
||||
) -> impl Future<Output = Result<StreamOfResults<(Vec<u8>, Address::Target)>, Error>> + 'static
|
||||
) -> impl Future<Output = Result<StreamOfResults<StorageKeyValuePair<Address>>, Error>> + 'static
|
||||
where
|
||||
Address: StorageAddress<IsIterable = Yes> + 'static,
|
||||
Address::Keys: 'static + Sized,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let block_ref = self.block_ref.clone();
|
||||
@@ -226,11 +230,14 @@ 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 entry = entry.entry_type();
|
||||
|
||||
let return_type_id = entry.value_ty();
|
||||
let hashers = StorageHashers::new(entry, metadata.types())?;
|
||||
|
||||
// The address bytes of this entry:
|
||||
let address_bytes = super::utils::storage_address_bytes(&address, &metadata)?;
|
||||
|
||||
let address_bytes =
|
||||
subxt_core::storage::utils::storage_address_bytes(&address, &metadata)?;
|
||||
let s = client
|
||||
.backend()
|
||||
.storage_fetch_descendant_values(address_bytes, block_ref.hash())
|
||||
@@ -240,12 +247,27 @@ where
|
||||
Ok(kv) => kv,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let val = Address::Target::decode_with_metadata(
|
||||
let value = Address::Target::decode_with_metadata(
|
||||
&mut &*kv.value,
|
||||
return_type_id,
|
||||
&metadata,
|
||||
)?;
|
||||
Ok((kv.key, val))
|
||||
|
||||
let key_bytes = kv.key;
|
||||
let cursor = &mut &key_bytes[..];
|
||||
strip_storage_addess_root_bytes(cursor)?;
|
||||
|
||||
let keys = <Address::Keys as StorageKey>::decode_storage_key(
|
||||
cursor,
|
||||
&mut hashers.iter(),
|
||||
metadata.types(),
|
||||
)?;
|
||||
|
||||
Ok(StorageKeyValuePair::<Address> {
|
||||
keys,
|
||||
key_bytes,
|
||||
value,
|
||||
})
|
||||
});
|
||||
|
||||
let s = StreamOfResults::new(Box::pin(s));
|
||||
@@ -265,8 +287,10 @@ where
|
||||
// construct the storage key. This is done similarly in `frame_support::traits::metadata::StorageVersion::storage_key()`.
|
||||
pub const STORAGE_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__STORAGE_VERSION__:";
|
||||
let mut key_bytes: Vec<u8> = vec![];
|
||||
key_bytes.extend(&sp_core_hashing::twox_128(pallet_name.as_ref().as_bytes()));
|
||||
key_bytes.extend(&sp_core_hashing::twox_128(
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
pallet_name.as_ref().as_bytes(),
|
||||
));
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
|
||||
));
|
||||
|
||||
@@ -290,6 +314,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips the first 16 bytes (8 for the pallet hash, 8 for the entry hash) off some storage address bytes.
|
||||
fn strip_storage_addess_root_bytes(address_bytes: &mut &[u8]) -> Result<(), StorageAddressError> {
|
||||
if address_bytes.len() >= 16 {
|
||||
*address_bytes = &address_bytes[16..];
|
||||
Ok(())
|
||||
} else {
|
||||
Err(StorageAddressError::UnexpectedAddressBytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of keys and values together with all the bytes that make up the storage address.
|
||||
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct StorageKeyValuePair<T: StorageAddress> {
|
||||
/// The bytes that make up the address of the storage entry.
|
||||
pub key_bytes: Vec<u8>,
|
||||
/// The keys that can be used to construct the address of this storage entry.
|
||||
pub keys: T::Keys,
|
||||
/// The value of the storage entry.
|
||||
pub value: T::Target,
|
||||
}
|
||||
|
||||
/// Validate a storage address against the metadata.
|
||||
pub(crate) fn validate_storage_address<Address: StorageAddress>(
|
||||
address: &Address,
|
||||
|
||||
@@ -6,22 +6,24 @@
|
||||
//! aren't things that should ever be overridden, and so don't exist on
|
||||
//! the trait itself.
|
||||
|
||||
use subxt_metadata::StorageHasher;
|
||||
|
||||
use super::StorageAddress;
|
||||
use crate::{error::Error, metadata::Metadata};
|
||||
|
||||
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
|
||||
/// and append those bytes to the output.
|
||||
pub(crate) fn write_storage_address_root_bytes<Address: StorageAddress>(
|
||||
pub 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()));
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
|
||||
}
|
||||
|
||||
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
|
||||
/// a lookup in a storage map at that location.
|
||||
pub(crate) fn storage_address_bytes<Address: StorageAddress>(
|
||||
pub fn storage_address_bytes<Address: StorageAddress>(
|
||||
addr: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
@@ -32,8 +34,27 @@ pub(crate) fn storage_address_bytes<Address: StorageAddress>(
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
write_storage_address_root_bytes(addr, &mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
|
||||
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
|
||||
match hasher {
|
||||
StorageHasher::Identity => bytes.extend(input),
|
||||
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
|
||||
StorageHasher::Blake2_128Concat => {
|
||||
bytes.extend(sp_crypto_hashing::blake2_128(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
|
||||
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
|
||||
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
|
||||
StorageHasher::Twox64Concat => {
|
||||
bytes.extend(sp_crypto_hashing::twox_64(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -11,15 +11,16 @@
|
||||
|
||||
use crate::macros::cfg_substrate_compat;
|
||||
|
||||
mod signer;
|
||||
mod tx_client;
|
||||
mod tx_payload;
|
||||
mod tx_progress;
|
||||
|
||||
pub use subxt_core::tx as tx_payload;
|
||||
pub use subxt_core::tx::signer;
|
||||
|
||||
// The PairSigner impl currently relies on Substrate bits and pieces, so make it an optional
|
||||
// feature if we want to avoid needing sp_core and sp_runtime.
|
||||
cfg_substrate_compat! {
|
||||
pub use self::signer::PairSigner;
|
||||
pub use signer::PairSigner;
|
||||
}
|
||||
|
||||
pub use self::{
|
||||
@@ -28,6 +29,6 @@ pub use self::{
|
||||
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
|
||||
ValidationResult,
|
||||
},
|
||||
tx_payload::{dynamic, BoxedPayload, DynamicPayload, Payload, TxPayload},
|
||||
tx_payload::{dynamic, DynamicPayload, Payload, TxPayload},
|
||||
tx_progress::{TxInBlock, TxProgress, TxStatus},
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
//! A library to **sub**mit e**xt**rinsics to a
|
||||
//! [substrate](https://github.com/paritytech/substrate) node via RPC.
|
||||
|
||||
use crate::macros::cfg_substrate_compat;
|
||||
use crate::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;
|
||||
|
||||
/// Return the "from" address.
|
||||
fn address(&self) -> T::Address;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
cfg_substrate_compat! {
|
||||
pub use pair_signer::PairSigner;
|
||||
}
|
||||
|
||||
// A signer suitable for substrate based chains. This provides compatibility with Substrate
|
||||
// packages like sp_keyring and such, and so relies on sp_core and sp_runtime to be included.
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod pair_signer {
|
||||
use super::Signer;
|
||||
use crate::Config;
|
||||
use sp_core::Pair as PairT;
|
||||
use sp_runtime::{
|
||||
traits::{IdentifyAccount, Verify},
|
||||
AccountId32 as SpAccountId32, MultiSignature as SpMultiSignature,
|
||||
};
|
||||
|
||||
/// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PairSigner<T: Config, Pair> {
|
||||
account_id: T::AccountId,
|
||||
signer: Pair,
|
||||
}
|
||||
|
||||
impl<T, Pair> PairSigner<T, Pair>
|
||||
where
|
||||
T: Config,
|
||||
Pair: PairT,
|
||||
// We go via an `sp_runtime::MultiSignature`. We can probably generalise this
|
||||
// by implementing some of these traits on our built-in MultiSignature and then
|
||||
// requiring them on all T::Signatures, to avoid any go-between.
|
||||
<SpMultiSignature as Verify>::Signer: From<Pair::Public>,
|
||||
T::AccountId: From<SpAccountId32>,
|
||||
{
|
||||
/// Creates a new [`Signer`] from an [`sp_core::Pair`].
|
||||
pub fn new(signer: Pair) -> Self {
|
||||
let account_id =
|
||||
<SpMultiSignature as Verify>::Signer::from(signer.public()).into_account();
|
||||
Self {
|
||||
account_id: account_id.into(),
|
||||
signer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`sp_core::Pair`] implementation used to construct this.
|
||||
pub fn signer(&self) -> &Pair {
|
||||
&self.signer
|
||||
}
|
||||
|
||||
/// Return the account ID.
|
||||
pub fn account_id(&self) -> &T::AccountId {
|
||||
&self.account_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Pair> Signer<T> for PairSigner<T, Pair>
|
||||
where
|
||||
T: Config,
|
||||
Pair: PairT,
|
||||
Pair::Signature: Into<T::Signature>,
|
||||
{
|
||||
fn account_id(&self) -> T::AccountId {
|
||||
self.account_id.clone()
|
||||
}
|
||||
|
||||
fn address(&self) -> T::Address {
|
||||
self.account_id.clone().into()
|
||||
}
|
||||
|
||||
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
|
||||
self.signer.sign(signer_payload).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
-31
@@ -7,18 +7,20 @@ use std::borrow::Cow;
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef, TransactionStatus},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher},
|
||||
error::{Error, MetadataError},
|
||||
config::{
|
||||
Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header, RefineParams,
|
||||
RefineParamsData,
|
||||
},
|
||||
error::{BlockError, 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 derive_where::derive_where;
|
||||
use sp_crypto_hashing::blake2_256;
|
||||
|
||||
/// A client for working with transactions.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct TxClient<T: Config, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomDataSendSync<T>,
|
||||
@@ -103,11 +105,13 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
}
|
||||
|
||||
/// Create a partial extrinsic.
|
||||
pub fn create_partial_signed_with_nonce<Call>(
|
||||
///
|
||||
/// 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_signed_offline<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
account_nonce: u64,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -120,11 +124,8 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
let call_data = self.call_data(call)?;
|
||||
|
||||
// 3. Construct our custom additional/extra params.
|
||||
let additional_and_extra_params = <T::ExtrinsicParams as ExtrinsicParams<T>>::new(
|
||||
account_nonce,
|
||||
self.client.clone(),
|
||||
other_params,
|
||||
)?;
|
||||
let additional_and_extra_params =
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(&self.client.client_state(), params)?;
|
||||
|
||||
// Return these details, ready to construct a signed extrinsic from.
|
||||
Ok(PartialExtrinsic {
|
||||
@@ -135,12 +136,14 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
}
|
||||
|
||||
/// Creates a signed extrinsic without submitting it.
|
||||
pub fn create_signed_with_nonce<Call, Signer>(
|
||||
///
|
||||
/// 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_signed_offline<Call, Signer>(
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
account_nonce: u64,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
@@ -152,8 +155,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
|
||||
// 2. Gather the "additional" and "extra" params along with the encoded call data,
|
||||
// ready to be signed.
|
||||
let partial_signed =
|
||||
self.create_partial_signed_with_nonce(call, account_nonce, other_params)?;
|
||||
let partial_signed = self.create_partial_signed_offline(call, params)?;
|
||||
|
||||
// 3. Sign and construct an extrinsic from these details.
|
||||
Ok(partial_signed.sign(signer))
|
||||
@@ -165,6 +167,30 @@ where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
|
||||
async fn refine_params(
|
||||
&self,
|
||||
account_id: &T::AccountId,
|
||||
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<(), Error> {
|
||||
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
|
||||
let block_header = self
|
||||
.client
|
||||
.backend()
|
||||
.block_header(block_ref.hash())
|
||||
.await?
|
||||
.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
|
||||
let account_nonce =
|
||||
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await?;
|
||||
|
||||
params.refine(&RefineParamsData::new(
|
||||
account_nonce,
|
||||
block_header.number().into(),
|
||||
block_header.hash(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the account nonce for a given account ID.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
|
||||
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
|
||||
@@ -176,13 +202,15 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
{
|
||||
let account_nonce = self.account_nonce(account_id).await?;
|
||||
self.create_partial_signed_with_nonce(call, account_nonce, other_params)
|
||||
// Refine the params by adding account nonce and latest block information:
|
||||
self.refine_params(account_id, &mut params).await?;
|
||||
// Create the partial extrinsic with the refined params:
|
||||
self.create_partial_signed_offline(call, params)
|
||||
}
|
||||
|
||||
/// Creates a signed extrinsic, without submitting it.
|
||||
@@ -190,14 +218,24 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
let account_nonce = self.account_nonce(&signer.account_id()).await?;
|
||||
self.create_signed_with_nonce(call, signer, account_nonce, other_params)
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
self.validate(call)?;
|
||||
|
||||
// 2. Gather the "additional" and "extra" params along with the encoded call data,
|
||||
// ready to be signed.
|
||||
let partial_signed = self
|
||||
.create_partial_signed(call, &signer.account_id(), params)
|
||||
.await?;
|
||||
|
||||
// 3. Sign and construct an extrinsic from these details.
|
||||
Ok(partial_signed.sign(signer))
|
||||
}
|
||||
|
||||
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
|
||||
@@ -213,7 +251,7 @@ where
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
|
||||
{
|
||||
self.sign_and_submit_then_watch(call, signer, Default::default())
|
||||
.await
|
||||
@@ -227,13 +265,13 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
self.create_signed(call, signer, other_params)
|
||||
self.create_signed(call, signer, params)
|
||||
.await?
|
||||
.submit_and_watch()
|
||||
.await
|
||||
@@ -257,7 +295,7 @@ where
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
|
||||
{
|
||||
self.sign_and_submit(call, signer, Default::default()).await
|
||||
}
|
||||
@@ -274,13 +312,13 @@ where
|
||||
&self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<T::Hash, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
self.create_signed(call, signer, other_params)
|
||||
self.create_signed(call, signer, params)
|
||||
.await?
|
||||
.submit()
|
||||
.await
|
||||
|
||||
@@ -1,192 +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 the trait and types used to represent
|
||||
//! transactions that can be submitted.
|
||||
|
||||
use crate::{
|
||||
dynamic::Value,
|
||||
error::{Error, MetadataError},
|
||||
metadata::Metadata,
|
||||
};
|
||||
use codec::Encode;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::{Composite, ValueDef, Variant};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
/// This represents a transaction payload that can be submitted
|
||||
/// to a node.
|
||||
pub trait TxPayload {
|
||||
/// Encode call data to the provided output.
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
|
||||
/// Encode call data and return the output. This is a convenience
|
||||
/// wrapper around [`TxPayload::encode_call_data_to`].
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct Payload<CallData> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
call_name: Cow<'static, str>,
|
||||
call_data: CallData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
/// A boxed transaction payload.
|
||||
// Dev Note: Arc used to enable easy cloning (given that we can't have dyn Clone).
|
||||
pub type BoxedPayload = Payload<Arc<dyn EncodeAsFields + Send + Sync + 'static>>;
|
||||
|
||||
/// The type of a payload typically used for dynamic transaction payloads.
|
||||
pub type DynamicPayload = Payload<Composite<()>>;
|
||||
|
||||
impl<CallData> Payload<CallData> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(
|
||||
pallet_name: impl Into<String>,
|
||||
call_name: impl Into<String>,
|
||||
call_data: CallData,
|
||||
) -> Self {
|
||||
Payload {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
call_name: Cow::Owned(call_name.into()),
|
||||
call_data,
|
||||
validation_hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Payload`] 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 {
|
||||
Payload {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
call_name: Cow::Borrowed(call_name),
|
||||
call_data,
|
||||
validation_hash: Some(validation_hash),
|
||||
}
|
||||
}
|
||||
|
||||
/// Box the payload.
|
||||
pub fn boxed(self) -> BoxedPayload
|
||||
where
|
||||
CallData: EncodeAsFields + Send + Sync + 'static,
|
||||
{
|
||||
BoxedPayload {
|
||||
pallet_name: self.pallet_name,
|
||||
call_name: self.call_name,
|
||||
call_data: Arc::new(self.call_data),
|
||||
validation_hash: self.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 Payload<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> TxPayload for Payload<CallData> {
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
|
||||
let call = pallet
|
||||
.call_variant_by_name(&self.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
|
||||
|
||||
let pallet_index = pallet.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)?;
|
||||
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 [`Payload::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 {
|
||||
Payload::new(pallet_name, call_name, call_data.into())
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
use std::task::Poll;
|
||||
|
||||
use crate::utils::strip_compact_prefix;
|
||||
use crate::{
|
||||
backend::{BlockRef, StreamOfResults, TransactionStatus as BackendTxStatus},
|
||||
client::OnlineClientT,
|
||||
@@ -14,8 +13,9 @@ use crate::{
|
||||
events::EventsClient,
|
||||
Config,
|
||||
};
|
||||
use derivative::Derivative;
|
||||
use derive_where::derive_where;
|
||||
use futures::{Stream, StreamExt};
|
||||
use subxt_core::utils::strip_compact_prefix;
|
||||
|
||||
/// This struct represents a subscription to the progress of some transaction.
|
||||
pub struct TxProgress<T: Config, C> {
|
||||
@@ -167,8 +167,7 @@ impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
|
||||
}
|
||||
|
||||
/// Possible transaction statuses returned from our [`TxProgress::next()`] call.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
|
||||
#[derive_where(Debug; C)]
|
||||
pub enum TxStatus<T: Config, C> {
|
||||
/// Transaction is part of the future queue.
|
||||
Validated,
|
||||
@@ -221,8 +220,7 @@ impl<T: Config, C> TxStatus<T, C> {
|
||||
}
|
||||
|
||||
/// This struct represents a transaction that has made it into a block.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
|
||||
#[derive_where(Debug; C)]
|
||||
pub struct TxInBlock<T: Config, C> {
|
||||
block_ref: BlockRef<T::Hash>,
|
||||
ext_hash: T::Hash,
|
||||
@@ -321,6 +319,8 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
|
||||
use crate::{
|
||||
backend::{StreamOfResults, TransactionStatus},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
@@ -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) -> RuntimeVersion {
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
|
||||
fn client_state(&self) -> subxt_core::client::ClientState<SubstrateConfig> {
|
||||
unimplemented!("just a mock impl to satisfy trait bounds")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,218 +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.
|
||||
|
||||
//! The "default" Substrate/Polkadot AccountId. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_core::AccountId32`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's
|
||||
/// `sp_core::crypto::AccountId32`. To obtain more functionality, convert this into
|
||||
/// that type.
|
||||
#[derive(
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
Debug,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub struct AccountId32(pub [u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for AccountId32 {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for AccountId32 {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for AccountId32 {
|
||||
fn from(x: [u8; 32]) -> Self {
|
||||
AccountId32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountId32 {
|
||||
// Return the ss58-check string for this key. Adapted from `sp_core::crypto`. We need this to
|
||||
// serialize our account appropriately but otherwise don't care.
|
||||
fn to_ss58check(&self) -> String {
|
||||
// For serializing to a string to obtain the account nonce, we use the default substrate
|
||||
// prefix (since we have no way to otherwise pick one). It doesn't really matter, since when
|
||||
// it's deserialized back in system_accountNextIndex, we ignore this (so long as it's valid).
|
||||
const SUBSTRATE_SS58_PREFIX: u8 = 42;
|
||||
// prefix <= 63 just take up one byte at the start:
|
||||
let mut v = vec![SUBSTRATE_SS58_PREFIX];
|
||||
// then push the account ID bytes.
|
||||
v.extend(self.0);
|
||||
// then push a 2 byte checksum of what we have so far.
|
||||
let r = ss58hash(&v);
|
||||
v.extend(&r[0..2]);
|
||||
// then encode to base58.
|
||||
use base58::ToBase58;
|
||||
v.to_base58()
|
||||
}
|
||||
|
||||
// This isn't strictly needed, but to give our AccountId32 a little more usefulness, we also
|
||||
// implement the logic needed to decode an AccountId32 from an SS58 encoded string. This is exposed
|
||||
// via a `FromStr` impl.
|
||||
fn from_ss58check(s: &str) -> Result<Self, FromSs58Error> {
|
||||
const CHECKSUM_LEN: usize = 2;
|
||||
let body_len = 32;
|
||||
|
||||
use base58::FromBase58;
|
||||
let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?;
|
||||
if data.len() < 2 {
|
||||
return Err(FromSs58Error::BadLength);
|
||||
}
|
||||
let prefix_len = match data[0] {
|
||||
0..=63 => 1,
|
||||
64..=127 => 2,
|
||||
_ => return Err(FromSs58Error::InvalidPrefix),
|
||||
};
|
||||
if data.len() != prefix_len + body_len + CHECKSUM_LEN {
|
||||
return Err(FromSs58Error::BadLength);
|
||||
}
|
||||
let hash = ss58hash(&data[0..body_len + prefix_len]);
|
||||
let checksum = &hash[0..CHECKSUM_LEN];
|
||||
if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum {
|
||||
// Invalid checksum.
|
||||
return Err(FromSs58Error::InvalidChecksum);
|
||||
}
|
||||
|
||||
let result = data[prefix_len..body_len + prefix_len]
|
||||
.try_into()
|
||||
.map_err(|_| FromSs58Error::BadLength)?;
|
||||
Ok(AccountId32(result))
|
||||
}
|
||||
}
|
||||
|
||||
/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32
|
||||
#[derive(thiserror::Error, Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FromSs58Error {
|
||||
#[error("Base 58 requirement is violated")]
|
||||
BadBase58,
|
||||
#[error("Length is bad")]
|
||||
BadLength,
|
||||
#[error("Invalid checksum")]
|
||||
InvalidChecksum,
|
||||
#[error("Invalid SS58 prefix byte.")]
|
||||
InvalidPrefix,
|
||||
}
|
||||
|
||||
// We do this just to get a checksum to help verify the validity of the address in to_ss58check
|
||||
fn ss58hash(data: &[u8]) -> Vec<u8> {
|
||||
use blake2::{Blake2b512, Digest};
|
||||
const PREFIX: &[u8] = b"SS58PRE";
|
||||
let mut ctx = Blake2b512::new();
|
||||
ctx.update(PREFIX);
|
||||
ctx.update(data);
|
||||
ctx.finalize().to_vec()
|
||||
}
|
||||
|
||||
impl Serialize for AccountId32 {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_ss58check())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AccountId32 {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
AccountId32::from_ss58check(&String::deserialize(deserializer)?)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AccountId32 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_ss58check())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AccountId32 {
|
||||
type Err = FromSs58Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
AccountId32::from_ss58check(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::*;
|
||||
|
||||
impl From<sp_runtime::AccountId32> for AccountId32 {
|
||||
fn from(value: sp_runtime::AccountId32) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
impl From<sp_core::sr25519::Public> for AccountId32 {
|
||||
fn from(value: sp_core::sr25519::Public) -> Self {
|
||||
let acc: sp_runtime::AccountId32 = value.into();
|
||||
acc.into()
|
||||
}
|
||||
}
|
||||
impl From<sp_core::ed25519::Public> for AccountId32 {
|
||||
fn from(value: sp_core::ed25519::Public) -> Self {
|
||||
let acc: sp_runtime::AccountId32 = value.into();
|
||||
acc.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
use sp_keyring::AccountKeyring;
|
||||
|
||||
#[test]
|
||||
fn ss58_is_compatible_with_substrate_impl() {
|
||||
let keyrings = vec![
|
||||
AccountKeyring::Alice,
|
||||
AccountKeyring::Bob,
|
||||
AccountKeyring::Charlie,
|
||||
];
|
||||
|
||||
for keyring in keyrings {
|
||||
let substrate_account = keyring.to_account_id();
|
||||
// Avoid "From" impl hidden behind "substrate-compat" feature so that this test
|
||||
// can work either way:
|
||||
let local_account = AccountId32(substrate_account.clone().into());
|
||||
|
||||
// Both should encode to ss58 the same way:
|
||||
let substrate_ss58 = substrate_account.to_ss58check();
|
||||
assert_eq!(substrate_ss58, local_account.to_ss58check());
|
||||
|
||||
// Both should decode from ss58 back to the same:
|
||||
assert_eq!(
|
||||
sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(),
|
||||
substrate_account
|
||||
);
|
||||
assert_eq!(
|
||||
AccountId32::from_ss58check(&substrate_ss58).unwrap(),
|
||||
local_account
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +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.
|
||||
|
||||
//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types.
|
||||
|
||||
use codec::{Compact, Input};
|
||||
use scale_bits::{
|
||||
scale::format::{Format, OrderFormat, StoreFormat},
|
||||
Bits,
|
||||
};
|
||||
use scale_decode::IntoVisitor;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum.
|
||||
///
|
||||
/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using
|
||||
/// `bitvec`-like type type parameters.
|
||||
pub trait BitStore {
|
||||
/// Corresponding `scale_bits::StoreFormat` value.
|
||||
const FORMAT: StoreFormat;
|
||||
/// Number of bits that the backing store types holds.
|
||||
const BITS: u32;
|
||||
}
|
||||
macro_rules! impl_store {
|
||||
($ty:ident, $wrapped:ty) => {
|
||||
impl BitStore for $wrapped {
|
||||
const FORMAT: StoreFormat = StoreFormat::$ty;
|
||||
const BITS: u32 = <$wrapped>::BITS;
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_store!(U8, u8);
|
||||
impl_store!(U16, u16);
|
||||
impl_store!(U32, u32);
|
||||
impl_store!(U64, u64);
|
||||
|
||||
/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum.
|
||||
///
|
||||
/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using
|
||||
/// `bitvec`-like type type parameters.
|
||||
pub trait BitOrder {
|
||||
/// Corresponding `scale_bits::OrderFormat` value.
|
||||
const FORMAT: OrderFormat;
|
||||
}
|
||||
macro_rules! impl_order {
|
||||
($ty:ident) => {
|
||||
#[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")]
|
||||
#[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum $ty {}
|
||||
impl BitOrder for $ty {
|
||||
const FORMAT: OrderFormat = OrderFormat::$ty;
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_order!(Lsb0);
|
||||
impl_order!(Msb0);
|
||||
|
||||
/// Constructs a run-time format parameters based on the corresponding type-level parameters.
|
||||
fn bit_format<Store: BitStore, Order: BitOrder>() -> Format {
|
||||
Format {
|
||||
order: Order::FORMAT,
|
||||
store: Store::FORMAT,
|
||||
}
|
||||
}
|
||||
|
||||
/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB)
|
||||
/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DecodedBits<Store, Order> {
|
||||
bits: Bits,
|
||||
_marker: PhantomData<(Store, Order)>,
|
||||
}
|
||||
|
||||
impl<Store, Order> DecodedBits<Store, Order> {
|
||||
/// Extracts the underlying `scale_bits::Bits` value.
|
||||
pub fn into_bits(self) -> Bits {
|
||||
self.bits
|
||||
}
|
||||
|
||||
/// References the underlying `scale_bits::Bits` value.
|
||||
pub fn as_bits(&self) -> &Bits {
|
||||
&self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store, Order> core::iter::FromIterator<bool> for DecodedBits<Store, Order> {
|
||||
fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
|
||||
DecodedBits {
|
||||
bits: Bits::from_iter(iter),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store: BitStore, Order: BitOrder> codec::Decode for DecodedBits<Store, Order> {
|
||||
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
/// Equivalent of `BitSlice::MAX_BITS` on 32bit machine.
|
||||
const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff;
|
||||
|
||||
let Compact(bits) = <Compact<u32>>::decode(input)?;
|
||||
// Otherwise it is impossible to store it on 32bit machine.
|
||||
if bits > ARCH32BIT_BITSLICE_MAX_BITS {
|
||||
return Err("Attempt to decode a BitVec with too many bits".into());
|
||||
}
|
||||
// NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised
|
||||
let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0);
|
||||
let bytes_in_elem = Store::BITS.saturating_div(u8::BITS);
|
||||
let bytes_needed = (elements * bytes_in_elem) as usize;
|
||||
|
||||
// NOTE: We could reduce allocations if it would be possible to directly
|
||||
// decode from an `Input` type using a custom format (rather than default <u8, Lsb0>)
|
||||
// for the `Bits` type.
|
||||
let mut storage = codec::Encode::encode(&Compact(bits));
|
||||
let prefix_len = storage.len();
|
||||
storage.reserve_exact(bytes_needed);
|
||||
storage.extend(vec![0; bytes_needed]);
|
||||
input.read(&mut storage[prefix_len..])?;
|
||||
|
||||
let decoder = scale_bits::decode_using_format_from(&storage, bit_format::<Store, Order>())?;
|
||||
let bits = decoder.collect::<Result<Vec<_>, _>>()?;
|
||||
let bits = Bits::from_iter(bits);
|
||||
|
||||
Ok(DecodedBits {
|
||||
bits,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store: BitStore, Order: BitOrder> codec::Encode for DecodedBits<Store, Order> {
|
||||
fn size_hint(&self) -> usize {
|
||||
self.bits.size_hint()
|
||||
}
|
||||
|
||||
fn encoded_size(&self) -> usize {
|
||||
self.bits.encoded_size()
|
||||
}
|
||||
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
scale_bits::encode_using_format(self.bits.iter(), bit_format::<Store, Order>())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct DecodedBitsVisitor<S, O>(std::marker::PhantomData<(S, O)>);
|
||||
impl<Store, Order> scale_decode::Visitor for DecodedBitsVisitor<Store, Order> {
|
||||
type Value<'scale, 'info> = DecodedBits<Store, Order>;
|
||||
type Error = scale_decode::Error;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
type_id: scale_decode::visitor::TypeId,
|
||||
types: &'info scale_info::PortableRegistry,
|
||||
) -> scale_decode::visitor::DecodeAsTypeResult<
|
||||
Self,
|
||||
Result<Self::Value<'scale, 'info>, Self::Error>,
|
||||
> {
|
||||
let res = scale_decode::visitor::decode_with_visitor(
|
||||
input,
|
||||
type_id.0,
|
||||
types,
|
||||
Bits::into_visitor(),
|
||||
)
|
||||
.map(|bits| DecodedBits {
|
||||
bits,
|
||||
_marker: PhantomData,
|
||||
});
|
||||
scale_decode::visitor::DecodeAsTypeResult::Decoded(res)
|
||||
}
|
||||
}
|
||||
impl<Store, Order> scale_decode::IntoVisitor for DecodedBits<Store, Order> {
|
||||
type Visitor = DecodedBitsVisitor<Store, Order>;
|
||||
fn into_visitor() -> Self::Visitor {
|
||||
DecodedBitsVisitor(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store, Order> scale_encode::EncodeAsType for DecodedBits<Store, Order> {
|
||||
fn encode_as_type_to(
|
||||
&self,
|
||||
type_id: u32,
|
||||
types: &scale_info::PortableRegistry,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.bits.encode_as_type_to(type_id, types, out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use codec::Decode as _;
|
||||
|
||||
// NOTE: We don't use `bitvec::order` types in our implementation, since we
|
||||
// don't want to depend on `bitvec`. Rather than reimplementing the unsafe
|
||||
// trait on our types here for testing purposes, we simply convert and
|
||||
// delegate to `bitvec`'s own types.
|
||||
trait ToBitVec {
|
||||
type Order: bitvec::order::BitOrder;
|
||||
}
|
||||
impl ToBitVec for Lsb0 {
|
||||
type Order = bitvec::order::Lsb0;
|
||||
}
|
||||
impl ToBitVec for Msb0 {
|
||||
type Order = bitvec::order::Msb0;
|
||||
}
|
||||
|
||||
fn scales_like_bitvec_and_roundtrips<
|
||||
'a,
|
||||
Store: BitStore + bitvec::store::BitStore + PartialEq,
|
||||
Order: BitOrder + ToBitVec + Debug + PartialEq,
|
||||
>(
|
||||
input: impl IntoIterator<Item = &'a bool>,
|
||||
) where
|
||||
BitVec<Store, <Order as ToBitVec>::Order>: codec::Encode + codec::Decode,
|
||||
{
|
||||
let input: Vec<_> = input.into_iter().copied().collect();
|
||||
|
||||
let decoded_bits = DecodedBits::<Store, Order>::from_iter(input.clone());
|
||||
let bitvec = BitVec::<Store, <Order as ToBitVec>::Order>::from_iter(input);
|
||||
|
||||
let decoded_bits_encoded = codec::Encode::encode(&decoded_bits);
|
||||
let bitvec_encoded = codec::Encode::encode(&bitvec);
|
||||
assert_eq!(decoded_bits_encoded, bitvec_encoded);
|
||||
|
||||
let decoded_bits_decoded =
|
||||
DecodedBits::<Store, Order>::decode(&mut &decoded_bits_encoded[..])
|
||||
.expect("SCALE-encoding DecodedBits to roundtrip");
|
||||
let bitvec_decoded =
|
||||
BitVec::<Store, <Order as ToBitVec>::Order>::decode(&mut &bitvec_encoded[..])
|
||||
.expect("SCALE-encoding BitVec to roundtrip");
|
||||
assert_eq!(decoded_bits, decoded_bits_decoded);
|
||||
assert_eq!(bitvec, bitvec_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoded_bitvec_scales_and_roundtrips() {
|
||||
let test_cases = [
|
||||
vec![],
|
||||
vec![true],
|
||||
vec![false],
|
||||
vec![true, false, true],
|
||||
vec![true, false, true, false, false, false, false, false, true],
|
||||
[vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(),
|
||||
[vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(),
|
||||
];
|
||||
|
||||
for test_case in &test_cases {
|
||||
scales_like_bitvec_and_roundtrips::<u8, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u16, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u32, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u64, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u8, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u16, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u32, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u64, Msb0>(test_case);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +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 scale_decode::DecodeAsType;
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
|
||||
/// An era to describe the longevity of a transaction.
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Default,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
DecodeAsType,
|
||||
EncodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub enum Era {
|
||||
/// The transaction is valid forever. The genesis hash must be present in the signed content.
|
||||
#[default]
|
||||
Immortal,
|
||||
|
||||
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
|
||||
///
|
||||
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
|
||||
/// of `system` module.
|
||||
Mortal {
|
||||
/// The number of blocks that the tx will be valid for after the checkpoint block
|
||||
/// hash found in the signer payload.
|
||||
period: u64,
|
||||
/// The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
/// implies which block hash is included in the signature material). If the `period` is
|
||||
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
/// `period` is.
|
||||
phase: u64,
|
||||
},
|
||||
}
|
||||
|
||||
// E.g. with period == 4:
|
||||
// 0 10 20 30 40
|
||||
// 0123456789012345678901234567890123456789012
|
||||
// |...|
|
||||
// authored -/ \- expiry
|
||||
// phase = 1
|
||||
// n = Q(current - phase, period) + phase
|
||||
impl Era {
|
||||
/// Create a new era based on a period (which should be a power of two between 4 and 65536
|
||||
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
|
||||
/// after the start).
|
||||
///
|
||||
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
|
||||
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
|
||||
/// prunes old blocks and renders transactions immediately invalid.
|
||||
pub fn mortal(period: u64, current: u64) -> Self {
|
||||
let period = period
|
||||
.checked_next_power_of_two()
|
||||
.unwrap_or(1 << 16)
|
||||
.clamp(4, 1 << 16);
|
||||
let phase = current % period;
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let quantized_phase = phase / quantize_factor * quantize_factor;
|
||||
|
||||
Self::Mortal {
|
||||
period,
|
||||
phase: quantized_phase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
|
||||
// it's really the most important bit here.
|
||||
impl codec::Encode for Era {
|
||||
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
|
||||
match self {
|
||||
Self::Immortal => output.push_byte(0),
|
||||
Self::Mortal { period, phase } => {
|
||||
let quantize_factor = (*period >> 12).max(1);
|
||||
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
|
||||
| ((phase / quantize_factor) << 4) as u16;
|
||||
encoded.encode_to(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl codec::Decode for Era {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let first = input.read_byte()?;
|
||||
if first == 0 {
|
||||
Ok(Self::Immortal)
|
||||
} else {
|
||||
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
|
||||
let period = 2 << (encoded % (1 << 4));
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let phase = (encoded >> 4) * quantize_factor;
|
||||
if period >= 4 && phase < period {
|
||||
Ok(Self::Mortal { period, phase })
|
||||
} else {
|
||||
Err("Invalid period and phase".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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.
|
||||
|
||||
use crate::macros::{cfg_jsonrpsee_native, cfg_jsonrpsee_web};
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
/// Possible errors encountered trying to fetch a chain spec from an RPC node.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FetchChainspecError {
|
||||
#[error("Cannot fetch chain spec: RPC error: {0}.")]
|
||||
RpcError(String),
|
||||
#[error("Cannot fetch chain spec: Invalid URL.")]
|
||||
InvalidUrl,
|
||||
#[error("Cannot fetch chain spec: Invalid URL scheme.")]
|
||||
InvalidScheme,
|
||||
#[error("Cannot fetch chain spec: Handshake error establishing WS connection.")]
|
||||
HandshakeError,
|
||||
}
|
||||
|
||||
/// Fetch a chain spec from an RPC node at the given URL.
|
||||
pub async fn fetch_chainspec_from_rpc_node(
|
||||
url: impl AsRef<str>,
|
||||
) -> Result<Box<RawValue>, FetchChainspecError> {
|
||||
use jsonrpsee::core::client::{ClientT, SubscriptionClientT};
|
||||
use jsonrpsee::rpc_params;
|
||||
|
||||
let client = jsonrpsee_helpers::client(url.as_ref()).await?;
|
||||
|
||||
let result = client
|
||||
.request("sync_state_genSyncSpec", jsonrpsee::rpc_params![true])
|
||||
.await
|
||||
.map_err(|err| FetchChainspecError::RpcError(err.to_string()))?;
|
||||
|
||||
// Subscribe to the finalized heads of the chain.
|
||||
let mut subscription = SubscriptionClientT::subscribe::<Box<RawValue>, _>(
|
||||
&client,
|
||||
"chain_subscribeFinalizedHeads",
|
||||
rpc_params![],
|
||||
"chain_unsubscribeFinalizedHeads",
|
||||
)
|
||||
.await
|
||||
.map_err(|err| FetchChainspecError::RpcError(err.to_string()))?;
|
||||
|
||||
// We must ensure that the finalized block of the chain is not the block included
|
||||
// in the chainSpec.
|
||||
// This is a temporary workaround for: https://github.com/smol-dot/smoldot/issues/1562.
|
||||
// The first finalized block that is received might by the finalized block could be the one
|
||||
// included in the chainSpec. Decoding the chainSpec for this purpose is too complex.
|
||||
let _ = subscription.next().await;
|
||||
let _ = subscription.next().await;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
cfg_jsonrpsee_native! {
|
||||
mod jsonrpsee_helpers {
|
||||
use super::FetchChainspecError;
|
||||
use tokio_util::compat::Compat;
|
||||
|
||||
pub use jsonrpsee::{
|
||||
client_transport::ws::{self, EitherStream, Url, WsTransportClientBuilder},
|
||||
core::client::Client,
|
||||
};
|
||||
|
||||
pub type Sender = ws::Sender<Compat<EitherStream>>;
|
||||
pub type Receiver = ws::Receiver<Compat<EitherStream>>;
|
||||
|
||||
/// Build WS RPC client from URL
|
||||
pub async fn client(url: &str) -> Result<Client, FetchChainspecError> {
|
||||
let url = Url::parse(url).map_err(|_| FetchChainspecError::InvalidUrl)?;
|
||||
|
||||
if url.scheme() != "ws" && url.scheme() != "wss" {
|
||||
return Err(FetchChainspecError::InvalidScheme);
|
||||
}
|
||||
|
||||
let (sender, receiver) = ws_transport(url).await?;
|
||||
|
||||
Ok(Client::builder()
|
||||
.max_buffer_capacity_per_subscription(4096)
|
||||
.build_with_tokio(sender, receiver))
|
||||
}
|
||||
|
||||
async fn ws_transport(url: Url) -> Result<(Sender, Receiver), FetchChainspecError> {
|
||||
WsTransportClientBuilder::default()
|
||||
.build(url)
|
||||
.await
|
||||
.map_err(|_| FetchChainspecError::HandshakeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_jsonrpsee_web! {
|
||||
mod jsonrpsee_helpers {
|
||||
use super::FetchChainspecError;
|
||||
pub use jsonrpsee::{
|
||||
client_transport::web,
|
||||
core::client::{Client, ClientBuilder},
|
||||
};
|
||||
|
||||
/// Build web RPC client from URL
|
||||
pub async fn client(url: &str) -> Result<Client, FetchChainspecError> {
|
||||
let (sender, receiver) = web::connect(url)
|
||||
.await
|
||||
.map_err(|_| FetchChainspecError::HandshakeError)?;
|
||||
|
||||
Ok(ClientBuilder::default()
|
||||
.max_buffer_capacity_per_subscription(4096)
|
||||
.build_with_wasm(sender, receiver))
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-71
@@ -4,50 +4,19 @@
|
||||
|
||||
//! Miscellaneous utility helpers.
|
||||
|
||||
mod account_id;
|
||||
pub mod bits;
|
||||
mod era;
|
||||
mod multi_address;
|
||||
mod multi_signature;
|
||||
mod static_type;
|
||||
mod unchecked_extrinsic;
|
||||
mod wrapper_opaque;
|
||||
|
||||
use crate::error::RpcError;
|
||||
use crate::Error;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use derivative::Derivative;
|
||||
use crate::macros::cfg_jsonrpsee;
|
||||
use crate::{error::RpcError, Error};
|
||||
use url::Url;
|
||||
|
||||
pub use account_id::AccountId32;
|
||||
pub use era::Era;
|
||||
pub use multi_address::MultiAddress;
|
||||
pub use multi_signature::MultiSignature;
|
||||
pub use static_type::Static;
|
||||
pub use unchecked_extrinsic::UncheckedExtrinsic;
|
||||
pub use wrapper_opaque::WrapperKeepOpaque;
|
||||
pub use subxt_core::utils::{
|
||||
bits, strip_compact_prefix, to_hex, AccountId32, Encoded, Era, KeyedVec, MultiAddress,
|
||||
MultiSignature, PhantomDataSendSync, Static, UncheckedExtrinsic, WrapperKeepOpaque, Yes, H160,
|
||||
H256, H512,
|
||||
};
|
||||
|
||||
// Used in codegen
|
||||
#[doc(hidden)]
|
||||
pub use primitive_types::{H160, H256, H512};
|
||||
|
||||
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
|
||||
/// the transaction payload
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Encoded(pub Vec<u8>);
|
||||
|
||||
impl codec::Encode for Encoded {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.0.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a compact encoded value from the beginning of the provided bytes,
|
||||
/// returning the value and any remaining bytes.
|
||||
pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> {
|
||||
let cursor = &mut &*bytes;
|
||||
let val = <Compact<u64>>::decode(cursor)?;
|
||||
Ok((val.0, *cursor))
|
||||
cfg_jsonrpsee! {
|
||||
mod fetch_chain_spec;
|
||||
pub use fetch_chain_spec::{fetch_chainspec_from_rpc_node, FetchChainspecError};
|
||||
}
|
||||
|
||||
/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost.
|
||||
@@ -74,33 +43,3 @@ pub fn validate_url_is_secure(url: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
|
||||
/// because regardless of the generic param, it is always possible to Send + Sync this
|
||||
/// 0 size type).
|
||||
#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)]
|
||||
#[derivative(
|
||||
Clone(bound = ""),
|
||||
PartialEq(bound = ""),
|
||||
Debug(bound = ""),
|
||||
Eq(bound = ""),
|
||||
Default(bound = ""),
|
||||
Hash(bound = "")
|
||||
)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
#[doc(hidden)]
|
||||
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T> PhantomDataSendSync<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(core::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for PhantomDataSendSync<T> {}
|
||||
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
||||
|
||||
/// This represents a key-value collection and is SCALE compatible
|
||||
/// with collections like BTreeMap. This has the same type params
|
||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
||||
|
||||
@@ -1,72 +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.
|
||||
|
||||
//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's
|
||||
/// `sp_runtime::MultiAddress`. To obtain more functionality, convert this into that type (this conversion
|
||||
/// functionality is provided via `From` impls if the `substrate-compat` feature is enabled).
|
||||
#[derive(
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
Debug,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub enum MultiAddress<AccountId, AccountIndex> {
|
||||
/// It's an account ID (pubkey).
|
||||
Id(AccountId),
|
||||
/// It's an account index.
|
||||
Index(#[codec(compact)] AccountIndex),
|
||||
/// It's some arbitrary raw bytes.
|
||||
Raw(Vec<u8>),
|
||||
/// It's a 32 byte representation.
|
||||
Address32([u8; 32]),
|
||||
/// Its a 20 byte representation.
|
||||
Address20([u8; 20]),
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> From<AccountId> for MultiAddress<AccountId, AccountIndex> {
|
||||
fn from(a: AccountId) -> Self {
|
||||
Self::Id(a)
|
||||
}
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::{super::AccountId32, *};
|
||||
|
||||
impl<N> From<sp_runtime::AccountId32> for MultiAddress<AccountId32, N> {
|
||||
fn from(value: sp_runtime::AccountId32) -> Self {
|
||||
let val: AccountId32 = value.into();
|
||||
val.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, N> From<sp_runtime::MultiAddress<Id, N>> for MultiAddress<AccountId32, N>
|
||||
where
|
||||
Id: Into<AccountId32>,
|
||||
{
|
||||
fn from(value: sp_runtime::MultiAddress<Id, N>) -> Self {
|
||||
match value {
|
||||
sp_runtime::MultiAddress::Id(v) => Self::Id(v.into()),
|
||||
sp_runtime::MultiAddress::Index(v) => Self::Index(v),
|
||||
sp_runtime::MultiAddress::Raw(v) => Self::Raw(v),
|
||||
sp_runtime::MultiAddress::Address32(v) => Self::Address32(v),
|
||||
sp_runtime::MultiAddress::Address20(v) => Self::Address20(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +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.
|
||||
|
||||
//! The "default" Substrate/Polkadot Signature type. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiSignature`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// Signature container that can store known signature types. This is a simplified version of
|
||||
/// `sp_runtime::MultiSignature`. To obtain more functionality, convert this into that type.
|
||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)]
|
||||
pub enum MultiSignature {
|
||||
/// An Ed25519 signature.
|
||||
Ed25519([u8; 64]),
|
||||
/// An Sr25519 signature.
|
||||
Sr25519([u8; 64]),
|
||||
/// An ECDSA/SECP256k1 signature (a 512-bit value, plus 8 bits for recovery ID).
|
||||
Ecdsa([u8; 65]),
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::*;
|
||||
|
||||
impl From<sp_runtime::MultiSignature> for MultiSignature {
|
||||
fn from(value: sp_runtime::MultiSignature) -> Self {
|
||||
match value {
|
||||
sp_runtime::MultiSignature::Ed25519(s) => Self::Ed25519(s.0),
|
||||
sp_runtime::MultiSignature::Sr25519(s) => Self::Sr25519(s.0),
|
||||
sp_runtime::MultiSignature::Ecdsa(s) => Self::Ecdsa(s.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::ed25519::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::ed25519::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::sr25519::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::sr25519::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::ecdsa::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::ecdsa::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +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 codec::{Decode, Encode};
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, Visitor};
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`].
|
||||
/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`].
|
||||
///
|
||||
/// In either direction, we ignore any type information and just attempt to encode/decode statically
|
||||
/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which
|
||||
/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but
|
||||
/// it's best to avoid using it where possible as it will not take into account any type information,
|
||||
/// and is thus more likely to encode or decode incorrectly.
|
||||
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
|
||||
pub struct Static<T>(pub T);
|
||||
|
||||
impl<T: Encode> EncodeAsType for Static<T> {
|
||||
fn encode_as_type_to(
|
||||
&self,
|
||||
_type_id: u32,
|
||||
_types: &scale_decode::PortableRegistry,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.0.encode_to(out);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StaticDecodeAsTypeVisitor<T>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Decode> Visitor for StaticDecodeAsTypeVisitor<T> {
|
||||
type Value<'scale, 'info> = Static<T>;
|
||||
type Error = scale_decode::Error;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
_type_id: scale_decode::visitor::TypeId,
|
||||
_types: &'info scale_info::PortableRegistry,
|
||||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
|
||||
use scale_decode::{visitor::DecodeError, Error};
|
||||
let decoded = T::decode(input)
|
||||
.map(Static)
|
||||
.map_err(|e| Error::new(DecodeError::CodecError(e).into()));
|
||||
DecodeAsTypeResult::Decoded(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> IntoVisitor for Static<T> {
|
||||
type Visitor = StaticDecodeAsTypeVisitor<T>;
|
||||
fn into_visitor() -> Self::Visitor {
|
||||
StaticDecodeAsTypeVisitor(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
// Make it easy to convert types into Static where required.
|
||||
impl<T> From<T> for Static<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Static<T> is just a marker type and should be as transparent as possible:
|
||||
impl<T> std::ops::Deref for Static<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for Static<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
@@ -1,136 +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.
|
||||
|
||||
//! The "default" Substrate/Polkadot UncheckedExtrinsic.
|
||||
//! This is used in codegen for runtime API calls.
|
||||
//!
|
||||
//! The inner bytes represent the encoded extrinsic expected by the
|
||||
//! runtime APIs. Deriving `EncodeAsType` would lead to the inner
|
||||
//! bytes to be re-encoded (length prefixed).
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, Visitor};
|
||||
|
||||
use super::{Encoded, Static};
|
||||
|
||||
/// The unchecked extrinsic from substrate.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Encode)]
|
||||
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>(
|
||||
Static<Encoded>,
|
||||
#[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>,
|
||||
);
|
||||
|
||||
impl<Address, Call, Signature, Extra> UncheckedExtrinsic<Address, Call, Signature, Extra> {
|
||||
/// Construct a new [`UncheckedExtrinsic`].
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Self(Static(Encoded(bytes)), PhantomData)
|
||||
}
|
||||
|
||||
/// Get the bytes of the encoded extrinsic.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
self.0 .0 .0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> Decode
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
// The bytes for an UncheckedExtrinsic are first a compact
|
||||
// encoded length, and then the bytes following. This is the
|
||||
// same encoding as a Vec, so easiest ATM is just to decode
|
||||
// into that, and then encode the vec bytes to get our extrinsic
|
||||
// bytes, which we save into an `Encoded` to preserve as-is.
|
||||
let xt_vec: Vec<u8> = Decode::decode(input)?;
|
||||
Ok(UncheckedExtrinsic::new(xt_vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsType
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn encode_as_type_to(
|
||||
&self,
|
||||
type_id: u32,
|
||||
types: &scale_info::PortableRegistry,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.0.encode_as_type_to(type_id, types, out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> From<Vec<u8>>
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn from(bytes: Vec<u8>) -> Self {
|
||||
UncheckedExtrinsic::new(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> From<UncheckedExtrinsic<Address, Call, Signature, Extra>>
|
||||
for Vec<u8>
|
||||
{
|
||||
fn from(bytes: UncheckedExtrinsic<Address, Call, Signature, Extra>) -> Self {
|
||||
bytes.0 .0 .0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>(
|
||||
PhantomData<(Address, Call, Signature, Extra)>,
|
||||
);
|
||||
|
||||
impl<Address, Call, Signature, Extra> Visitor
|
||||
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>
|
||||
{
|
||||
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>;
|
||||
type Error = scale_decode::Error;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
type_id: scale_decode::visitor::TypeId,
|
||||
types: &'info scale_info::PortableRegistry,
|
||||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
|
||||
DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id.0, types))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> IntoVisitor
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
type Visitor = UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>;
|
||||
|
||||
fn into_visitor() -> Self::Visitor {
|
||||
UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn unchecked_extrinsic_encoding() {
|
||||
// A tx is basically some bytes with a compact length prefix; ie an encoded vec:
|
||||
let tx_bytes = vec![1u8, 2, 3].encode();
|
||||
|
||||
let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone());
|
||||
let encoded_tx_bytes = unchecked_extrinsic.encode();
|
||||
|
||||
// The encoded representation must not alter the provided bytes.
|
||||
assert_eq!(tx_bytes, encoded_tx_bytes);
|
||||
|
||||
// However, for decoding we expect to be able to read the extrinsic from the wire
|
||||
// which would be length prefixed.
|
||||
let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap();
|
||||
let decoded_tx_bytes = decoded_tx.bytes();
|
||||
let encoded_tx_bytes = decoded_tx.encode();
|
||||
|
||||
assert_eq!(decoded_tx_bytes, encoded_tx_bytes);
|
||||
// Ensure we can decode the tx and fetch only the tx bytes.
|
||||
assert_eq!(vec![1, 2, 3], encoded_tx_bytes);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +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::PhantomDataSendSync;
|
||||
use codec::{Compact, Decode, DecodeAll, Encode};
|
||||
use derivative::Derivative;
|
||||
use scale_decode::{IntoVisitor, Visitor};
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
|
||||
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
|
||||
/// access the real type `T` [`Self::try_decode`] needs to be used.
|
||||
// Dev notes:
|
||||
//
|
||||
// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs).
|
||||
// - The encoded bytes will be a compact encoded length followed by that number of bytes.
|
||||
// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself.
|
||||
// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec<u8>`, but we need a custom [`EncodeAsType`]
|
||||
// and [`Visitor`] implementation to encode and decode based on TypeInfo.
|
||||
#[derive(Derivative, Encode, Decode)]
|
||||
#[derivative(
|
||||
Debug(bound = ""),
|
||||
Clone(bound = ""),
|
||||
PartialEq(bound = ""),
|
||||
Eq(bound = ""),
|
||||
Default(bound = ""),
|
||||
Hash(bound = "")
|
||||
)]
|
||||
pub struct WrapperKeepOpaque<T> {
|
||||
data: Vec<u8>,
|
||||
_phantom: PhantomDataSendSync<T>,
|
||||
}
|
||||
|
||||
impl<T> WrapperKeepOpaque<T> {
|
||||
/// Try to decode the wrapped type from the inner `data`.
|
||||
///
|
||||
/// Returns `None` if the decoding failed.
|
||||
pub fn try_decode(&self) -> Option<T>
|
||||
where
|
||||
T: Decode,
|
||||
{
|
||||
T::decode_all(&mut &self.data[..]).ok()
|
||||
}
|
||||
|
||||
/// Returns the length of the encoded `T`.
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Returns the encoded data.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Create from the given encoded `data`.
|
||||
pub fn from_encoded(data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from some raw value by encoding it.
|
||||
pub fn from_value(value: T) -> Self
|
||||
where
|
||||
T: Encode,
|
||||
{
|
||||
Self {
|
||||
data: value.encode(),
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EncodeAsType for WrapperKeepOpaque<T> {
|
||||
fn encode_as_type_to(
|
||||
&self,
|
||||
type_id: u32,
|
||||
types: &scale_info::PortableRegistry,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
use scale_encode::error::{Error, ErrorKind, Kind};
|
||||
|
||||
let Some(ty) = types.resolve(type_id) else {
|
||||
return Err(Error::new(ErrorKind::TypeNotFound(type_id)));
|
||||
};
|
||||
|
||||
// Do a basic check that the target shape lines up.
|
||||
let scale_info::TypeDef::Composite(_) = &ty.type_def else {
|
||||
return Err(Error::new(ErrorKind::WrongShape {
|
||||
actual: Kind::Struct,
|
||||
expected: type_id,
|
||||
}));
|
||||
};
|
||||
|
||||
// Check that the name also lines up.
|
||||
if ty.path.ident().as_deref() != Some("WrapperKeepOpaque") {
|
||||
return Err(Error::new(ErrorKind::WrongShape {
|
||||
actual: Kind::Struct,
|
||||
expected: type_id,
|
||||
}));
|
||||
}
|
||||
|
||||
// Just blat the bytes out.
|
||||
self.data.encode_to(out);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WrapperKeepOpaqueVisitor<T>(std::marker::PhantomData<T>);
|
||||
impl<T> Visitor for WrapperKeepOpaqueVisitor<T> {
|
||||
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
|
||||
type Error = scale_decode::Error;
|
||||
|
||||
fn visit_composite<'scale, 'info>(
|
||||
self,
|
||||
value: &mut scale_decode::visitor::types::Composite<'scale, 'info>,
|
||||
_type_id: scale_decode::visitor::TypeId,
|
||||
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
|
||||
use scale_decode::error::{Error, ErrorKind};
|
||||
|
||||
if value.path().ident().as_deref() != Some("WrapperKeepOpaque") {
|
||||
return Err(Error::custom_str(
|
||||
"Type to decode is not 'WrapperTypeKeepOpaque'",
|
||||
));
|
||||
}
|
||||
if value.remaining() != 2 {
|
||||
return Err(Error::new(ErrorKind::WrongLength {
|
||||
actual_len: value.remaining(),
|
||||
expected_len: 2,
|
||||
}));
|
||||
}
|
||||
|
||||
// The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes.
|
||||
let Compact(len) = value
|
||||
.decode_item(Compact::<u32>::into_visitor())
|
||||
.expect("length checked")?;
|
||||
let field = value.next().expect("length checked")?;
|
||||
|
||||
// Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field.
|
||||
if field.bytes().len() != len as usize {
|
||||
return Err(Error::custom_str("WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len"));
|
||||
}
|
||||
|
||||
Ok(WrapperKeepOpaque {
|
||||
data: field.bytes().to_vec(),
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoVisitor for WrapperKeepOpaque<T> {
|
||||
type Visitor = WrapperKeepOpaqueVisitor<T>;
|
||||
fn into_visitor() -> Self::Visitor {
|
||||
WrapperKeepOpaqueVisitor(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs
|
||||
// and used for tests to check that we can work with the expected TypeInfo without needing to import
|
||||
// the frame_support crate, which has quite a lot of dependencies.
|
||||
impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
|
||||
type Identity = Self;
|
||||
fn type_info() -> scale_info::Type {
|
||||
use scale_info::{build::Fields, meta_type, Path, Type, TypeParameter};
|
||||
|
||||
Type::builder()
|
||||
.path(Path::new("WrapperKeepOpaque", module_path!()))
|
||||
.type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
|
||||
.composite(
|
||||
Fields::unnamed()
|
||||
.field(|f| f.compact::<u32>())
|
||||
.field(|f| f.ty::<T>().type_name("T")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a type definition, return type ID and registry representing it.
|
||||
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
|
||||
let m = scale_info::MetaType::new::<T>();
|
||||
let mut types = scale_info::Registry::new();
|
||||
let id = types.register_type(&m);
|
||||
let portable_registry: scale_info::PortableRegistry = types.into();
|
||||
(id.id, portable_registry)
|
||||
}
|
||||
|
||||
fn roundtrips_like_scale_codec<T>(t: T)
|
||||
where
|
||||
T: EncodeAsType
|
||||
+ DecodeAsType
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ PartialEq
|
||||
+ std::fmt::Debug
|
||||
+ scale_info::TypeInfo
|
||||
+ 'static,
|
||||
{
|
||||
let (type_id, types) = make_type::<T>();
|
||||
|
||||
let scale_codec_encoded = t.encode();
|
||||
let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
scale_codec_encoded, encode_as_type_encoded,
|
||||
"encoded bytes should match"
|
||||
);
|
||||
|
||||
let decode_as_type_bytes = &mut &*scale_codec_encoded;
|
||||
let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types)
|
||||
.expect("decode-as-type decodes");
|
||||
|
||||
let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
|
||||
let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
|
||||
|
||||
assert!(
|
||||
decode_as_type_bytes.is_empty(),
|
||||
"no bytes should remain in decode-as-type impl"
|
||||
);
|
||||
assert!(
|
||||
decode_scale_codec_bytes.is_empty(),
|
||||
"no bytes should remain in codec-decode impl"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoded_as_type, decoded_scale_codec,
|
||||
"decoded values should match"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_keep_opaque_roundtrips_ok() {
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user