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

Create no_std example for extrinsic creation #556

Merged
merged 22 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
os: [ ubuntu-latest ]
example: [
benchmark_bulk_xt,
compose_extrinsic_offline,
compose_extrinsic,
custom_nonce,
check_extrinsic_events,
get_account_identity,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The following examples can be found in the [examples](/examples/examples) folder

* [benchmark_bulk_xt](/examples/examples/benchmark_bulk_xt.rs): Float the node with a series of transactions.
* [check_extrinsic_events](/examples/examples/check_extrinsic_events.rs): Check and react according to events associated to an extrinsic.
* [compose_extrinsic_offline](/examples/examples/compose_extrinsic_offline.rs): Compose an extrinsic without interacting with the node.
* [compose_extrinsic](/examples/examples/compose_extrinsic.rs): Compose an extrinsic without interacting with the node or in no_std mode.
* [contract_instantiate_with_code](/examples/examples/contract_instantiate_with_code.rs): Instantiate a contract on the chain.
* [custom_nonce](/examples/examples/custom_nonce.rs): Compose an with a custom nonce.
* [get_account_identity](/examples/examples/get_account_identity.rs): Create an custom Unchecked Extrinsic to set an account identity and retrieve it afterwards with a getter.
Expand Down
144 changes: 144 additions & 0 deletions examples/examples/compose_extrinsic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
Copyright 2019 Supercomputing Systems AG
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

//! This example shows two special ways to create an extrinsic:
//! - Compose an extrinsic in a no_std environment that works without having acces to an `Api` instance
//! - Compose an extrinsic without asking the node for nonce and without knowing the metadata

use codec::Compact;
use kitchensink_runtime::{BalancesCall, RuntimeCall};
use sp_keyring::AccountKeyring;
use sp_runtime::{generic::Era, MultiAddress};
use substrate_api_client::{
ac_compose_macros::{compose_call, compose_extrinsic_offline},
ac_primitives::{
config::Config, AssetRuntimeConfig, AssetTip, ExtrinsicParams, ExtrinsicSigner,
GenericAdditionalParams, SignExtrinsic,
},
rpc::JsonrpseeClient,
Api, GetChainInfo, SubmitAndWatch, XtStatus,
};

type AssetExtrinsicSigner = <AssetRuntimeConfig as Config>::ExtrinsicSigner;
type AccountId = <AssetRuntimeConfig as Config>::AccountId;
type ExtrinsicAddressOf<Signer> = <Signer as SignExtrinsic<AccountId>>::ExtrinsicAddress;

type Hash = <AssetRuntimeConfig as Config>::Hash;
/// Get the balance type from your node runtime and adapt it if necessary.
type Balance = <AssetRuntimeConfig as Config>::Balance;
/// We need AssetTip here, because the kitchensink runtime uses the asset pallet. Change to PlainTip if your node uses the balance pallet only.
type AdditionalParams = GenericAdditionalParams<AssetTip<Balance>, Hash>;

type Address = <AssetRuntimeConfig as Config>::Address;

// To test this example with CI we run it against the Substrate kitchensink node, which uses the asset pallet.
// Therefore, we need to use the `AssetRuntimeConfig` in this example.
// ! However, most Substrate runtimes do not use the asset pallet at all. So if you run an example against your own node
// you most likely should use `DefaultRuntimeConfig` instead.

#[tokio::main]
async fn main() {
env_logger::init();

// Initialize api and set the signer (sender) that is used to sign the extrinsics.
let signer = AccountKeyring::Alice.pair();
let client = JsonrpseeClient::with_default_url().unwrap();

let mut api = Api::<AssetRuntimeConfig, _>::new(client).unwrap();
let extrinsic_signer = ExtrinsicSigner::<AssetRuntimeConfig>::new(signer);
// Signer is needed to set the nonce and sign the extrinsic.
api.set_signer(extrinsic_signer.clone());

let recipient: Address = MultiAddress::Id(AccountKeyring::Bob.to_account_id());

// Get the last finalized header to retrieve information for Era for mortal transactions (online).
let last_finalized_header_hash = api.get_finalized_head().unwrap().unwrap();
let header = api.get_header(Some(last_finalized_header_hash)).unwrap().unwrap();
let period = 5;

// Construct extrinsic without using Api (no_std).
Niederb marked this conversation as resolved.
Show resolved Hide resolved
let additional_extrinsic_params: AdditionalParams = GenericAdditionalParams::new()
.era(Era::mortal(period, header.number.into()), last_finalized_header_hash)
.tip(0);

println!("Compose extrinsic in no_std environment (No Api instance)");
let signer_nonce = api.get_nonce().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this below the descriptive comment below? I think this belongs to the same category.

println!("[+] Alice's Account Nonce is {}", signer_nonce);
// Get information out of Api (online). This information could also be set offline in the `no_std`,
// but that would need to be static and adapted whenever the node changes.
// You can get the information directly from the node runtime file or the api of https://polkadot.js.org.
let spec_version = api.runtime_version().spec_version;
let transaction_version = api.runtime_version().transaction_version;
let genesis_hash = api.genesis_hash();
let metadata = api.metadata();

let recipients_extrinsic_address: ExtrinsicAddressOf<AssetExtrinsicSigner> =
recipient.clone().into();

// Construct an extrinsic using only functionality available in no_std
let xt = {
let extrinsic_params = <AssetRuntimeConfig as Config>::ExtrinsicParams::new(
spec_version,
transaction_version,
signer_nonce,
genesis_hash,
additional_extrinsic_params,
);

let call = compose_call!(
metadata,
"Balances",
"transfer_allow_death",
recipients_extrinsic_address,
Compact(4u32)
);
compose_extrinsic_offline!(extrinsic_signer, call, extrinsic_params)
};

println!("[+] Composed Extrinsic:\n {:?}", xt);
let hash = api
Niederb marked this conversation as resolved.
Show resolved Hide resolved
.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock)
.unwrap()
.block_hash
.unwrap();
println!("[+] Extrinsic got included in block {:?}", hash);

println!();

println!("Compose extrinsic offline");
let signer_nonce = api.get_nonce().unwrap();
println!("[+] Alice's Account Nonce is {}", signer_nonce);

// Construct an extrinsic offline
Niederb marked this conversation as resolved.
Show resolved Hide resolved
let xt = {
// Set the additional params.
api.set_additional_params(additional_extrinsic_params);

// Compose the extrinsic (offline).
let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: recipient,
value: 42,
});
api.compose_extrinsic_offline(call, signer_nonce)
};

println!("[+] Composed Extrinsic:\n {:?}", xt);
let hash = api
.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock)
.unwrap()
.block_hash
.unwrap();
println!("[+] Extrinsic got included in block {:?}", hash);
}
76 changes: 0 additions & 76 deletions examples/examples/compose_extrinsic_offline.rs

This file was deleted.

2 changes: 1 addition & 1 deletion primitives/src/extrinsics/extrinsic_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<Tip, Index> GenericSignedExtra<Tip, Index> {

/// Default AdditionalSigned fields of a Polkadot/Substrate node.
/// Order: (CheckNonZeroSender, CheckSpecVersion, CheckTxVersion, CheckGenesis, Check::Era, CheckNonce, CheckWeight, transactionPayment::ChargeTransactionPayment).
// The order and types can must match the one defined in the runtime.
// The order and types must match the one defined in the runtime.
// Example: https://github.com/paritytech/substrate/blob/cbd8f1b56fd8ab9af0d9317432cc735264c89d70/bin/node/runtime/src/lib.rs#L1779-L1788
// The `AdditionalSigned` is the tuple returned from the call SignedExtra::additional_signed().
// Each member defined in the `SignedExtra` on the node side implements the trait `SignedExtension`, which
Expand Down