Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ExtrinsicParams more flexible, and introduce signed extensions #1107

Merged
merged 11 commits into from
Aug 8, 2023
130 changes: 65 additions & 65 deletions subxt/examples/setup_config_custom.rs
Original file line number Diff line number Diff line change
@@ -1,95 +1,95 @@
use codec::Encode;
use primitive_types::H256;
use subxt::config::{Config, ExtrinsicParams};
use subxt::client::OfflineClientT;
use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}

// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum StatemintConfig {}
pub enum CustomConfig {}

impl Config for StatemintConfig {
impl Config for CustomConfig {
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;
type ExtrinsicParams = CustomExtrinsicParams<Self>;
}

#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct StatemintExtrinsicParams {
extra_params: StatemintExtraParams,
additional_params: StatemintAdditionalParams,
// This represents some arbitrary (and nonsensical) custom parameters that
// will be attached to transaction extra and additional payloads:
pub struct CustomExtrinsicParams<T: Config> {
genesis_hash: T::Hash,
tip: u128,
foo: bool,
}

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,
};
// We can provide a "pretty" interface to allow users to provide these:
#[derive(Default)]
pub struct CustomExtrinsicParamsBuilder {
tip: u128,
foo: bool,
}

Self {
extra_params,
additional_params,
}
impl CustomExtrinsicParamsBuilder {
pub fn new() -> Self {
Default::default()
}

// Encode the relevant params when asked:
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.extra_params.encode_to(v);
pub fn tip(mut self, value: u128) -> Self {
self.tip = value;
self
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.additional_params.encode_to(v);
pub fn enable_foo(mut self) -> Self {
self.foo = true;
self
}
}

#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct StatemintExtraParams {
era: sp_runtime::generic::Era,
nonce: u64,
charge: ChargeAssetTxPayment,
}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
type OtherParams = CustomExtrinsicParamsBuilder;
type Error = std::convert::Infallible;

#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct ChargeAssetTxPayment {
#[codec(compact)]
tip: u128,
asset_id: Option<u32>,
// Gather together all of the params we will need to encode:
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(Self {
genesis_hash: client.genesis_hash(),
tip: other_params.tip,
foo: other_params.foo,
})
}
}

#[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,
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
(self.tip, self.foo).encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.genesis_hash.encode_to(v)
}
}

#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let _client_fut = subxt::OnlineClient::<StatemintConfig>::new();
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();

let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());

// Build your custom "OtherParams":
let tx_config = CustomExtrinsicParamsBuilder::new().tip(1234).enable_foo();

// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config);
}
99 changes: 99 additions & 0 deletions subxt/examples/setup_config_signed_extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use codec::Encode;
use subxt::client::OfflineClientT;
use subxt::config::signed_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}

// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum CustomConfig {}

impl Config for CustomConfig {
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 = signed_extensions::AnyOf<
Self,
(
// Load in all of the existing signed extensions:
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<Self>,
signed_extensions::CheckMortality<Self>,
signed_extensions::ChargeAssetTxPayment,
signed_extensions::ChargeTransactionPayment,
// And add a new one of our own:
CustomSignedExtension,
),
>;
}

// Our custom signed extension doesn't do much:
pub struct CustomSignedExtension;

// Give the extension a name; this allows [`AnyOf`] to look it
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> signed_extensions::SignedExtension<T> for CustomSignedExtension {
const NAME: &'static str = "CustomSignedExtension";
}

// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomSignedExtension {
type OtherParams = ();
type Error = std::convert::Infallible;

fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CustomSignedExtension)
}
}

// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomSignedExtension {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}

// When composing a tuple of signed extensions, the user parameters we need must
// be able to convert `Into` a tuple of corresponding `OtherParams`. Here, we just
// "hijack" the default param builder, but add the `OtherParams` (`()`) for our
// new signed extension at the end, to make the types line up. IN reality you may wish
// to construct an entirely new interface to provide the relevant `OtherParams`.
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::OtherParams {
let (a, b, c, d, e, f, g) = params.raw();
(a, b, c, d, e, f, g, ())
}

#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();

let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());

// Configure the tx params:
let tx_config = DefaultExtrinsicParamsBuilder::new().tip(1234);

// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config));
}
11 changes: 6 additions & 5 deletions subxt/examples/tx_with_params.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
use subxt::config::polkadot::PolkadotExtrinsicParamsBuilder as Params;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

Expand All @@ -14,10 +14,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dest = dev::bob().public_key().into();
let tx = polkadot::tx().balances().transfer(dest, 10_000);

// Configure the transaction parameters; for Polkadot the tip and era:
let tx_params = Params::new()
.tip(PlainTip::new(1_000))
.era(Era::Immortal, api.genesis_hash());
let latest_block = api.blocks().at_latest().await?;

// Configure the transaction parameters; we give a small tip and set the
// transaction to live for 32 blocks from the `latest_block` above.
let tx_params = Params::new().tip(1_000).mortal(latest_block.header(), 32);

// submit the transaction:
let from = dev::alice();
Expand Down
Loading