mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 01:51:09 +00:00
Example: How to connect to parachain (#1043)
* parachain rpc lists * guide almost done * add the 3rd config * subscribe to block with configs * delete table file * spaces instead of tabs * remove original ajuna example * zombienet setup * nft minting example * include port, use different names * link the example from the book * format book * add config creation to book, simplify example structure * fix the nft creation script * fix doc ref * fixing links to foreign crates * fix table formatting * include nits * move more docs to book, and simplify parachain-example * another pass over docs and link to exampels from guide * nit: adjust comment to numbers * teeny README fix for parachain-example * fix command in readme * add CI for examples and fix parachain-example bug I left in * add target arch * cargo fmt * make CI not fail * remove index from docs --------- Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
@@ -24,7 +24,7 @@ env:
|
||||
WASM_BINDGEN_TEST_TIMEOUT: 60
|
||||
|
||||
jobs:
|
||||
build:
|
||||
check:
|
||||
name: Cargo check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -83,6 +83,34 @@ jobs:
|
||||
- name: Cargo hack; check each feature/crate on its own
|
||||
run: cargo hack --exclude subxt --exclude subxt-signer --exclude subxt-lightclient --exclude-all-features --each-feature check --workspace
|
||||
|
||||
# Check examples, which aren't a part of the workspace and so are otherwise missed:
|
||||
- name: Cargo check examples
|
||||
run: |
|
||||
cargo check --manifest-path examples/parachain-example/Cargo.toml
|
||||
|
||||
wasm_check:
|
||||
name: Cargo check (WASM)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f # v2.5.1
|
||||
|
||||
# Check WASM examples, which aren't a part of the workspace and so are otherwise missed:
|
||||
- name: Cargo check WASM examples
|
||||
run: |
|
||||
cargo check --manifest-path examples/wasm-example/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
fmt:
|
||||
name: Cargo fmt
|
||||
runs-on: ubuntu-latest
|
||||
@@ -141,7 +169,7 @@ jobs:
|
||||
command: test
|
||||
args: --doc
|
||||
|
||||
nonwasm_tests:
|
||||
tests:
|
||||
name: "Test non-wasm"
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
@@ -175,7 +203,7 @@ jobs:
|
||||
command: nextest
|
||||
args: run --workspace
|
||||
|
||||
nonwasm_light_client_tests:
|
||||
light_client_tests:
|
||||
name: "Test Light Client"
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
timeout-minutes: 25
|
||||
|
||||
@@ -4,3 +4,5 @@
|
||||
cargo-timing*
|
||||
/examples/wasm-example/dist
|
||||
/examples/wasm-example/target
|
||||
/examples/parachain-example/target
|
||||
/examples/parachain-example/metadata/target
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ exclude = [
|
||||
"testing/wasm-rpc-tests",
|
||||
"testing/wasm-lightclient-tests",
|
||||
"signer/wasm-tests",
|
||||
"examples/wasm-example"
|
||||
"examples/wasm-example",
|
||||
"examples/parachain-example"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
Generated
+4587
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "parachain-example"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
subxt = { path = "../../subxt" }
|
||||
subxt-signer = { path = "../../signer", features = ["subxt"] }
|
||||
futures = { version = "0.3.27", default-features = false, features = ["std"] }
|
||||
tokio = { version = "1.28", features = ["macros", "time", "rt-multi-thread"] }
|
||||
sp-core = "21.0.0"
|
||||
sp-runtime = "24.0.0"
|
||||
codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false }
|
||||
scale-decode = "0.7.0"
|
||||
scale-encode = "0.3.0"
|
||||
@@ -0,0 +1,78 @@
|
||||
# parachain-example
|
||||
|
||||
This example showcases working with Subxt and Zombienet to try out connecting to a locally deployed parachain, here
|
||||
["Statemint"](https://parachains.info/details/statemint), also known as "Asset Hub".
|
||||
|
||||
## Running the example
|
||||
|
||||
### 1. Install `zombienet`
|
||||
|
||||
[Zombienet](https://github.com/paritytech/zombienet) is a tool for quickly spinning up a (local) blockchain
|
||||
network. We will use it to start up a local Asset Hub for us.
|
||||
|
||||
Please follow the install guide in the [zombienet github repo](https://github.com/paritytech/zombienet) to
|
||||
install it.
|
||||
|
||||
### 2. `polkadot`
|
||||
|
||||
We need a relay chain. Build the polkadot binary from the [polkadot github repo](https://github.com/paritytech/polkadot)
|
||||
and install it in your path:
|
||||
|
||||
```txt
|
||||
git clone https://github.com/paritytech/polkadot.git
|
||||
cd polkadot
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
### 3. `polkadot-parachain`
|
||||
|
||||
The Asset Hub is part of the [cumulus github repo](https://github.com/paritytech/cumulus), an SDK for developing
|
||||
parachains. Building the cumulus workspace produces a binary called `polkadot-parachain` which can be used to run
|
||||
Asset Hub nodes.
|
||||
|
||||
```txt
|
||||
git clone https://github.com/paritytech/cumulus.git
|
||||
cd cumulus
|
||||
cargo install --path polkadot-parachain
|
||||
```
|
||||
|
||||
### 4. Run the parachain locally
|
||||
|
||||
With these binaries installed, Zombienet can now get the parachain running locally from a configuration file, `asset-hub-zombienet.toml`
|
||||
in this case. We need to have at least 2 validator nodes running via the `polkadot` binary, and an Asset Hub node running via the
|
||||
`polkadot-parachain` binary. Zombienet starts these up, and gets the parachain registered with the validator nodes for us. To do that,
|
||||
run:
|
||||
|
||||
```txt
|
||||
zombienet -p native spawn asset-hub-zombienet.toml
|
||||
```
|
||||
|
||||
Zombienet uses Kubernetes by default, but we can use it without Kubernetes by providing the `-p native` flag.
|
||||
|
||||
You might have noticed that we use `chain = "rococo-local"` in the `asset-hub-zombienet.toml` file for the relay chain. This is just to
|
||||
make the epoch time shorter and should have no effect on your interactions with the parachain. Polkadot / Kusama / Rococo have different
|
||||
epoch times of `24h` / `2h` / `2min` respectively.
|
||||
|
||||
### 5. Run the example
|
||||
|
||||
The parachain is only registered after the first epoch. So after the previous step, we need to wait 2 minutes until the parachain becomes
|
||||
interactive and produces blocks. At this point, we can run:
|
||||
|
||||
```
|
||||
cargo run --bin parachain-example
|
||||
```
|
||||
|
||||
To run our example code.
|
||||
|
||||
## Dev notes
|
||||
|
||||
We can obtain the metadata for Statemint via the [subxt cli](https://crates.io/crates/subxt-cli) tool, like so:
|
||||
|
||||
```txt
|
||||
subxt metadata --url wss://polkadot-asset-hub-rpc.polkadot.io:443 > statemint_metadata.scale
|
||||
```
|
||||
|
||||
It is important to explicitly specify the port as `443`.
|
||||
|
||||
One way to find a suitable URL to obtain this from is by looking through the sidebar on [Polkadot.js](https://polkadot.js.org/apps/)
|
||||
to find the Asset Hub entry, and seeing which RPC node URLs it uses.
|
||||
@@ -0,0 +1,26 @@
|
||||
[relaychain]
|
||||
default_image = "docker.io/parity/polkadot:latest"
|
||||
default_command = "polkadot"
|
||||
default_args = ["-lparachain=debug"]
|
||||
|
||||
chain = "rococo-local"
|
||||
|
||||
[[relaychain.nodes]]
|
||||
name = "alice"
|
||||
validator = true
|
||||
|
||||
[[relaychain.nodes]]
|
||||
name = "bob"
|
||||
validator = true
|
||||
|
||||
[[parachains]]
|
||||
id = 100
|
||||
chain = "asset-hub-polkadot-local"
|
||||
|
||||
[parachains.collator]
|
||||
name = "collator01"
|
||||
image = "docker.io/parity/polkadot-parachain:latest"
|
||||
ws_port = 42069
|
||||
command = "polkadot-parachain"
|
||||
args = ["-lparachain=debug"]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use subxt::{
|
||||
Config, PolkadotConfig, SubstrateConfig,
|
||||
utils::{AccountId32, MultiAddress},
|
||||
OnlineClient,
|
||||
};
|
||||
use subxt_signer::sr25519::dev::{self};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "statemint_metadata.scale")]
|
||||
pub mod statemint {}
|
||||
|
||||
/// Custom config that works with Statemint:
|
||||
pub enum StatemintConfig {}
|
||||
|
||||
impl Config for StatemintConfig {
|
||||
type Hash = <PolkadotConfig as Config>::Hash;
|
||||
type AccountId = <PolkadotConfig as Config>::AccountId;
|
||||
type Address = <PolkadotConfig as Config>::Address;
|
||||
type Signature = <PolkadotConfig as Config>::Signature;
|
||||
type Hasher = <PolkadotConfig as Config>::Hasher;
|
||||
type Header = <PolkadotConfig as Config>::Header;
|
||||
type ExtrinsicParams = <SubstrateConfig as Config>::ExtrinsicParams;
|
||||
}
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
if let Err(err) = run().await {
|
||||
eprintln!("{err}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// (the port 42069 is specified in the asset-hub-zombienet.toml)
|
||||
let api = OnlineClient::<StatemintConfig>::from_url("ws://127.0.0.1:42069").await?;
|
||||
println!("Connection with parachain established.");
|
||||
|
||||
let alice: MultiAddress<AccountId32, ()> = dev::alice().public_key().into();
|
||||
let alice_pair_signer = dev::alice();
|
||||
|
||||
const COLLECTION_ID: u32 = 12;
|
||||
const NTF_ID: u32 = 234;
|
||||
|
||||
// create a collection with id `12`
|
||||
let collection_creation_tx = statemint::tx()
|
||||
.uniques()
|
||||
.create(COLLECTION_ID, alice.clone());
|
||||
let _collection_creation_events = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&collection_creation_tx, &alice_pair_signer)
|
||||
.await
|
||||
.map(|e| {
|
||||
println!("Collection creation submitted, waiting for transaction to be finalized...");
|
||||
e
|
||||
})?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
println!("Collection created.");
|
||||
|
||||
// create an nft in that collection with id `234`
|
||||
let nft_creation_tx = statemint::tx()
|
||||
.uniques()
|
||||
.mint(COLLECTION_ID, NTF_ID, alice.clone());
|
||||
let _nft_creation_events = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&nft_creation_tx, &alice_pair_signer)
|
||||
.await
|
||||
.map(|e| {
|
||||
println!("NFT creation submitted, waiting for transaction to be finalized...");
|
||||
e
|
||||
})?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
println!("NFT created.");
|
||||
|
||||
// check in storage, that alice is the official owner of the NFT:
|
||||
let nft_owner_storage_query = statemint::storage().uniques().asset(COLLECTION_ID, NTF_ID);
|
||||
let nft_storage_details = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&nft_owner_storage_query)
|
||||
.await?
|
||||
.ok_or("The NFT should have an owner (alice)")?;
|
||||
|
||||
// make sure that alice is the owner of the NFT:
|
||||
assert_eq!(nft_storage_details.owner, dev::alice().public_key().into());
|
||||
println!("Storage Item Details: {:?}", nft_storage_details);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Binary file not shown.
Generated
+973
-53
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
# wasm-example
|
||||
|
||||
This is a small WASM app using the Yew UI framework to showcase how to use Subxt's features in a WASM environment.
|
||||
|
||||
To run the app locally we first install Trunk, a WASM bundler:
|
||||
|
||||
```
|
||||
cargo install --locked trunk
|
||||
```
|
||||
|
||||
You need to have a local polkadot/substrate node with it's JSON-RPC HTTP server running at 127.0.0.1:9933 in order for the examples to be working.
|
||||
If you have a `polkadot` binary already, running this should be sufficient:
|
||||
|
||||
```
|
||||
polkadot --dev
|
||||
```
|
||||
|
||||
Then, in another terminal, run the app locally with:
|
||||
|
||||
```
|
||||
trunk serve --open
|
||||
```
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
//! This is a small WASM app using the Yew UI framework showcasing how to use Subxt's features in a WASM environment.
|
||||
//!
|
||||
//! To run the app locally use Trunk, a WASM bundler:
|
||||
//! ```
|
||||
//! cargo install --locked trunk
|
||||
//! ```
|
||||
//! Run the app locally:
|
||||
//! ```
|
||||
//! trunk serve --open
|
||||
//! ```
|
||||
//! You need to have a local polkadot/substrate node with it's JSON-RPC HTTP server running at 127.0.0.1:9933 in order for the examples to be working.
|
||||
//! Also make sure your browser supports WASM.
|
||||
use futures::{self, FutureExt};
|
||||
|
||||
use yew::prelude::*;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
use codec::Encode;
|
||||
use primitive_types::H256;
|
||||
use subxt::config::{Config, ExtrinsicParams};
|
||||
|
||||
// We don't need to construct this at runtime,
|
||||
// so an empty enum is appropriate:
|
||||
pub enum StatemintConfig {}
|
||||
|
||||
impl Config for StatemintConfig {
|
||||
type Hash = subxt::utils::H256;
|
||||
type AccountId = subxt::utils::AccountId32;
|
||||
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
|
||||
type Signature = subxt::utils::MultiSignature;
|
||||
type Hasher = subxt::config::substrate::BlakeTwo256;
|
||||
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
|
||||
type ExtrinsicParams = StatemintExtrinsicParams;
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct StatemintExtrinsicParams {
|
||||
extra_params: StatemintExtraParams,
|
||||
additional_params: StatemintAdditionalParams,
|
||||
}
|
||||
|
||||
impl ExtrinsicParams<H256> for StatemintExtrinsicParams {
|
||||
// We need these additional values that aren't otherwise
|
||||
// provided. Calls like api.tx().sign_and_submit_then_watch()
|
||||
// allow the user to provide an instance of these, so it's wise
|
||||
// to give this a nicer interface in reality:
|
||||
type OtherParams = (
|
||||
sp_core::H256,
|
||||
sp_runtime::generic::Era,
|
||||
ChargeAssetTxPayment,
|
||||
);
|
||||
|
||||
// Gather together all of the params we will need to encode:
|
||||
fn new(
|
||||
spec_version: u32,
|
||||
tx_version: u32,
|
||||
nonce: u64,
|
||||
genesis_hash: H256,
|
||||
other_params: Self::OtherParams,
|
||||
) -> Self {
|
||||
let (mortality_hash, era, charge) = other_params;
|
||||
|
||||
let extra_params = StatemintExtraParams { era, nonce, charge };
|
||||
let additional_params = StatemintAdditionalParams {
|
||||
spec_version,
|
||||
tx_version,
|
||||
genesis_hash,
|
||||
mortality_hash,
|
||||
};
|
||||
|
||||
Self {
|
||||
extra_params,
|
||||
additional_params,
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the relevant params when asked:
|
||||
fn encode_extra_to(&self, v: &mut Vec<u8>) {
|
||||
self.extra_params.encode_to(v);
|
||||
}
|
||||
fn encode_additional_to(&self, v: &mut Vec<u8>) {
|
||||
self.additional_params.encode_to(v);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct StatemintExtraParams {
|
||||
era: sp_runtime::generic::Era,
|
||||
nonce: u64,
|
||||
charge: ChargeAssetTxPayment,
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ChargeAssetTxPayment {
|
||||
#[codec(compact)]
|
||||
tip: u128,
|
||||
asset_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct StatemintAdditionalParams {
|
||||
spec_version: u32,
|
||||
tx_version: u32,
|
||||
genesis_hash: sp_core::H256,
|
||||
mortality_hash: sp_core::H256,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// With the config defined, it can be handed to Subxt as follows:
|
||||
let _client_fut = subxt::OnlineClient::<StatemintConfig>::new();
|
||||
}
|
||||
+12
-2
@@ -71,8 +71,9 @@
|
||||
//! recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a
|
||||
//! parachain node), you'll want to:
|
||||
//!
|
||||
//! 1. [Generate an interface](setup::codegen).
|
||||
//! 2. [Configure and instantiate the client](setup::client).
|
||||
//! 1. [Generate an interface](setup::codegen)
|
||||
//! 2. [Create a config](setup::config)
|
||||
//! 3. [Use the config to instantiate the client](setup::client)
|
||||
//!
|
||||
//! Follow the above links to learn more about each step.
|
||||
//!
|
||||
@@ -92,5 +93,14 @@
|
||||
//! - [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve
|
||||
//! data.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Some complete, self contained examples which are not a part of this guide:
|
||||
//!
|
||||
//! - [`parachain-example`](https://github.com/paritytech/subxt/tree/master/examples/parachain-example) is an example
|
||||
//! which uses Zombienet to spawn a parachain locally, and then connects to it using Subxt.
|
||||
//! - [`wasm-example`](https://github.com/paritytech/subxt/tree/master/examples/wasm-example) is an example of writing
|
||||
//! a Rust app that contains a Yew based UI, uses Subxt to interact with a chain, and compiles to WASM in order to
|
||||
//! run entirely in the browser.
|
||||
pub mod setup;
|
||||
pub mod usage;
|
||||
|
||||
@@ -11,14 +11,16 @@
|
||||
//!
|
||||
//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give
|
||||
//! the client certain information about how to interact with a node that isn't otherwise available
|
||||
//! or possible to include in the node metadata. Subxt ships out of the box with two default
|
||||
//! implementations:
|
||||
//! or possible to include in the node metadata.
|
||||
//!
|
||||
//! The [`crate::config::Config`] trait mimics the `frame_system::Config` trait and
|
||||
//! subxt ships out of the box with two default implementations:
|
||||
//!
|
||||
//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and
|
||||
//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate.
|
||||
//!
|
||||
//! The latter will generally work in many cases, but will need modifying if the chain you'd like to
|
||||
//! connect to has altered any of the details mentioned in [the trait](`crate::config::Config`).
|
||||
//! The latter will generally work in many cases, but [may need special customization](super::config) if
|
||||
//! the node differs in any of the types the [`Config`](crate::config::Config) trait wants to know about.
|
||||
//!
|
||||
//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it:
|
||||
//!
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//! # Creating a Config
|
||||
//!
|
||||
//! Subxt requires you to provide a type implementing [`crate::config::Config`] in order to connect to a node.
|
||||
//! The [`crate::config::Config`] trait for the most part mimics the `frame_system::Config` trait.
|
||||
//! For most use cases, you can just use one of the following Configs shipped with Subxt:
|
||||
//!
|
||||
//! - [`PolkadotConfig`](crate::config::PolkadotConfig) for talking to Polkadot nodes, and
|
||||
//! - [`SubstrateConfig`](crate::config::SubstrateConfig) for talking to generic nodes built with Substrate.
|
||||
//!
|
||||
//! # How to create a Config for a custom chain?
|
||||
//!
|
||||
//! Some chains may use config that is not compatible with our [`PolkadotConfig`](crate::config::PolkadotConfig) or
|
||||
//! [`SubstrateConfig`](crate::config::SubstrateConfig).
|
||||
//!
|
||||
//! We now walk through creating a [`crate::config::Config`] for a parachain, using the
|
||||
//! ["Statemint"](https://parachains.info/details/statemint) parachain, also known as "Asset Hub", as an example. It
|
||||
//! is currently (as of 2023-06-26) deployed on Polkadot and [Kusama (as "Statemine")](https://parachains.info/details/statemine).
|
||||
//!
|
||||
//! To construct a config, we need to investigate which types Statemint uses as `AccountId`, `Hasher`, etc.
|
||||
//! We need to take a look at the source code of Statemint and find out how it implements some substrate functionalities.
|
||||
//! Statemint (Polkadot Asset Hub) is part of the [Cumulus Github repository](https://github.com/paritytech/cumulus).
|
||||
//! The crate defining the parachains runtime can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot).
|
||||
//!
|
||||
//! ## Creating the `Config` from scratch
|
||||
//!
|
||||
//! Creating the config from scratch is the most arduous approach but also the most flexible, so first we'll walk through
|
||||
//! how to do this, and then we'll show how to simplify the process where possible.
|
||||
//!
|
||||
//! ### AccountId, Hash, Hasher and Header
|
||||
//!
|
||||
//! For these config types, we need to find out where the parachain runtime implements the `frame_system::Config` trait.
|
||||
//! Look for a code fragment like `impl frame_system::Config for Runtime { ... }` In the source code.
|
||||
//! For Statemint it looks like [this](https://github.com/paritytech/cumulus/blob/e2b7ad2061824f490c08df27a922c64f50accd6b/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L179)
|
||||
//! at the time of writing. The `AccountId`, `Hash` and `Header` types of the [frame_system::pallet::Config](https://docs.rs/frame-system/latest/frame_system/pallet/trait.Config.html)
|
||||
//! correspond to the ones we want to use for implementing [crate::Config]. In the Case of Statemint (Asset Hub) they are:
|
||||
//!
|
||||
//! - AccountId: [`sp_core::crypto::AccountId32`]
|
||||
//! - Hash: [`sp_core::H256`]
|
||||
//! - Hasher (type `Hashing` in [frame_system::pallet::Config](https://docs.rs/frame-system/latest/frame_system/pallet/trait.Config.html)): [`sp_runtime::traits::BlakeTwo256`]
|
||||
//! - Header: [`sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>`](sp_runtime::generic::Header)
|
||||
//!
|
||||
//! Subxt has its own versions of some of these types in order to avoid needing to pull in Substrate dependencies:
|
||||
//!
|
||||
//! - [`sp_core::crypto::AccountId32`] can be swapped with [`crate::utils::AccountId32`].
|
||||
//! - [`sp_core::H256`] is a re-export which subxt also provides as [`crate::config::substrate::H256`].
|
||||
//! - [`sp_runtime::traits::BlakeTwo256`] can be swapped with [`crate::config::substrate::BlakeTwo256`].
|
||||
//! - [`sp_runtime::generic::Header`] can be swapped with [`crate::config::substrate::SubstrateHeader`].
|
||||
//!
|
||||
//! Having a look at how those types are implemented can give some clues as to how to implement other custom types that
|
||||
//! you may need to use as part of your config.
|
||||
//!
|
||||
//! ### Address, Signature
|
||||
//!
|
||||
//! A Substrate runtime is typically constructed by using the [frame_support::construct_runtime](https://docs.rs/frame-support/latest/frame_support/macro.construct_runtime.html) macro.
|
||||
//! In this macro, we need to specify the type of an `UncheckedExtrinsic`. Most of the time, the `UncheckedExtrinsic` will be of the type
|
||||
//! [sp_runtime::generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>](sp_runtime::generic::UncheckedExtrinsic).
|
||||
//! The generic parameters `Address` and `Signature` specified when declaring the `UncheckedExtrinsic` type
|
||||
//! are the types for `Address` and `Signature` we should use when implementing the [crate::Config] trait. This information can
|
||||
//! also be obtained from the metadata (see [`frame_metadata::v15::ExtrinsicMetadata`]). In case of Statemint (Polkadot Asset Hub)
|
||||
//! we see the following types being used in `UncheckedExtrinsic`:
|
||||
//!
|
||||
//! - Address: [sp_runtime::MultiAddress<Self::AccountId, ()>](sp_runtime::MultiAddress)
|
||||
//! - Signature: [sp_runtime::MultiSignature]
|
||||
//!
|
||||
//! As above, Subxt has its own versions of these types that can be used instead to avoid pulling in Substrate dependencies.
|
||||
//! Using the Subxt versions also makes interacting with generated code (which uses them in some places) a little nicer:
|
||||
//!
|
||||
//! - [`sp_runtime::MultiAddress`] can be swapped with [`crate::utils::MultiAddress`].
|
||||
//! - [`sp_runtime::MultiSignature`] can be swapped with [`crate::utils::MultiSignature`].
|
||||
//!
|
||||
//! ### ExtrinsicParams
|
||||
//!
|
||||
//! Chains each have a set of "signed extensions" configured. Signed extensions provide a means to extend how transactions
|
||||
//! work. Each signed extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
|
||||
//! "additional" data which is included in the transaction signer payload, but not transmitted along with the transaction. On
|
||||
//! a node, signed extensions can then perform additional checks on the submitted transactions to ensure their validity.
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! In order to construct a valid implementation of the `ExtrinsicParams` trait, you must first find out which signed extensions
|
||||
//! are in use by a chain. This information can be obtained from the `SignedExtra` parameter of the `UncheckedExtrinsic` of your
|
||||
//! parachain, which will be a tuple of signed extensions. It can also be obtained from the metadata (see
|
||||
//! [`frame_metadata::v15::SignedExtensionMetadata`]).
|
||||
//!
|
||||
//! For statemint, the signed extensions look like
|
||||
//! [this](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L779):
|
||||
//!
|
||||
//! ```rs
|
||||
//! pub type SignedExtra = (
|
||||
//! frame_system::CheckNonZeroSender<Runtime>,
|
||||
//! frame_system::CheckSpecVersion<Runtime>,
|
||||
//! frame_system::CheckTxVersion<Runtime>,
|
||||
//! frame_system::CheckGenesis<Runtime>,
|
||||
//! frame_system::CheckEra<Runtime>,
|
||||
//! frame_system::CheckNonce<Runtime>,
|
||||
//! frame_system::CheckWeight<Runtime>,
|
||||
//! pallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>,
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! Each element of the `SignedExtra` tuple implements [codec::Encode] and [sp_runtime::traits::SignedExtension]
|
||||
//! which has an associated type `AdditionalSigned` that also implements [codec::Encode]. Let's look at the underlying types
|
||||
//! for each tuple element. All zero-sized types have been replaced by `()` for simplicity.
|
||||
//!
|
||||
//! | tuple element | struct type | `AdditionalSigned` type |
|
||||
//! | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
||||
//! | [`frame_system::CheckNonZeroSender`](https://docs.rs/frame-system/latest/frame_system/struct.CheckNonZeroSender.html) | () | () |
|
||||
//! | [`frame_system::CheckSpecVersion`](https://docs.rs/frame-system/latest/frame_system/struct.CheckSpecVersion.html) | () | [u32] |
|
||||
//! | [`frame_system::CheckTxVersion`](https://docs.rs/frame-system/latest/frame_system/struct.CheckTxVersion.html) | () | [u32] |
|
||||
//! | [`frame_system::CheckGenesis`](https://docs.rs/frame-system/latest/frame_system/struct.CheckGenesis.html) | () | `Config::Hash` = [sp_core::H256] |
|
||||
//! | [`frame_system::CheckMortality`](https://docs.rs/frame-system/latest/frame_system/struct.CheckMortality.html) | [sp_runtime::generic::Era] | `Config::Hash` = [sp_core::H256] |
|
||||
//! | [`frame_system::CheckNonce`](https://docs.rs/frame-system/latest/frame_system/struct.CheckNonce.html) | `frame_system::pallet::Config::Index` = u32 | () |
|
||||
//! | [`frame_system::CheckWeight`](https://docs.rs/frame-system/latest/frame_system/struct.CheckWeight.html) | () | () |
|
||||
//! | [`frame_system::ChargeAssetTxPayment`](https://docs.rs/frame-system/latest/frame_system/struct.ChargeAssetTxPayment.html) | [pallet_asset_tx_payment::ChargeAssetTxPayment](https://docs.rs/pallet-asset-tx-payment/latest/pallet_asset_tx_payment/struct.ChargeAssetTxPayment.html) | () |
|
||||
//!
|
||||
//! All types in the `struct type` column make up the "extra" data that we're expected to provide. All types in the
|
||||
//! `AdditionalSigned` column make up the "additional" data that we're expected to provide. The goal of an
|
||||
//! [`crate::config::ExtrinsicParams`] impl then is to provide the appropriate (SCALE encoded) data for these via
|
||||
//! [`crate::config::ExtrinsicParams::encode_extra_to()`] and [`crate::config::ExtrinsicParams::encode_additional_to()`]
|
||||
//! respectively. If the [`crate::config::ExtrinsicParams`] impl needs additional data to be able to do this, it can use
|
||||
//! the [`crate::config::ExtrinsicParams::OtherParams`] associated type to obtain it from the user.
|
||||
//!
|
||||
//! Given the above information, here is a fairly naive approach to implementing config for Statemint, including the
|
||||
//! [`crate::config::ExtrinsicParams`] trait, in a compatible way:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str ! ("../../../examples/setup_config_custom.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ## Using [`PolkadotConfig`](crate::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig) values to compose a Config
|
||||
//!
|
||||
//! Subxt already provides [`PolkadotConfig`](crate::config::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig). These
|
||||
//! two configs are actually very similar, and only differ slightly in the `MultiAddress` type, and in terms of the type of `Tip` provided
|
||||
//! as part of the `ExtrinsicParams`. Many chains share a lot of the same types as these, and so often times you can just reuse parts
|
||||
//! of these implementations.
|
||||
//!
|
||||
//! From looking at the types needed for the Statemint config above, we can see that it is indeed very similar to these. It ultimately
|
||||
//! uses the same signed extensions that Substrate uses, and otherwise uses the same types as Polkadot. So, we can create a config which
|
||||
//! just reuses these existing types:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{Config, PolkadotConfig, SubstrateConfig};
|
||||
//!
|
||||
//! pub enum StatemintConfig {}
|
||||
//!
|
||||
//! impl Config for StatemintConfig {
|
||||
//! type Hash = <PolkadotConfig as Config>::Hash;
|
||||
//! type AccountId = <PolkadotConfig as Config>::AccountId;
|
||||
//! type Address = <PolkadotConfig as Config>::Address;
|
||||
//! type Signature = <PolkadotConfig as Config>::Signature;
|
||||
//! type Hasher = <PolkadotConfig as Config>::Hasher;
|
||||
//! type Header = <PolkadotConfig as Config>::Header;
|
||||
//! // this is the only difference to the PolkadotConfig:
|
||||
//! type ExtrinsicParams = <SubstrateConfig as Config>::ExtrinsicParams;
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! When the types are very similar, building a custom config like this is much simpler than implementing one from scratch.
|
||||
@@ -11,3 +11,4 @@
|
||||
|
||||
pub mod client;
|
||||
pub mod codegen;
|
||||
pub mod config;
|
||||
|
||||
@@ -468,7 +468,7 @@ mod jsonrpsee_helpers {
|
||||
}
|
||||
|
||||
// helpers for a jsonrpsee specific OnlineClient.
|
||||
#[cfg(all(feature = "jsonrpsee", feature = "web"))]
|
||||
#[cfg(all(feature = "jsonrpsee", feature = "web", target_arch = "wasm32"))]
|
||||
mod jsonrpsee_helpers {
|
||||
pub use jsonrpsee::{
|
||||
client_transport::web,
|
||||
@@ -480,7 +480,9 @@ mod jsonrpsee_helpers {
|
||||
|
||||
/// Build web RPC client from URL
|
||||
pub async fn client(url: &str) -> Result<Client, Error> {
|
||||
let (sender, receiver) = web::connect(url).await.unwrap();
|
||||
let (sender, receiver) = web::connect(url)
|
||||
.await
|
||||
.map_err(|e| Error::Transport(e.into()))?;
|
||||
Ok(ClientBuilder::default()
|
||||
.max_notifs_per_subscription(4096)
|
||||
.build_with_wasm(sender, receiver))
|
||||
|
||||
@@ -135,6 +135,14 @@ mod substrate_impls {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for sp_runtime::traits::BlakeTwo256 {
|
||||
type Output = H256;
|
||||
|
||||
fn hash(s: &[u8]) -> Self::Output {
|
||||
<Self as sp_core::Hasher>::hash(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for sp_core::KeccakHasher {
|
||||
type Output = H256;
|
||||
|
||||
@@ -142,4 +150,12 @@ mod substrate_impls {
|
||||
<Self as sp_core::Hasher>::hash(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for sp_runtime::traits::Keccak256 {
|
||||
type Output = H256;
|
||||
|
||||
fn hash(s: &[u8]) -> Self::Output {
|
||||
<Self as sp_core::Hasher>::hash(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user