Skip to content

Commit

Permalink
Create no_std example for extrinsic creation (#556)
Browse files Browse the repository at this point in the history
* Start version of example

* Start moving code from Api into example

* Move more code into example

* Make some progress with types

* Cleanup

* Cleanup

* Documentation

* Some cleanup

* Make it compile again

* Use compose_call

* Create call differently

* Switch to using AssetTip

* Cleanup

* Cleanup

* Merge compose_extrinsic_offline into compose_extrinsic_no_std

* Incorporate review feedback

* Cleanup of merging of the two examples (compose_extrinsic_offline.rs and compose_extrinsic_no_std.rs)

* Just run both approaches to create an extrinsic

* Remove duplicated example

* Have only extrinsic related code in the construction blocks

* Apply suggestions from code review

Co-authored-by: Bigna Härdi <[email protected]>

* Move code below comment

---------

Co-authored-by: Bigna Härdi <[email protected]>
  • Loading branch information
Niederb and haerdib authored Dec 5, 2023
1 parent e58919a commit e83aee9
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 79 deletions.
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
145 changes: 145 additions & 0 deletions examples/examples/compose_extrinsic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
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 params needed for the extrinsic construction. For more information on what these parameters mean, take a look at Substrate docs: https://docs.substrate.io/reference/transaction-format/.
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)");
// 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 signer_nonce = api.get_nonce().unwrap();
println!("[+] Alice's Account Nonce is {}", signer_nonce);

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);
// To send the extrinsic to the node, we need an rpc client which is only available within std-environment. If you want to operate a rpc client in your own no-std environment, take a look at https://github.com/scs/substrate-api-client#rpc-client on how to implement one yourself.
let hash = api
.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 (without any calls to the node) with the help of the api client. For example, this allows you to set your own nonce (to achieve future calls or construct an extrsinic that must be sent at a later time).
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

0 comments on commit e83aee9

Please sign in to comment.