Rework Subxt API to support offline and dynamic transactions (#593)

* WIP API changes

* debug impls

* Get main crate compiling with first round of changes

* Some tidy up

* Add WithExtrinsicParams, and have SubstrateConfig + PolkadotConfig, not DefaultConfig

* move transaction into extrinsic folder

* Add runtime updates back to OnlineClient

* rework to be 'client first' to fit better with storage + events

* add support for events to Client

* tidy dupe trait bound

* Wire storage into client, but need to remove static reliance

* various tidy up and start stripping codegen to remove bits we dont need now

* First pass updating calls and constants codegen

* WIP storage client updates

* First pass migrated runtime storage over to new format

* pass over codegen to generate StorageAddresses and throw other stuff out

* don't need a Call trait any more

* shuffle things around a bit

* Various proc_macro fixes to get 'cargo check' working

* organise what's exposed from subxt

* Get first example working; balance_transfer_with_params

* get balance_transfer example compiling

* get concurrent_storage_requests.rs example compiling

* get fetch_all_accounts example compiling

* get a bunch more of the examples compiling

* almost get final example working; type mismatch to look into

* wee tweaks

* move StorageAddress to separate file

* pass Defaultable/Iterable info to StorageAddress in codegen

* fix storage validation ne, and partial run through example code

* Remove static iteration and strip a generic param from everything

* fix doc tests in subxt crate

* update test utils and start fixing frame tests

* fix frame staking tests

* fix the rest of the test compile issues, Borrow on storage values

* cargo fmt

* remove extra logging during tests

* Appease clippy and no more need for into_iter on events

* cargo fmt

* fix dryRun tests by waiting for blocks

* wait for blocks instead of sleeping or other test hacks

* cargo fmt

* Fix doc links

* Traitify StorageAddress

* remove out-of-date doc comments

* optimise decoding storage a little

* cleanup tx stuff, trait for TxPayload, remove Err type param and decode at runtime

* clippy fixes

* fix doc links

* fix doc example

* constant address trait for consistency

* fix a typo and remove EncodeWithMetadata stuff

* Put EventDetails behind a proper interface and allow decoding into top level event, too

* fix docs

* tweak StorageAddress docs

* re-export StorageAddress at root for consistency

* fix clippy things

* Add support for dynamic values

* fix double encoding of storage map key after refactor

* clippy fix

* Fixes and add a dynamic usage example (needs new scale_value release)

* bump scale_value version

* cargo fmt

* Tweak event bits

* cargo fmt

* Add a test and bump scale-value to 0.4.0 to support this

* remove unnecessary vec from dynamic example

* Various typo/grammar fixes

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>

* Address PR nits

* Undo accidental rename in changelog

* Small PR nits/tidyups

* fix tests; codegen change against latest substrate

* tweak storage address util names

* move error decoding to DecodeError and expose

* impl some basic traits on the extrinsic param builder

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2022-08-08 11:55:20 +01:00
committed by GitHub
parent 7a09ac6cd7
commit e48f0e3b1d
84 changed files with 23097 additions and 35863 deletions
+61 -95
View File
@@ -7,67 +7,29 @@
//! This is used behind the scenes by various `subxt` APIs, but can
//! also be used directly.
//!
//! # Examples
//! # Example
//!
//! ## Fetch Storage
//! Fetching storage keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//! use subxt::{ PolkadotConfig, OnlineClient, storage::StorageKey };
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // Storage prefix is `twox_128("System") ++ twox_128("ExtrinsicCount")`.
//! let key = StorageKeyPrefix::new::<polkadot::system::storage::ExtrinsicCount>()
//! .to_storage_key();
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//!
//! let result = rpc.storage(&key, None).await.unwrap();
//! println!("Storage result: {:?}", result);
//! # }
//! ```
//!
//! ## Fetch Keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! let key = StorageKeyPrefix::new::<polkadot::xcm_pallet::storage::VersionNotifiers>()
//! .to_storage_key();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//! let key = polkadot::storage()
//! .xcm_pallet()
//! .version_notifiers_root()
//! .to_bytes();
//!
//! // Fetch up to 10 keys.
//! let keys = rpc
//! .storage_keys_paged(Some(key), 10, None, None)
//! let keys = api
//! .rpc()
//! .storage_keys_paged(&key, 10, None, None)
//! .await
//! .unwrap();
//!
@@ -88,10 +50,10 @@ use std::{
};
use crate::{
error::BasicError,
error::Error,
utils::PhantomDataSendSync,
Config,
Metadata,
PhantomDataSendSync,
};
use codec::{
Decode,
@@ -198,7 +160,7 @@ pub type SystemProperties = serde_json::Map<String, serde_json::Value>;
/// must be kept compatible with that type from the target substrate version.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SubstrateTransactionStatus<Hash, BlockHash> {
pub enum SubstrateTxStatus<Hash, BlockHash> {
/// Transaction is part of the future queue.
Future,
/// Transaction is part of the ready queue.
@@ -324,13 +286,13 @@ impl<T: Config> Rpc<T> {
}
}
/// Fetch a storage key
/// Fetch the raw bytes for a given storage key
pub async fn storage(
&self,
key: &StorageKey,
key: &[u8],
hash: Option<T::Hash>,
) -> Result<Option<StorageData>, BasicError> {
let params = rpc_params![key, hash];
) -> Result<Option<StorageData>, Error> {
let params = rpc_params![to_hex(key), hash];
let data = self.client.request("state_getStorage", params).await?;
Ok(data)
}
@@ -340,12 +302,13 @@ impl<T: Config> Rpc<T> {
/// If `start_key` is passed, return next keys in storage in lexicographic order.
pub async fn storage_keys_paged(
&self,
key: Option<StorageKey>,
key: &[u8],
count: u32,
start_key: Option<StorageKey>,
start_key: Option<&[u8]>,
hash: Option<T::Hash>,
) -> Result<Vec<StorageKey>, BasicError> {
let params = rpc_params![key, count, start_key, hash];
) -> Result<Vec<StorageKey>, Error> {
let start_key = start_key.map(to_hex);
let params = rpc_params![to_hex(key), count, start_key, hash];
let data = self.client.request("state_getKeysPaged", params).await?;
Ok(data)
}
@@ -353,10 +316,11 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
from: T::Hash,
to: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, from, to];
self.client
.request("state_queryStorage", params)
@@ -367,9 +331,10 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage_at(
&self,
keys: &[StorageKey],
keys: impl IntoIterator<Item = &[u8]>,
at: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, at];
self.client
.request("state_queryStorageAt", params)
@@ -378,7 +343,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the genesis hash
pub async fn genesis_hash(&self) -> Result<T::Hash, BasicError> {
pub async fn genesis_hash(&self) -> Result<T::Hash, Error> {
let block_zero = 0u32;
let params = rpc_params![block_zero];
let genesis_hash: Option<T::Hash> =
@@ -387,7 +352,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the metadata
pub async fn metadata(&self) -> Result<Metadata, BasicError> {
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: Bytes = self
.client
.request("state_getMetadata", rpc_params![])
@@ -398,7 +363,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system properties
pub async fn system_properties(&self) -> Result<SystemProperties, BasicError> {
pub async fn system_properties(&self) -> Result<SystemProperties, Error> {
Ok(self
.client
.request("system_properties", rpc_params![])
@@ -406,22 +371,22 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system health
pub async fn system_health(&self) -> Result<Health, BasicError> {
pub async fn system_health(&self) -> Result<Health, Error> {
Ok(self.client.request("system_health", rpc_params![]).await?)
}
/// Fetch system chain
pub async fn system_chain(&self) -> Result<String, BasicError> {
pub async fn system_chain(&self) -> Result<String, Error> {
Ok(self.client.request("system_chain", rpc_params![]).await?)
}
/// Fetch system name
pub async fn system_name(&self) -> Result<String, BasicError> {
pub async fn system_name(&self) -> Result<String, Error> {
Ok(self.client.request("system_name", rpc_params![]).await?)
}
/// Fetch system version
pub async fn system_version(&self) -> Result<String, BasicError> {
pub async fn system_version(&self) -> Result<String, Error> {
Ok(self.client.request("system_version", rpc_params![]).await?)
}
@@ -429,7 +394,7 @@ impl<T: Config> Rpc<T> {
pub async fn system_account_next_index(
&self,
account: &T::AccountId,
) -> Result<T::Index, BasicError> {
) -> Result<T::Index, Error> {
Ok(self
.client
.request("system_accountNextIndex", rpc_params![account])
@@ -440,7 +405,7 @@ impl<T: Config> Rpc<T> {
pub async fn header(
&self,
hash: Option<T::Hash>,
) -> Result<Option<T::Header>, BasicError> {
) -> Result<Option<T::Header>, Error> {
let params = rpc_params![hash];
let header = self.client.request("chain_getHeader", params).await?;
Ok(header)
@@ -450,14 +415,14 @@ impl<T: Config> Rpc<T> {
pub async fn block_hash(
&self,
block_number: Option<BlockNumber>,
) -> Result<Option<T::Hash>, BasicError> {
) -> Result<Option<T::Hash>, Error> {
let params = rpc_params![block_number];
let block_hash = self.client.request("chain_getBlockHash", params).await?;
Ok(block_hash)
}
/// Get a block hash of the latest finalized block
pub async fn finalized_head(&self) -> Result<T::Hash, BasicError> {
pub async fn finalized_head(&self) -> Result<T::Hash, Error> {
let hash = self
.client
.request("chain_getFinalizedHead", rpc_params![])
@@ -469,7 +434,7 @@ impl<T: Config> Rpc<T> {
pub async fn block(
&self,
hash: Option<T::Hash>,
) -> Result<Option<ChainBlock<T>>, BasicError> {
) -> Result<Option<ChainBlock<T>>, Error> {
let params = rpc_params![hash];
let block = self.client.request("chain_getBlock", params).await?;
Ok(block)
@@ -483,7 +448,7 @@ impl<T: Config> Rpc<T> {
pub async fn block_stats(
&self,
block_hash: T::Hash,
) -> Result<Option<BlockStats>, BasicError> {
) -> Result<Option<BlockStats>, Error> {
let params = rpc_params![block_hash];
let stats = self.client.request("dev_getBlockStats", params).await?;
Ok(stats)
@@ -492,9 +457,10 @@ impl<T: Config> Rpc<T> {
/// Get proof of storage entries at a specific block's state.
pub async fn read_proof(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
hash: Option<T::Hash>,
) -> Result<ReadProof<T::Hash>, BasicError> {
) -> Result<ReadProof<T::Hash>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, hash];
let proof = self.client.request("state_getReadProof", params).await?;
Ok(proof)
@@ -504,7 +470,7 @@ impl<T: Config> Rpc<T> {
pub async fn runtime_version(
&self,
at: Option<T::Hash>,
) -> Result<RuntimeVersion, BasicError> {
) -> Result<RuntimeVersion, Error> {
let params = rpc_params![at];
let version = self
.client
@@ -514,7 +480,7 @@ impl<T: Config> Rpc<T> {
}
/// Subscribe to blocks.
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, BasicError> {
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -530,7 +496,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to finalized blocks.
pub async fn subscribe_finalized_blocks(
&self,
) -> Result<Subscription<T::Header>, BasicError> {
) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -545,7 +511,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to runtime version updates that produce changes in the metadata.
pub async fn subscribe_runtime_version(
&self,
) -> Result<Subscription<RuntimeVersion>, BasicError> {
) -> Result<Subscription<RuntimeVersion>, Error> {
let subscription = self
.client
.subscribe(
@@ -561,7 +527,7 @@ impl<T: Config> Rpc<T> {
pub async fn submit_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<T::Hash, BasicError> {
) -> Result<T::Hash, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let xt_hash = self
@@ -575,8 +541,7 @@ impl<T: Config> Rpc<T> {
pub async fn watch_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<Subscription<SubstrateTransactionStatus<T::Hash, T::Hash>>, BasicError>
{
) -> Result<Subscription<SubstrateTxStatus<T::Hash, T::Hash>>, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let subscription = self
@@ -596,14 +561,14 @@ impl<T: Config> Rpc<T> {
key_type: String,
suri: String,
public: Bytes,
) -> Result<(), BasicError> {
) -> Result<(), Error> {
let params = rpc_params![key_type, suri, public];
self.client.request("author_insertKey", params).await?;
Ok(())
}
/// Generate new session keys and returns the corresponding public keys.
pub async fn rotate_keys(&self) -> Result<Bytes, BasicError> {
pub async fn rotate_keys(&self) -> Result<Bytes, Error> {
Ok(self
.client
.request("author_rotateKeys", rpc_params![])
@@ -615,10 +580,7 @@ impl<T: Config> Rpc<T> {
/// `session_keys` is the SCALE encoded session keys object from the runtime.
///
/// Returns `true` iff all private keys could be found.
pub async fn has_session_keys(
&self,
session_keys: Bytes,
) -> Result<bool, BasicError> {
pub async fn has_session_keys(&self, session_keys: Bytes) -> Result<bool, Error> {
let params = rpc_params![session_keys];
Ok(self.client.request("author_hasSessionKeys", params).await?)
}
@@ -630,7 +592,7 @@ impl<T: Config> Rpc<T> {
&self,
public_key: Bytes,
key_type: String,
) -> Result<bool, BasicError> {
) -> Result<bool, Error> {
let params = rpc_params![public_key, key_type];
Ok(self.client.request("author_hasKey", params).await?)
}
@@ -642,8 +604,8 @@ impl<T: Config> Rpc<T> {
&self,
encoded_signed: &[u8],
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, BasicError> {
let params = rpc_params![format!("0x{}", hex::encode(encoded_signed)), at];
) -> Result<ApplyExtrinsicResult, Error> {
let params = rpc_params![to_hex(encoded_signed), at];
let result_bytes: Bytes = self.client.request("system_dryRun", params).await?;
let data: ApplyExtrinsicResult =
codec::Decode::decode(&mut result_bytes.0.as_slice())?;
@@ -669,6 +631,10 @@ async fn ws_transport(url: &str) -> Result<(WsSender, WsReceiver), RpcError> {
.map_err(|e| RpcError::Transport(e.into()))
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
#[cfg(test)]
mod test {
use super::*;