Skip to content

Instantly share code, notes, and snippets.

@jordy25519
Last active September 21, 2022 03:34
Show Gist options
  • Save jordy25519/da143d2edb233dfcb59d6e603090eb44 to your computer and use it in GitHub Desktop.
Save jordy25519/da143d2edb233dfcb59d6e603090eb44 to your computer and use it in GitHub Desktop.
Substrate/Plug/CENNZnet Knowledge Dump

Rust

cargo check

Sometimes errors from macro expansion are too opaque to debug. To see errors with the expanded macro set RUSTFLAGS like so:

RUSTFLAGS="-Z external-macro-backtrace" cargo +nightly [test|check]
# new version
RUSTFLAGS="-Zproc-macro-backtrace" cargo +nightly check

Capture value in a closure converts to 'static lifetime

Need to borrow a field from &self, however the &self could not be used as a &'static lifetime Solution was to move the borrowed value into ownership by the future

error: lifetime may not live long enough
   --> ethy-gadget/rpc/src/lib.rs:102:61
    |
87  |     fn subscribe_event_proofs(&self, pending: PendingSubscription) {
    |                               - let's call the lifetime of this reference `'1`
...
102 |         self.executor.spawn("ethy-rpc-subscription", Some("rpc"), fut.boxed());
    |                                                                   ^^^^^^^^^^^ cast requires that `'1` must outlive `'static`

solution (tip from here: https://users.rust-lang.org/t/help-with-static-lifetime-on-self-when-using-async/31482/4)

// thing is an `Arc<T>`
fn method(&self) {
	let my_borrow = self.thing.clone()
	// ...
	// this moves the `my_borrow` to ownership of the closure thus giving the 'static lifetime
	.map(move |x| do_something(my_borrow, x))
	// ...
}

PolkadotJS

The schnorrkelKeypairFromSeed function requires a length 32 UintArray seed
It will throw wasm unreachable errors otherwise

schnorrkelKeypairFromSeed(seed: Uint8Array);

Executive trait bounds missing (Substrate)

When changing generic types or adding trait bounds in Substrate quite often an error occurs that looks like the following:

error[E0599]: no function or associated item named `execute_block` found for type `srml_executive::doughnut::DoughnutExecutive<Runtime, sr_primitives::generic::block::Block<sr_primitives::generic::header::Header<u64, sr_primitives::traits::BlakeTwo256, Log>, cennznet_primitives::cennznet_extrinsic::CennznetExtrinsic<substrate_primitives::sr25519::Public, srml_indices::address::Address<substrate_primitives::sr25519::Public, u32>, u64, Call, sr_primitives::AnySignature, u128, doughnut_rs::v0::parity::DoughnutV0>>, srml_system::ChainContext<Runtime>, fee::ExtrinsicFeeCharger, (srml_aura::Module<Runtime>, srml_timestamp::Module<Runtime>, prml_generic_asset::Module<Runtime>, srml_consensus::Module<Runtime>, srml_indices::Module<Runtime>, srml_session::Module<Runtime>, srml_staking::Module<Runtime>, srml_democracy::Module<Runtime>, srml_council::seats::Module<Runtime>, srml_council::voting::Module<Runtime>, srml_council::motions::Module<Runtime>, srml_grandpa::Module<Runtime>, srml_contract::Module<Runtime>, srml_sudo::Module<Runtime>, prml_fees::Module<Runtime>, crml_rewards::Module<Runtime>, prml_attestation::Module<Runtime>, crml_cennzx_spot::Module<Runtime>, crml_sylo::groups::Module<Runtime>, crml_sylo::e2ee::Module<Runtime>, crml_sylo::device::Module<Runtime>, crml_sylo::inbox::Module<Runtime>, crml_sylo::response::Module<Runtime>, crml_sylo::vault::Module<Runtime>)>` in the current scope
   --> /Users/jordanbeauchamp/Projects/cennznet/runtime/src/lib.rs:313:15
    |
313 |             Executive::execute_block(block)
    |                        ^^^^^^^^^^^^^ function or associated item not found in `srml_executive::doughnut::DoughnutExecutive<Runtime, sr_primitives::generic::block::Block<sr_primitives::generic::header::Header<u64, sr_primitives::traits::BlakeTwo256, Log>, cennznet_primitives::cennznet_extrinsic::CennznetExtrinsic<substrate_primitives::sr25519::Public, srml_indices::address::Address<substrate_primitives::sr25519::Public, u32>, u64, Call, sr_primitives::AnySignature, u128, doughnut_rs::v0::parity::DoughnutV0>>, srml_system::ChainContext<Runtime>, fee::ExtrinsicFeeCharger, (srml_aura::Module<Runtime>, srml_timestamp::Module<Runtime>, prml_generic_asset::Module<Runtime>, srml_consensus::Module<Runtime>, srml_indices::Module<Runtime>, srml_session::Module<Runtime>, srml_staking::Module<Runtime>, srml_democracy::Module<Runtime>, srml_council::seats::Module<Runtime>, srml_council::voting::Module<Runtime>, srml_council::motions::Module<Runtime>, srml_grandpa::Module<Runtime>, srml_contract::Module<Runtime>, srml_sudo::Module<Runtime>, prml_fees::Module<Runtime>, crml_rewards::Module<Runtime>, prml_attestation::Module<Runtime>, crml_cennzx_spot::Module<Runtime>, crml_sylo::groups::Module<Runtime>, crml_sylo::e2ee::Module<Runtime>, crml_sylo::device::Module<Runtime>, crml_sylo::inbox::Module<Runtime>, crml_sylo::response::Module<Runtime>, crml_sylo::vault::Module<Runtime>)>`
    |
    = note: the method `execute_block` exists but the following trait bounds were not satisfied:
            `cennznet_primitives::cennznet_extrinsic::CennznetExtrinsic<substrate_primitives::sr25519::Public, srml_indices::address::Address<substrate_primitives::sr25519::Public, u32>, u64, Call, sr_primitives::AnySignature, u128, doughnut_rs::v0::parity::DoughnutV0> : sr_primitives::traits::Checkable<srml_system::ChainContext<Runtime>>

This is usually down to a type being wrong or more specifically a trait bound not being satisfied by the given types. In the example above

`cennznet_primitives::cennznet_extrinsic::CennznetExtrinsic<substrate_primitives::sr25519::Public, srml_indices::address::Address<substrate_primitives::sr25519::Public, u32>, u64, Call, sr_primitives::AnySignature, u128, doughnut_rs::v0::parity::DoughnutV0> : sr_primitives::traits::Checkable<srml_system::ChainContext<Runtime>>

Is saying the CennnznetExtrinsic<_> (generic params elided for brevity) type does not implement Checkable. Checkable is a trait bound required by the extrinsic type required by the Executive trait which is why this error is raised there also.

The heart of the issue in this case above was that a trait bound on the CennznetExtrinsic Signature type was not satisfied.

impl Checkable for CennznetExtrinsic<_, Signature, _>
where
 //..
 Signature: Borrow<u8; 64>
 {
   // ...
 }

One way of debugging this problem is to remove andy modified trait bounds or types one-by-one until the error disappears.
This will highlight which exact bound or type is causing the problem.

Cannot find trait in module (Substrate)

Sometimes an error is caused by missing cargo features from dependent crates.
The crate is imported implemented and in-scope but this error appears.

error[E0405]: cannot find trait `OffchainWorkerApi` in module `offchain_primitives`
   --> runtime/src/lib.rs:357:28
    |
357 |     impl offchain_primitives::OffchainWorkerApi<Block> for Runtime {
    |                               ^^^^^^^^^^^^^^^^^ not found in `offchain_primitives`
help: possible candidate is found in another module, you can import it into scope
    |
22  | use offchain_primitives::runtime_decl_for_OffchainWorkerApi::OffchainWorkerApi;
    |

error[E0425]: cannot find function `offchain_worker_call_api_at` in module `offchain_primitives::runtime_decl_for_OffchainWorkerApi`
   --> runtime/src/lib.rs:304:1
    |
304 | / impl_runtime_apis! {
305 | |     impl client_api::Core<Block> for Runtime {
306 | |         fn version() -> RuntimeVersion {
307 | |             VERSION
...   |
407 | |     }
408 | | }
    | |_^ not found in `offchain_primitives::runtime_decl_for_OffchainWorkerApi`

error[E0425]: cannot find function `offchain_worker_native_call_generator` in module `offchain_primitives::runtime_decl_for_OffchainWorkerApi`
   --> runtime/src/lib.rs:304:1
304 | / impl_runtime_apis! {
305 | |     impl client_api::Core<Block> for Runtime {
306 | |         fn version() -> RuntimeVersion {
307 | |             VERSION
...   |
407 | |     }
408 | | }
    | |_^ not found in `offchain_primitives::runtime_decl_for_OffchainWorkerApi`

It doesn't makes sense becase the trait is defined in the module but not in the version of the module cargo is trying to compile. This is caused by gated features in Cargo.toml that are missing. Solution is to add them to the feature in Cargo.toml

std = [
  "my-missing-module/std"
]

Conflicting Implemenations of Trait

This strange error was solved by adding a missing type Doughnut = () to a impl system::Trait for Runtime {} block

conflicting implementations of trait `std::convert::Into<std::result::Result<system::RawOrigin<_, [type error]>, Origin>>` for type `Origin`:
   --> srml/support/test/tests/instance.rs:251:1
    |
251 | / srml_support::construct_runtime!(
252 | |     pub enum Runtime where
253 | |         Block = Block,
254 | |         NodeBlock = Block,
...   |
275 | |     }
276 | | );
    | |__^
    |
    = note: conflicting implementation in crate `core`:
            - impl<T, U> std::convert::Into<U> for T
              where U: std::convert::From<T>;
    = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Downgrade a cargo crate which has updated too far

Sometimes a cargo crate gets updated and you want to rollback. A simple solution is as follows:

  1. Checkout the project Cargo.lock
  2. Remove the relevant folder from ~/.cargo/[git|registry]/<bad-crate-12391230123>
  3. run cargo build

Dual Token Economy and Reward Calculations

Substrate uses a single token represented by the Balances module.
This functions as the staking and reward token for consensus. In CENNZnet, we use a dual token economy (CENNZ, CENTRAPAY). This meant some unit-tests in the substrate staking module had been ignored (#[ignore]) because they tested balance changes using a single token in the Balance module, whereas CENNZnet relies on it's generic-asset module to manage token balances.

During the process of merging upgrades to Substrate 2.0 for plug-blockchain, a new test was added which used a reward calculation, this meant it was failing but simply needed to be ignored. The real solution to reinstate the tests was to check whether reward currency == staking currency and let tests environment use a single token setup (plug-blockchain). It would be desirable to test rewards for dual token in CENNZnet with the generic-asset module.

SignedExtension / AdditionalSigned

The PR paritytech/substrate#3102 changed the way extrinsics are extended on substrate chanins. Previously, a new extrinsic struct was defined and implemented a bespoke codec. The change introduces a trait SignedExtension and a generic type Extra on extrinsics. Extra is a struct (currently a big tuple struct) of all extensible fields includable in a transaction.

pub type SignedExtra = (
	system::CheckGenesis<Runtime>,
	system::CheckEra<Runtime>,
	system::CheckNonce<Runtime>,
	system::CheckWeight<Runtime>,
	balances::TakeFees<Runtime>,
);

The type of an extrinsic then becomes

/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
/// Extrinsic type that has already been checked.
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExtra>;

Structs of interest can be embedded into the SignedExtra type and have logic associated with them for validation and unpacking by implementing SignedExtension for the type e.g.

impl<T: Trait> SignedExtension for CheckNonce<T> {
	type AccountId = T::AccountId;
	type AdditionalSigned = ();

	fn additional_signed(&self) -> rstd::result::Result<Self::AdditionalSigned, &'static str> { Ok(()) }

	fn pre_dispatch(
		self,
		who: &Self::AccountId,
		_info: DispatchInfo,
		_len: usize,
	) -> Result<(), DispatchError> {
		let expected = <AccountNonce<T>>::get(who);
		if self.0 != expected {
			return Err(
				if self.0 < expected { DispatchError::Stale } else { DispatchError::Future }
			)
		}
		<AccountNonce<T>>::insert(who, expected + T::Index::one());
		Ok(())
	}

Doughnut implementation details Plug / CENNZnet

Doughnuts are a proof of delegation, showing an issuing party has given some permissions to a holding party. This is acheived using public key cryptography and simply including the holding party's public key in a message with permissions with a signature from the issuing party.

While Doughnut is a decentralized mechanism for delegated proofs we have an implementation for CENNZnet which supports some permission delegation based on the runtime module structure at the heart of substrate chains. The real challenge is providing meaning to the permissions part of the doughnut. The spec caters for this by allowing arbitraty permission "domains" to exist whithin a doughnut. A domain is simply a top level key to a data payload, the contents of which are open to interpretation by the given domain. In CENNZnet the doughnut has a permission domain "cennznet" which uses ascii strings to target a runtime module, method, and arguments. It is also intended to support bytecode interpretation, allowing the expression of logical constraints on arguments e.g. amount < 100.

Doughnut support is acheived in these chains by allowing users to provide a doughnut certificate as a data field in an extrinsic. It is expected that the extrinsic signer is the holder of an attached doughnut, these validations are provided by an auxilliary crate https://github.com/cennznet/doughnut-rs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment