diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a654e219f..393c6a9ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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, diff --git a/README.md b/README.md index 8223f1169..cdc777eb4 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/examples/examples/compose_extrinsic.rs b/examples/examples/compose_extrinsic.rs new file mode 100644 index 000000000..f178bb3e0 --- /dev/null +++ b/examples/examples/compose_extrinsic.rs @@ -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 = ::ExtrinsicSigner; +type AccountId = ::AccountId; +type ExtrinsicAddressOf = >::ExtrinsicAddress; + +type Hash = ::Hash; +/// Get the balance type from your node runtime and adapt it if necessary. +type Balance = ::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, Hash>; + +type Address = ::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::::new(client).unwrap(); + let extrinsic_signer = ExtrinsicSigner::::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 = + recipient.clone().into(); + + // Construct an extrinsic using only functionality available in no_std + let xt = { + let extrinsic_params = ::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); +} diff --git a/examples/examples/compose_extrinsic_offline.rs b/examples/examples/compose_extrinsic_offline.rs deleted file mode 100644 index a69f3e795..000000000 --- a/examples/examples/compose_extrinsic_offline.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* - 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 how to use the compose_extrinsic_offline macro which generates an extrinsic -//! without asking the node for nonce and does not need to know the metadata. - -use kitchensink_runtime::{BalancesCall, RuntimeCall}; -use sp_keyring::AccountKeyring; -use sp_runtime::{generic::Era, MultiAddress}; -use substrate_api_client::{ - ac_primitives::{AssetRuntimeConfig, ExtrinsicSigner, GenericAdditionalParams}, - rpc::JsonrpseeClient, - Api, GetChainInfo, SubmitAndWatch, XtStatus, -}; - -// 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(); - // Api::new(..) is not actually an offline call, but retrieves metadata and other information from the node. - // If this is not acceptable, use the Api::new_offline(..) function instead. There are no examples for this, - // because of the constantly changing substrate node. But check out our unit tests - there are Apis created with `new_offline`. - let mut api = Api::::new(client).unwrap(); - api.set_signer(ExtrinsicSigner::::new(signer)); - - // 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; - let tx_params = GenericAdditionalParams::new() - .era(Era::mortal(period, header.number.into()), last_finalized_header_hash) - .tip(0); - - // Set the additional params. - api.set_additional_params(tx_params); - - // Get the nonce of the signer account (online). - let signer_nonce = api.get_nonce().unwrap(); - println!("[+] Alice's Account Nonce is {}\n", signer_nonce); - - // Compose the extrinsic (offline). - let recipient = MultiAddress::Id(AccountKeyring::Bob.to_account_id()); - let call = - RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: recipient, value: 42 }); - let xt = api.compose_extrinsic_offline(call, signer_nonce); - - println!("[+] Composed Extrinsic:\n {:?}\n", xt); - - // Send and watch extrinsic until in block (online). - let block_hash = api - .submit_and_watch_extrinsic_until(xt, XtStatus::InBlock) - .unwrap() - .block_hash - .unwrap(); - println!("[+] Extrinsic got included in block {:?}", block_hash); -} diff --git a/primitives/src/extrinsics/extrinsic_params.rs b/primitives/src/extrinsics/extrinsic_params.rs index 29a3a92eb..072035d0d 100644 --- a/primitives/src/extrinsics/extrinsic_params.rs +++ b/primitives/src/extrinsics/extrinsic_params.rs @@ -48,7 +48,7 @@ impl GenericSignedExtra { /// 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