Integrating Simnode

Simnode supports both parachain and standalone runtimes. In order for simnode to be fully functional, Simnode needs to be integrated into both your runtime and node cli.

Runtime Integration

The only runtime integration necessary is to expose a runtime API. This enables the Simnode RPC to construct extrinsics that resemble transactions from an account, but contain an empty signature.
Why does simnode need this runtime API? Well two things, first off if you’re operating on a live chain state then the native code is most definitely going to be different from your runtime code. This means that the transaction version is potentially different, or the SignedExtra might have changed. By making the runtime produce the extrinsic itself, we insulate ourselves from these potential problems.
First in your runtime crate add the simnode runtime API dependency.
toml
simnode-runtime-api = { version = "1.6.0", default-features = false }
NOTE that simnode releases will always track polkadot releases.
Find the impl_runtime_apis! line in your runtime’s lib.rs file and add the following code:
rust
impl<RuntimeCall, AccountId> simnode_runtime_api::CreateTransactionApi<Block, RuntimeCall, AccountId> for Runtime where RuntimeCall: codec::Codec, Block: sp_runtime::traits::Block, /// These trait bounds are just so that UncheckedExtrinsic can encode the AccountId AccountId: codec::Codec + codec::EncodeLike<sp_runtime::AccountId32> + Into<sp_runtime::AccountId32> + Clone + PartialEq + scale_info::TypeInfo + core::fmt::Debug, { fn create_transaction(account: AccountId, call: RuntimeCall) -> Vec<u8> { // just some imports we'll need. use codec::Encode; use sp_core::sr25519; use sp_runtime::{generic::Era, traits::StaticLookup, MultiSignature}; // let's fetch the account nonce from frame_system as we need to have a valid nonce for this extrinsic to be valid let nonce = frame_system::Pallet::<Runtime>::account_nonce(account.clone()); // here we define the signed extras, this will be different depending on your runtime requirements // make this matches the [`SignedExtra`] type you've defined in your runtime let extra = ( frame_system::CheckNonZeroSender::<Runtime>::new(), frame_system::CheckSpecVersion::<Runtime>::new(), frame_system::CheckTxVersion::<Runtime>::new(), frame_system::CheckGenesis::<Runtime>::new(), frame_system::CheckEra::<Runtime>::from(Era::Immortal), frame_system::CheckNonce::<Runtime>::from(nonce), frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0), ); // most runtimes use the multi-signature type so they can support all kinds of signature schemes // notice that this signature is empty. let signature = MultiSignature::from(sr25519::Signature([0_u8; 64])); // converts the AccountId to the Address type the runtime expects. let address = AccountIdLookup::unlookup(account.into()); // constructs the "signed" extrinsic let ext = generic::UncheckedExtrinsic::<Address, RuntimeCall, Signature, SignedExtra>::new_signed( call, address, signature, extra, ); ext.encode() } }
In cases where you’re trying to run simnode over live chain data, and the runtime onchain doesn’t yet expose this runtime API, simnode will still work. But it helps to be aware that you’ll have to configure simnode correctly in the next step

CLI Integration

This is a bit more involved, but the pay-offs are worth it. First we’ll need to bring in the simnode library into your node crate. Depending on what kind of consensus mechanism your substrate-based blockchain uses, you’ll need to enable the appropriate feature as well.
toml
# features must be one of parachain, babe or aura sc-simnode = { version = "1.6.0", features = ["parachain"] }
Next we’ll need to implement the sc_simnode::ChainInfo for your runtime. Add this code to the bottom of your command.rs file.
rust
pub struct RuntimeInfo; impl sc_simnode::ChainInfo for RuntimeInfo { // make sure you pass the opaque::Block here type Block = parachain_template_runtime::opaque::Block; // the runtime type type Runtime = parachain_template_runtime::Runtime; // the runtime api type RuntimeApi = parachain_template_runtime::RuntimeApi; // [`SignedExtra`] for your runtime type SignedExtras = parachain_template_runtime::SignedExtra; // Initialize the [`SignedExtra`] for your runtime, you'll notice I'm calling a pallet method here in // order to read from the runtime storage. This is possible becase this method is called in an externalities // provided environment. So feel free to read your runtime storage. fn signed_extras( from: <Self::Runtime as frame_system::pallet::Config>::AccountId, ) -> Self::SignedExtras { let nonce = frame_system::Pallet::<Self::Runtime>::account_nonce(from); ( frame_system::CheckNonZeroSender::<Self::Runtime>::new(), frame_system::CheckSpecVersion::<Self::Runtime>::new(), frame_system::CheckTxVersion::<Self::Runtime>::new(), frame_system::CheckGenesis::<Self::Runtime>::new(), frame_system::CheckEra::<Self::Runtime>::from(Era::Immortal), frame_system::CheckNonce::<Self::Runtime>::from(nonce), frame_system::CheckWeight::<Self::Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Self::Runtime>::from(0), ) } }
The next steps will vary depending on if you have a parachain or standalone runtime. Toggle the appropriate drop down for your situation.

Parachain

AURA

BABE

Now that you’ve integrated simnode, compile your node and run the following command to witness simnode in it’s full glory.
bash
cargo build --release -p your-node ./target/release/your-node simnode --dev --state-pruning=archive --blocks-pruning=archive
You’ll notice that no blocks are being created and the node basically sits idle. This is exactly what should happen.
bash
2023-05-18 12:11:26 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:31 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:36 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:41 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:46 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:51 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:11:56 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:01 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:06 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:11 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:16 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:21 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:26 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:31 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00 2023-05-18 12:12:36 💤 Idle (0 peers), best: #0 (0xf2ac7e42), finalized #0 (0xf2ac7e42),00
Test that you can create blocks like so:
bash
curl http://127.0.0.1:9933 -d '{"id":29,"jsonrpc":"2.0","method":"engine_createBlock","params":[true, true]}' -H "Content-Type: application/json" {"jsonrpc":"2.0","result":{"hash":"0xbf1120ffcc74ab53811f1c2ca27edcf61dc30546a44b5141c968b43581432278","aux":{"header_only":false,"clear_justification_requests":false,"needs_justification":false,"bad_justification":false,"is_new_best":true}},"id":29}
You should see your node output something along the following lines:
bash
2023-05-18 12:08:50 Accepting new connection 1/100 2023-05-18 12:08:50 🙌 Starting consensus session on top of parent 0xf2ac8c19f4f0138303250778ec575d1ad9cb7dfc40f41e359acd28dc4aee7e42 2023-05-18 12:08:50 🎁 Prepared block for proposing at 1 (0 ms) [hash: 0xf7dce7803a59d35f3e99f2c2a2d5e841a681349e4c863cd86dae452a22259494; parent_hash: 0xf2ac7e42; extrinsics (2): [0x71a8…f4a4, 0x61d2…b836]] 2023-05-18 12:08:50 ✨ Imported #1 (0xf7dc9494)
And that's it! You've successfully integrated Simnode into your substrate node! 🥳🎉🎊.
Next we’ll look at how to leverage simnode for writing integration tests for your runtime.

Powered by Notaku