From e2f0ba9ad9b495b86827632e6c45c117b5ff10a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 22 Apr 2024 21:28:27 +0200 Subject: [PATCH 1/2] wasm-builder: Make it easier to build a WASM binary (#4177) Basically combines all the recommended calls into one `build_using_defaults()` call or `init_with_defaults()` when there are some custom changes required. --- .../runtimes/assets/asset-hub-rococo/build.rs | 6 +--- .../assets/asset-hub-westend/build.rs | 6 +--- .../collectives/collectives-westend/build.rs | 6 +--- .../contracts/contracts-rococo/build.rs | 6 +--- .../runtimes/glutton/glutton-westend/build.rs | 6 +--- .../runtimes/testing/penpal/build.rs | 6 +--- .../testing/rococo-parachain/build.rs | 6 +--- cumulus/test/runtime/build.rs | 10 ++---- polkadot/runtime/rococo/build.rs | 11 ++----- polkadot/runtime/test-runtime/build.rs | 6 +--- polkadot/runtime/westend/build.rs | 6 +--- prdoc/pr_4177.prdoc | 12 +++++++ substrate/utils/wasm-builder/src/builder.rs | 33 +++++++++++++++++++ substrate/utils/wasm-builder/src/lib.rs | 12 ++----- templates/minimal/runtime/build.rs | 6 +--- templates/parachain/runtime/build.rs | 6 +--- templates/solochain/runtime/build.rs | 6 +--- 17 files changed, 64 insertions(+), 86 deletions(-) create mode 100644 prdoc/pr_4177.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs index 60f8a125129f..239ccac19ec7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs index 60f8a125129f..239ccac19ec7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs index 60f8a125129f..239ccac19ec7 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs index 60f8a125129f..239ccac19ec7 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs index 1580e6f07bec..2f311357403c 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs @@ -16,9 +16,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/cumulus/parachains/runtimes/testing/penpal/build.rs b/cumulus/parachains/runtimes/testing/penpal/build.rs index 9c9cde9a25a1..c2fa89aa7028 100644 --- a/cumulus/parachains/runtimes/testing/penpal/build.rs +++ b/cumulus/parachains/runtimes/testing/penpal/build.rs @@ -16,11 +16,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs index 60f8a125129f..239ccac19ec7 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/build.rs @@ -15,11 +15,7 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index 5e5f6a35a505..ebd5c178cba0 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -18,16 +18,10 @@ fn main() { use substrate_wasm_builder::WasmBuilder; - WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + WasmBuilder::build_using_defaults(); - WasmBuilder::new() - .with_current_project() + WasmBuilder::init_with_defaults() .enable_feature("increment-spec-version") - .import_memory() .set_file_name("wasm_binary_spec_version_incremented.rs") .build(); } diff --git a/polkadot/runtime/rococo/build.rs b/polkadot/runtime/rococo/build.rs index 0b7ee77b0d0d..403c31ff21c7 100644 --- a/polkadot/runtime/rococo/build.rs +++ b/polkadot/runtime/rococo/build.rs @@ -16,18 +16,11 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() + substrate_wasm_builder::WasmBuilder::init_with_defaults() .set_file_name("fast_runtime_binary.rs") .enable_feature("fast-runtime") - .import_memory() - .export_heap_base() .build(); } diff --git a/polkadot/runtime/test-runtime/build.rs b/polkadot/runtime/test-runtime/build.rs index 404ba3f2fdbd..caf24317d0b3 100644 --- a/polkadot/runtime/test-runtime/build.rs +++ b/polkadot/runtime/test-runtime/build.rs @@ -17,9 +17,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/polkadot/runtime/westend/build.rs b/polkadot/runtime/westend/build.rs index 428c971bc132..0b3e12c78c74 100644 --- a/polkadot/runtime/westend/build.rs +++ b/polkadot/runtime/westend/build.rs @@ -17,9 +17,5 @@ use substrate_wasm_builder::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .import_memory() - .export_heap_base() - .build() + WasmBuilder::build_using_defaults(); } diff --git a/prdoc/pr_4177.prdoc b/prdoc/pr_4177.prdoc new file mode 100644 index 000000000000..29d011c93516 --- /dev/null +++ b/prdoc/pr_4177.prdoc @@ -0,0 +1,12 @@ +title: "wasm-builder: Make it easier to build a WASM binary" + +doc: + - audience: [Runtime Dev, Node Dev] + description: | + Combines all the recommended calls of the `WasmBuilder` into + `build_using_defaults()` or `init_with_defaults()` if more changes are required. + Otherwise the interface doesn't change and users can still continue to use + the "old" interface. + +crates: + - name: substrate-wasm-builder diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index d2aaff448bc5..163703fbec62 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -116,6 +116,39 @@ impl WasmBuilder { WasmBuilderSelectProject { _ignore: () } } + /// Build the WASM binary using the recommended default values. + /// + /// This is the same as calling: + /// ```no_run + /// substrate_wasm_builder::WasmBuilder::new() + /// .with_current_project() + /// .import_memory() + /// .export_heap_base() + /// .build(); + /// ``` + pub fn build_using_defaults() { + WasmBuilder::new() + .with_current_project() + .import_memory() + .export_heap_base() + .build(); + } + + /// Init the wasm builder with the recommended default values. + /// + /// In contrast to [`Self::build_using_defaults`] it does not build the WASM binary directly. + /// + /// This is the same as calling: + /// ```no_run + /// substrate_wasm_builder::WasmBuilder::new() + /// .with_current_project() + /// .import_memory() + /// .export_heap_base(); + /// ``` + pub fn init_with_defaults() -> Self { + WasmBuilder::new().with_current_project().import_memory().export_heap_base() + } + /// Enable exporting `__heap_base` as global variable in the WASM binary. /// /// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`. diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 178e499e8f5b..9ebab38b9cb2 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -33,15 +33,9 @@ //! use substrate_wasm_builder::WasmBuilder; //! //! fn main() { -//! WasmBuilder::new() -//! // Tell the builder to build the project (crate) this `build.rs` is part of. -//! .with_current_project() -//! // Make sure to export the `heap_base` global, this is required by Substrate -//! .export_heap_base() -//! // Build the Wasm file so that it imports the memory (need to be provided by at instantiation) -//! .import_memory() -//! // Build it. -//! .build() +//! // Builds the WASM binary using the recommended defaults. +//! // If you need more control, you can call `new` or `init_with_defaults`. +//! WasmBuilder::build_using_defaults(); //! } //! ``` //! diff --git a/templates/minimal/runtime/build.rs b/templates/minimal/runtime/build.rs index b7676a70dfe8..e6f92757e225 100644 --- a/templates/minimal/runtime/build.rs +++ b/templates/minimal/runtime/build.rs @@ -18,10 +18,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/templates/parachain/runtime/build.rs b/templates/parachain/runtime/build.rs index 02d6973f29cf..bb05afe02b1f 100644 --- a/templates/parachain/runtime/build.rs +++ b/templates/parachain/runtime/build.rs @@ -1,10 +1,6 @@ #[cfg(feature = "std")] fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } /// The wasm builder is deactivated when compiling diff --git a/templates/solochain/runtime/build.rs b/templates/solochain/runtime/build.rs index c03d618535be..f262c320393b 100644 --- a/templates/solochain/runtime/build.rs +++ b/templates/solochain/runtime/build.rs @@ -1,10 +1,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } From 667f7f4bdc31f4f617ad6f9978f44b182e112caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 22 May 2024 16:21:32 +0200 Subject: [PATCH 2/2] Implement `CheckMetadataHash` extension (#4274) This implements the `CheckMetadataHash` extension as described in [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). Besides the signed extension, the `substrate-wasm-builder` is extended to support generating the metadata-hash. Closes: https://github.com/paritytech/polkadot-sdk/issues/291 --------- Co-authored-by: Oliver Tale-Yazdi Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: Liam Aharon Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .github/workflows/check-semver.yml | 56 ++ .gitlab/pipeline/test.yml | 11 + Cargo.lock | 705 ++++++++---------- Cargo.toml | 1 + .../tests/assets/asset-hub-westend/Cargo.toml | 1 + .../src/tests/xcm_fee_estimation.rs | 371 +++++++++ .../assets/asset-hub-rococo/Cargo.toml | 7 +- .../runtimes/assets/asset-hub-rococo/build.rs | 9 +- .../assets/asset-hub-rococo/src/lib.rs | 1 + .../assets/asset-hub-westend/Cargo.toml | 7 +- .../assets/asset-hub-westend/build.rs | 9 +- .../assets/asset-hub-westend/src/lib.rs | 1 + docs/sdk/Cargo.toml | 1 + polkadot/node/service/Cargo.toml | 15 +- polkadot/node/service/src/benchmarking.rs | 4 + polkadot/runtime/rococo/Cargo.toml | 7 +- polkadot/runtime/rococo/build.rs | 15 +- polkadot/runtime/rococo/src/lib.rs | 3 + polkadot/runtime/westend/Cargo.toml | 7 +- polkadot/runtime/westend/build.rs | 13 +- polkadot/runtime/westend/src/lib.rs | 2 + prdoc/pr_4274.prdoc | 39 + substrate/bin/node/cli/Cargo.toml | 1 + substrate/bin/node/cli/benches/executor.rs | 2 +- substrate/bin/node/cli/src/service.rs | 16 +- substrate/bin/node/cli/tests/common.rs | 2 +- substrate/bin/node/runtime/Cargo.toml | 4 + substrate/bin/node/runtime/build.rs | 20 +- substrate/bin/node/runtime/src/lib.rs | 2 + substrate/bin/node/testing/Cargo.toml | 1 + substrate/bin/node/testing/src/bench.rs | 2 + substrate/bin/node/testing/src/keyring.rs | 13 +- substrate/client/executor/wasmtime/src/lib.rs | 4 + .../frame/metadata-hash-extension/Cargo.toml | 39 + .../frame/metadata-hash-extension/src/lib.rs | 168 +++++ .../metadata-hash-extension/src/tests.rs | 179 +++++ substrate/test-utils/runtime/Cargo.toml | 5 +- substrate/test-utils/runtime/build.rs | 1 + substrate/test-utils/runtime/src/extrinsic.rs | 30 +- substrate/test-utils/runtime/src/lib.rs | 15 +- substrate/utils/wasm-builder/Cargo.toml | 31 + substrate/utils/wasm-builder/src/builder.rs | 36 + substrate/utils/wasm-builder/src/lib.rs | 5 +- .../utils/wasm-builder/src/metadata_hash.rs | 132 ++++ .../utils/wasm-builder/src/wasm_project.rs | 197 ++--- templates/parachain/runtime/Cargo.toml | 16 + templates/parachain/runtime/build.rs | 10 +- templates/parachain/runtime/src/lib.rs | 1 + 48 files changed, 1707 insertions(+), 510 deletions(-) create mode 100644 .github/workflows/check-semver.yml create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs create mode 100644 prdoc/pr_4274.prdoc create mode 100644 substrate/frame/metadata-hash-extension/Cargo.toml create mode 100644 substrate/frame/metadata-hash-extension/src/lib.rs create mode 100644 substrate/frame/metadata-hash-extension/src/tests.rs create mode 100644 substrate/utils/wasm-builder/src/metadata_hash.rs diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml new file mode 100644 index 000000000000..04c63f4192b2 --- /dev/null +++ b/.github/workflows/check-semver.yml @@ -0,0 +1,56 @@ +name: Check semver + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - prdoc/*.prdoc + +jobs: + check-semver: + runs-on: ubuntu-latest + container: + image: docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + + - name: Rust compilation prerequisites + run: | + rustup default nightly-2024-03-01 + rustup target add wasm32-unknown-unknown --toolchain nightly-2024-03-01 + rustup component add rust-src --toolchain nightly-2024-03-01 + + - name: install parity-publish + run: cargo install parity-publish@0.5.1 + + - name: extra git setup + run: | + git config --global --add safe.directory '*' + git fetch --no-tags --no-recurse-submodules --depth=1 origin master + git branch old origin/master + + - name: check semver + run: | + export CARGO_TARGET_DIR=target + export RUSTFLAGS='-A warnings -A missing_docs' + export SKIP_WASM_BUILD=1 + if ! parity-publish --color always prdoc --since old --validate prdoc/pr_$PR.prdoc --toolchain nightly-2024-03-01 -v; then + cat <::new_ext().execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + + let call = RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4(destination.clone())), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets)), + fee_asset_item: 0, + weight_limit: Unlimited, + }); + let sender = Alice; // Is the same as `WestendSender`. + let extrinsic = construct_extrinsic_westend(sender, call); + let result = Runtime::dry_run_extrinsic(extrinsic).unwrap(); + assert_eq!(result.forwarded_xcms.len(), 1); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + assert_eq!(messages_to_query.len(), 1); + remote_message = messages_to_query[0].clone(); + let delivery_fees = + Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) + .unwrap(); + delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // This is set in the AssetHubWestend closure. + let mut remote_execution_fees = 0; + ::execute_with(|| { + type Runtime = ::Runtime; + + let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); + remote_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) + .unwrap(); + }); + + let test_args = TestContext { + sender: WestendSender::get(), // Alice. + receiver: AssetHubWestendReceiver::get(), // Bob in Asset Hub. + args: TestArgs::new_relay(destination, beneficiary_id, teleport_amount), + }; + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + assert_eq!(sender_balance_before, 1_000_000_000_000_000_000); + assert_eq!(receiver_balance_before, 4_096_000_000_000); + + test.set_dispatchable::(transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // We now know the exact fees. + assert_eq!( + sender_balance_after, + sender_balance_before - delivery_fees_amount - teleport_amount + ); + assert_eq!( + receiver_balance_after, + receiver_balance_before + teleport_amount - remote_execution_fees + ); +} + +/// We are able to dry-run and estimate the fees for a multi-hop XCM journey. +/// Scenario: Alice on PenpalA has some WND and wants to send them to PenpalB. +/// We want to know the fees using the `XcmDryRunApi` and `XcmPaymentApi`. +#[test] +fn multi_hop_works() { + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let amount_to_send = 1_000_000_000_000; // One WND (12 decimals). + let asset_owner = PenpalAssetOwner::get(); + let assets: Assets = (Parent, amount_to_send).into(); + let relay_native_asset_location = RelayLocation::get(); + let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); + let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay.clone()); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + + // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + Westend::fund_accounts(vec![(sov_of_sender_on_relay.clone().into(), amount_to_send * 2)]); + + // Init values for Parachain Destination + let beneficiary_id = PenpalBReceiver::get(); + let beneficiary: Location = AccountId32 { + id: beneficiary_id.clone().into(), + network: None, // Test doesn't allow specifying a network here. + } + .into(); + + // We get them from the PenpalA closure. + let mut delivery_fees_amount = 0; + let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); + ::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + + let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4(destination.clone())), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets.clone())), + fee_asset_item: 0, + weight_limit: Unlimited, + }); + let sender = Alice; // Same as `PenpalASender`. + let extrinsic = construct_extrinsic_penpal(sender, call); + let result = Runtime::dry_run_extrinsic(extrinsic).unwrap(); + assert_eq!(result.forwarded_xcms.len(), 1); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + assert_eq!(messages_to_query.len(), 1); + remote_message = messages_to_query[0].clone(); + let delivery_fees = + Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) + .unwrap(); + delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // This is set in the Westend closure. + let mut intermediate_execution_fees = 0; + let mut intermediate_delivery_fees_amount = 0; + let mut intermediate_remote_message = VersionedXcm::V4(Xcm::<()>(Vec::new())); + ::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + + // First we get the execution fees. + let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); + intermediate_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Here.into())).unwrap(); + + // We have to do this to turn `VersionedXcm<()>` into `VersionedXcm`. + let xcm_program = + VersionedXcm::V4(Xcm::::from(remote_message.clone().try_into().unwrap())); + + // Now we get the delivery fees to the final destination. + let result = + Runtime::dry_run_xcm(sender_as_seen_by_relay.clone().into(), xcm_program).unwrap(); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + // There's actually two messages here. + // One created when the message we sent from PenpalA arrived and was executed. + // The second one when we dry-run the xcm. + // We could've gotten the message from the queue without having to dry-run, but + // offchain applications would have to dry-run, so we do it here as well. + intermediate_remote_message = messages_to_query[0].clone(); + let delivery_fees = Runtime::query_delivery_fees( + destination_to_query.clone(), + intermediate_remote_message.clone(), + ) + .unwrap(); + intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // Get the final execution fees in the destination. + let mut final_execution_fees = 0; + ::execute_with(|| { + type Runtime = ::Runtime; + + let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap(); + final_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) + .unwrap(); + }); + + // Dry-running is done. + PenpalA::reset_ext(); + Westend::reset_ext(); + PenpalB::reset_ext(); + + // Fund accounts again. + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + Westend::fund_accounts(vec![(sov_of_sender_on_relay.into(), amount_to_send * 2)]); + + // Actually run the extrinsic. + let test_args = TestContext { + sender: PenpalASender::get(), // Alice. + receiver: PenpalBReceiver::get(), // Bob in PenpalB. + args: TestArgs::new_para( + destination, + beneficiary_id.clone(), + amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = ParaToParaThroughRelayTest::new(test_args); + + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &beneficiary_id) + }); + + test.set_dispatchable::(transfer_assets_para_to_para); + test.assert(); + + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &beneficiary_id) + }); + + // We know the exact fees on every hop. + assert_eq!( + sender_assets_after, + sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly + * from the sender's + * account. */ + ); + assert_eq!( + receiver_assets_after, + receiver_assets_before + amount_to_send - + intermediate_execution_fees - + intermediate_delivery_fees_amount - + final_execution_fees + ); +} + +fn get_amount_from_versioned_assets(assets: VersionedAssets) -> u128 { + let latest_assets: Assets = assets.try_into().unwrap(); + let Fungible(amount) = latest_assets.inner()[0].fun else { + unreachable!("asset is fungible"); + }; + amount +} + +fn transfer_assets(test: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::transfer_assets( + test.signed_origin, + bx!(test.args.dest.into()), + bx!(test.args.beneficiary.into()), + bx!(test.args.assets.into()), + test.args.fee_asset_item, + test.args.weight_limit, + ) +} + +fn transfer_assets_para_to_para(test: ParaToParaThroughRelayTest) -> DispatchResult { + ::PolkadotXcm::transfer_assets( + test.signed_origin, + bx!(test.args.dest.into()), + bx!(test.args.beneficiary.into()), + bx!(test.args.assets.into()), + test.args.fee_asset_item, + test.args.weight_limit, + ) +} + +// Constructs the SignedExtra component of an extrinsic for the Westend runtime. +fn construct_extrinsic_westend( + sender: sp_keyring::AccountKeyring, + call: westend_runtime::RuntimeCall, +) -> westend_runtime::UncheckedExtrinsic { + type Runtime = ::Runtime; + let account_id = ::AccountId::from(sender.public()); + let tip = 0; + let extra: westend_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(sp_runtime::generic::Era::immortal()), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&account_id).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), + ); + let raw_payload = westend_runtime::SignedPayload::new(call, extra).unwrap(); + let signature = raw_payload.using_encoded(|payload| sender.sign(payload)); + let (call, extra, _) = raw_payload.deconstruct(); + westend_runtime::UncheckedExtrinsic::new_signed( + call, + account_id.into(), + MultiSignature::Sr25519(signature), + extra, + ) +} + +// Constructs the SignedExtra component of an extrinsic for the Westend runtime. +fn construct_extrinsic_penpal( + sender: sp_keyring::AccountKeyring, + call: penpal_runtime::RuntimeCall, +) -> penpal_runtime::UncheckedExtrinsic { + type Runtime = ::Runtime; + let account_id = ::AccountId::from(sender.public()); + let tip = 0; + let extra: penpal_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::immortal()), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&account_id).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_asset_tx_payment::ChargeAssetTxPayment::::from(tip, None), + ); + type SignedPayload = + generic::SignedPayload; + let raw_payload = SignedPayload::new(call, extra).unwrap(); + let signature = raw_payload.using_encoded(|payload| sender.sign(payload)); + let (call, extra, _) = raw_payload.deconstruct(); + penpal_runtime::UncheckedExtrinsic::new_signed( + call, + account_id.into(), + MultiSignature::Sr25519(signature), + extra, + ) +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 0733156716c1..c91ecd47ad70 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.11.1", default-features = false, features = ["derive # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } +frame-metadata-hash-extension = { path = "../../../../../substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "../../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../../substrate/frame/system", default-features = false } frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } @@ -195,6 +196,7 @@ std = [ "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -253,7 +255,10 @@ std = [ "xcm/std", ] +# Enable the metadata hash generation in the wasm builder. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs index 239ccac19ec7..99e510e22695 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/build.rs @@ -13,10 +13,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "std")] +#[cfg(all(not(feature = "metadata-hash"), feature = "std"))] fn main() { substrate_wasm_builder::WasmBuilder::build_using_defaults(); } +#[cfg(all(feature = "metadata-hash", feature = "std"))] +fn main() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("ROC", 12) + .build(); +} + #[cfg(not(feature = "std"))] fn main() {} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index f68d142fd5b8..f9a4e732930a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -956,6 +956,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, + frame_metadata_hash_extension::CheckMetadataHash, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index e25554ec0a5f..f7634addd437 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.11.1", default-features = false, features = ["derive # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } +frame-metadata-hash-extension = { path = "../../../../../substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "../../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../../substrate/frame/system", default-features = false } frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } @@ -183,6 +184,7 @@ std = [ "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -238,7 +240,10 @@ std = [ "xcm/std", ] +# Enable the metadata hash generation in the wasm builder. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs index 239ccac19ec7..cf9664aeb2f3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/build.rs @@ -13,10 +13,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "std")] +#[cfg(all(not(feature = "metadata-hash"), feature = "std"))] fn main() { substrate_wasm_builder::WasmBuilder::build_using_defaults(); } +#[cfg(all(feature = "metadata-hash", feature = "std"))] +fn main() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("WND", 12) + .build(); +} + #[cfg(not(feature = "std"))] fn main() {} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 4c5da99c8eaa..552f19349029 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -928,6 +928,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, + frame_metadata_hash_extension::CheckMetadataHash, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 64b23866f0cd..c2f20692bb4d 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -38,6 +38,7 @@ frame-system = { path = "../../substrate/frame/system", default-features = false frame-support = { path = "../../substrate/frame/support", default-features = false } frame-executive = { path = "../../substrate/frame/executive", default-features = false } pallet-example-single-block-migrations = { path = "../../substrate/frame/examples/single-block-migrations" } +frame-metadata-hash-extension = { path = "../../substrate/frame/metadata-hash-extension" } # Substrate Client sc-network = { path = "../../substrate/client/network" } diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 9688ab556473..0bfef59d2d30 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -67,6 +67,7 @@ sp-version = { path = "../../../substrate/primitives/version" } pallet-babe = { path = "../../../substrate/frame/babe" } pallet-staking = { path = "../../../substrate/frame/staking" } pallet-transaction-payment-rpc-runtime-api = { path = "../../../substrate/frame/transaction-payment/rpc/runtime-api" } +frame-metadata-hash-extension = { path = "../../../substrate/frame/metadata-hash-extension", optional = true } frame-system = { path = "../../../substrate/frame/system" } # Substrate Other @@ -187,8 +188,18 @@ full-node = [ ] # Configure the native runtimes to use. -westend-native = ["bitvec", "westend-runtime", "westend-runtime-constants"] -rococo-native = ["bitvec", "rococo-runtime", "rococo-runtime-constants"] +westend-native = [ + "bitvec", + "frame-metadata-hash-extension", + "westend-runtime", + "westend-runtime-constants", +] +rococo-native = [ + "bitvec", + "frame-metadata-hash-extension", + "rococo-runtime", + "rococo-runtime-constants", +] runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", diff --git a/polkadot/node/service/src/benchmarking.rs b/polkadot/node/service/src/benchmarking.rs index a0c4d3b04469..4dcff2078419 100644 --- a/polkadot/node/service/src/benchmarking.rs +++ b/polkadot/node/service/src/benchmarking.rs @@ -201,6 +201,7 @@ fn westend_sign_call( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), ); let payload = runtime::SignedPayload::from_raw( @@ -215,6 +216,7 @@ fn westend_sign_call( (), (), (), + None, ), ); @@ -253,6 +255,7 @@ fn rococo_sign_call( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), ); let payload = runtime::SignedPayload::from_raw( @@ -267,6 +270,7 @@ fn rococo_sign_call( (), (), (), + None, ), ); diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 19cc984e5829..ccb38ef36a4c 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -91,6 +91,7 @@ pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-feat pallet-root-testing = { path = "../../../substrate/frame/root-testing", default-features = false } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-metadata-hash-extension = { path = "../../../substrate/frame/metadata-hash-extension", default-features = false } frame-try-runtime = { path = "../../../substrate/frame/try-runtime", default-features = false, optional = true } frame-system-benchmarking = { path = "../../../substrate/frame/system/benchmarking", default-features = false, optional = true } hex-literal = { version = "0.4.1" } @@ -129,6 +130,7 @@ std = [ "block-builder-api/std", "frame-benchmarking?/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -313,6 +315,9 @@ try-runtime = [ "sp-runtime/try-runtime", ] +# Enable the metadata hash generation in the wasm builder. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + # Set timing constants (e.g. session period) to faster versions to speed up testing. fast-runtime = ["rococo-runtime-constants/fast-runtime"] @@ -321,4 +326,4 @@ runtime-metrics = ["runtime-parachains/runtime-metrics", "sp-io/with-tracing"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] diff --git a/polkadot/runtime/rococo/build.rs b/polkadot/runtime/rococo/build.rs index 403c31ff21c7..7aae84cd5e0f 100644 --- a/polkadot/runtime/rococo/build.rs +++ b/polkadot/runtime/rococo/build.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -#[cfg(feature = "std")] +#[cfg(all(not(feature = "metadata-hash"), feature = "std"))] fn main() { substrate_wasm_builder::WasmBuilder::build_using_defaults(); @@ -24,5 +24,18 @@ fn main() { .build(); } +#[cfg(all(feature = "metadata-hash", feature = "std"))] +fn main() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("ROC", 12) + .build(); + + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .enable_metadata_hash("ROC", 12) + .build(); +} + #[cfg(not(feature = "std"))] fn main() {} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index b8ab319ef0b6..0f653c1d3413 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -551,7 +551,9 @@ where frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_metadata_hash_extension::CheckMetadataHash::new(true), ); + let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { log::warn!("Unable to create signed payload: {:?}", e); @@ -1414,6 +1416,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index d726adfb8e6e..6ac22db74c82 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -45,6 +45,7 @@ sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", def frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support", default-features = false } frame-executive = { path = "../../../substrate/frame/executive", default-features = false } +frame-metadata-hash-extension = { path = "../../../substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false, features = ["experimental", "tuples-96"] } frame-system = { path = "../../../substrate/frame/system", default-features = false } frame-system-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false } @@ -141,6 +142,7 @@ std = [ "frame-benchmarking?/std", "frame-election-provider-support/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -337,6 +339,9 @@ try-runtime = [ "sp-runtime/try-runtime", ] +# Enable the metadata hash generation in the wasm builder. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + # Set timing constants (e.g. session period) to faster versions to speed up testing. fast-runtime = [] @@ -345,4 +350,4 @@ runtime-metrics = ["runtime-parachains/runtime-metrics", "sp-io/with-tracing"] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm # to make it smaller, like logging for example. -on-chain-release-build = ["sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash", "sp-api/disable-logging"] diff --git a/polkadot/runtime/westend/build.rs b/polkadot/runtime/westend/build.rs index 0b3e12c78c74..8ff3a4fb9112 100644 --- a/polkadot/runtime/westend/build.rs +++ b/polkadot/runtime/westend/build.rs @@ -14,8 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use substrate_wasm_builder::WasmBuilder; +#[cfg(all(not(feature = "metadata-hash"), feature = "std"))] +fn main() { + substrate_wasm_builder::WasmBuilder::build_using_defaults(); +} +#[cfg(all(feature = "metadata-hash", feature = "std"))] fn main() { - WasmBuilder::build_using_defaults(); + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("WND", 12) + .build(); } + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index ad44d8bb5061..41beb92a0036 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -777,6 +777,7 @@ where frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { @@ -1585,6 +1586,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, ); pub struct NominationPoolsMigrationV4OldPallet; diff --git a/prdoc/pr_4274.prdoc b/prdoc/pr_4274.prdoc new file mode 100644 index 000000000000..77f5d1387cf7 --- /dev/null +++ b/prdoc/pr_4274.prdoc @@ -0,0 +1,39 @@ +title: Introduce `CheckMetadataHash` signed extension + +doc: + - audience: Runtime Dev + description: | + Introduces the new `CheckMetadataHash` signed extension. This extension can be added to a + runtime to support verifying the metadata hash as described in + [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). + This removes the requirement for having a metadata portal and in general a centralized + authentication of the metadata. With this signed extension the runtime is able to verify + that the metadata used by the wallet was correct. This is mainly useful for offline wallets + which users need to trust any way, not that useful for online wallets. + + There is a guide `generate_metadata_hash` for how to integrate this into a runtime that + should make it quite easy to integrate the signed extension. + - audience: Runtime User + description: | + This brings support for the new Ledger app and similar hardware wallets. These hardware + wallets will be able to decode the transaction using the metadata. The runtime will + ensure that the metadata used for this decoding process is correct and that the online + wallet did not tried to trick you. + +crates: + - name: substrate-wasm-builder + bump: minor + - name: sc-executor-wasmtime + bump: patch + - name: frame-metadata-hash-extension + bump: major + - name: polkadot-service + bump: none + - name: rococo-runtime + bump: major + - name: westend-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 6346063b9d27..70061546bd41 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -98,6 +98,7 @@ sc-offchain = { path = "../../../client/offchain" } # frame dependencies frame-benchmarking = { path = "../../../frame/benchmarking" } +frame-metadata-hash-extension = { path = "../../../frame/metadata-hash-extension" } frame-system = { path = "../../../frame/system" } frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api" } pallet-assets = { path = "../../../frame/assets" } diff --git a/substrate/bin/node/cli/benches/executor.rs b/substrate/bin/node/cli/benches/executor.rs index a326e1a79ea3..30b52b9ecf6d 100644 --- a/substrate/bin/node/cli/benches/executor.rs +++ b/substrate/bin/node/cli/benches/executor.rs @@ -55,7 +55,7 @@ const HEAP_PAGES: u64 = 20; type TestExternalities = CoreTestExternalities; fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { - node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH) + node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH, None) } fn new_test_ext(genesis_config: &RuntimeGenesisConfig) -> TestExternalities { diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index d6e2a29d30b8..221599fd148e 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -124,6 +124,7 @@ pub fn create_extrinsic( kitchensink_runtime::Runtime, >::from(tip, None), ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), ); let raw_payload = kitchensink_runtime::SignedPayload::from_raw( @@ -138,6 +139,7 @@ pub fn create_extrinsic( (), (), (), + None, ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); @@ -996,6 +998,7 @@ mod tests { let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None), ); + let metadata_hash = frame_metadata_hash_extension::CheckMetadataHash::new(false); let extra = ( check_non_zero_sender, check_spec_version, @@ -1005,11 +1008,22 @@ mod tests { check_nonce, check_weight, tx_payment, + metadata_hash, ); let raw_payload = SignedPayload::from_raw( function, extra, - ((), spec_version, transaction_version, genesis_hash, genesis_hash, (), (), ()), + ( + (), + spec_version, + transaction_version, + genesis_hash, + genesis_hash, + (), + (), + (), + None, + ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); let (function, extra, _) = raw_payload.deconstruct(); diff --git a/substrate/bin/node/cli/tests/common.rs b/substrate/bin/node/cli/tests/common.rs index 2d74cdd5a041..8de87c8b76e6 100644 --- a/substrate/bin/node/cli/tests/common.rs +++ b/substrate/bin/node/cli/tests/common.rs @@ -83,7 +83,7 @@ pub const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_ve pub type TestExternalities = CoreTestExternalities; pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { - node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH) + node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH, None) } pub fn default_transfer_call() -> pallet_balances::Call { diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 8f68b1d3e2f0..a9e6c4c298af 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -58,6 +58,7 @@ sp-io = { path = "../../../primitives/io", default-features = false } frame-executive = { path = "../../../frame/executive", default-features = false } frame-benchmarking = { path = "../../../frame/benchmarking", default-features = false } frame-benchmarking-pallet-pov = { path = "../../../frame/benchmarking/pov", default-features = false } +frame-metadata-hash-extension = { path = "../../../frame/metadata-hash-extension", default-features = false } frame-support = { path = "../../../frame/support", default-features = false, features = ["experimental", "tuples-96"] } frame-system = { path = "../../../frame/system", default-features = false } frame-system-benchmarking = { path = "../../../frame/system/benchmarking", default-features = false, optional = true } @@ -157,6 +158,7 @@ std = [ "frame-benchmarking/std", "frame-election-provider-support/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -428,3 +430,5 @@ experimental = [ "frame-system/experimental", "pallet-example-tasks/experimental", ] + +metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/substrate/bin/node/runtime/build.rs b/substrate/bin/node/runtime/build.rs index b7676a70dfe8..0e11c579f09e 100644 --- a/substrate/bin/node/runtime/build.rs +++ b/substrate/bin/node/runtime/build.rs @@ -15,13 +15,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(all(feature = "std", not(feature = "metadata-hash")))] fn main() { - #[cfg(feature = "std")] - { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build(); - } + substrate_wasm_builder::WasmBuilder::build_using_defaults() } + +#[cfg(all(feature = "std", feature = "metadata-hash"))] +fn main() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("Test", 14) + .build() +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index a9606ac0bb75..c71223e0ace6 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1423,6 +1423,7 @@ where tip, None, ), ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { @@ -2468,6 +2469,7 @@ pub type SignedExtra = ( Runtime, pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, >, + frame_metadata_hash_extension::CheckMetadataHash, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index fa3f90193ba5..7f930d9a53b3 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -21,6 +21,7 @@ fs_extra = "1" futures = "0.3.30" log = { workspace = true, default-features = true } tempfile = "3.1.0" +frame-metadata-hash-extension = { path = "../../../frame/metadata-hash-extension" } frame-system = { path = "../../../frame/system" } node-cli = { package = "staging-node-cli", path = "../cli" } node-primitives = { path = "../primitives" } diff --git a/substrate/bin/node/testing/src/bench.rs b/substrate/bin/node/testing/src/bench.rs index e5c2563905e9..007d314684cf 100644 --- a/substrate/bin/node/testing/src/bench.rs +++ b/substrate/bin/node/testing/src/bench.rs @@ -571,6 +571,8 @@ impl BenchKeyring { tx_version, genesis_hash, genesis_hash, + // metadata_hash + None::<()>, ); let key = self.accounts.get(&signed).expect("Account id not found in keyring"); let signature = payload.using_encoded(|b| { diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index f712191bed69..eab088d9100e 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -82,6 +82,7 @@ pub fn signed_extra(nonce: Nonce, extra_fee: Balance) -> SignedExtra { pallet_skip_feeless_payment::SkipCheckIfFeeless::from( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), ) } @@ -91,11 +92,19 @@ pub fn sign( spec_version: u32, tx_version: u32, genesis_hash: [u8; 32], + metadata_hash: Option<[u8; 32]>, ) -> UncheckedExtrinsic { match xt.signed { Some((signed, extra)) => { - let payload = - (xt.function, extra.clone(), spec_version, tx_version, genesis_hash, genesis_hash); + let payload = ( + xt.function, + extra.clone(), + spec_version, + tx_version, + genesis_hash, + genesis_hash, + metadata_hash, + ); let key = AccountKeyring::from_account_id(&signed).unwrap(); let signature = payload diff --git a/substrate/client/executor/wasmtime/src/lib.rs b/substrate/client/executor/wasmtime/src/lib.rs index 82e62b4a5dd3..8e8e92017df9 100644 --- a/substrate/client/executor/wasmtime/src/lib.rs +++ b/substrate/client/executor/wasmtime/src/lib.rs @@ -41,3 +41,7 @@ pub use runtime::{ prepare_runtime_artifact, Config, DeterministicStackLimit, InstantiationStrategy, Semantics, WasmtimeRuntime, }; +pub use sc_executor_common::{ + runtime_blob::RuntimeBlob, + wasm_runtime::{HeapAllocStrategy, WasmModule}, +}; diff --git a/substrate/frame/metadata-hash-extension/Cargo.toml b/substrate/frame/metadata-hash-extension/Cargo.toml new file mode 100644 index 000000000000..13d4bd0c2ea9 --- /dev/null +++ b/substrate/frame/metadata-hash-extension/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "frame-metadata-hash-extension" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME signed extension for verifying the metadata hash" + +[dependencies] +array-bytes = "6.2.2" +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["derive", "serde"] } +sp-runtime = { path = "../../primitives/runtime", default-features = false, features = ["serde"] } +frame-support = { path = "../support", default-features = false } +frame-system = { path = "../system", default-features = false } +log = { workspace = true, default-features = false } +docify = "0.2.8" + +[dev-dependencies] +substrate-wasm-builder = { path = "../../utils/wasm-builder", features = ["metadata-hash"] } +substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } +sp-api = { path = "../../primitives/api" } +sp-transaction-pool = { path = "../../primitives/transaction-pool" } +merkleized-metadata = "0.1.0" +frame-metadata = { version = "16.0.0", features = ["current"] } +sp-tracing = { path = "../../primitives/tracing" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-runtime/std", +] diff --git a/substrate/frame/metadata-hash-extension/src/lib.rs b/substrate/frame/metadata-hash-extension/src/lib.rs new file mode 100644 index 000000000000..d09acbfb3df2 --- /dev/null +++ b/substrate/frame/metadata-hash-extension/src/lib.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +//! The [`CheckMetadataHash`] signed extension. +//! +//! The extension for optionally checking the metadata hash. For information how it works and what +//! it does exactly, see the docs of [`CheckMetadataHash`]. +//! +//! # Integration +//! +//! As any signed extension you will need to add it to your runtime signed extensions: +#![doc = docify::embed!("src/tests.rs", add_metadata_hash_extension)] +//! As the extension requires the `RUNTIME_METADATA_HASH` environment variable to be present at +//! compile time, it requires a little bit more setup. To have this environment variable available +//! at compile time required to tell the `substrate-wasm-builder` to do so: +#![doc = docify::embed!("src/tests.rs", enable_metadata_hash_in_wasm_builder)] +//! As generating the metadata hash requires to compile the runtime twice, it is +//! recommended to only enable the metadata hash generation when doing a build for a release or when +//! you want to test this feature. + +extern crate alloc; +/// For our tests +extern crate self as frame_metadata_hash_extension; + +use codec::{Decode, Encode}; +use frame_support::DebugNoBound; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{TransactionValidityError, UnknownTransaction}, +}; + +#[cfg(test)] +mod tests; + +/// The mode of [`CheckMetadataHash`]. +#[derive(Decode, Encode, PartialEq, Debug, TypeInfo, Clone, Copy, Eq)] +enum Mode { + Disabled, + Enabled, +} + +/// Wrapper around the metadata hash and from where to get it from. +#[derive(Default, Debug, PartialEq, Clone, Copy, Eq)] +enum MetadataHash { + /// Fetch it from the `RUNTIME_METADATA_HASH` env variable at compile time. + #[default] + FetchFromEnv, + /// Use the given metadata hash. + Custom([u8; 32]), +} + +impl MetadataHash { + /// Returns the metadata hash. + fn hash(&self) -> Option<[u8; 32]> { + match self { + Self::FetchFromEnv => + option_env!("RUNTIME_METADATA_HASH").map(array_bytes::hex2array_unchecked), + Self::Custom(hash) => Some(*hash), + } + } +} + +/// Extension for optionally verifying the metadata hash. +/// +/// The metadata hash is cryptographical representation of the runtime metadata. This metadata hash +/// is build as described in [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). +/// This metadata hash should give users the confidence that what they build with an online wallet +/// is the same they are signing with their offline wallet and then applying on chain. To ensure +/// that the online wallet is not tricking the offline wallet into decoding and showing an incorrect +/// extrinsic, the offline wallet will include the metadata hash into the additional signed data and +/// the runtime will then do the same. If the metadata hash doesn't match, the signature +/// verification will fail and thus, the transaction will be rejected. The RFC contains more details +/// on how it works. +/// +/// The extension adds one byte (the `mode`) to the size of the extrinsic. This one byte is +/// controlling if the metadata hash should be added to the signed data or not. Mode `0` means that +/// the metadata hash is not added and thus, `None` is added to the signed data. Mode `1` means that +/// the metadata hash is added and thus, `Some(metadata_hash)` is added to the signed data. Further +/// values of `mode` are reserved for future changes. +/// +/// The metadata hash is read from the environment variable `RUNTIME_METADATA_HASH`. This +/// environment variable is for example set by the `substrate-wasm-builder` when the feature for +/// generating the metadata hash is enabled. If the environment variable is not set and `mode = 1` +/// is passed, the transaction is rejected with [`UnknownTransaction::CannotLookup`]. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, DebugNoBound)] +#[scale_info(skip_type_params(T))] +pub struct CheckMetadataHash { + _phantom: core::marker::PhantomData, + mode: Mode, + #[codec(skip)] + metadata_hash: MetadataHash, +} + +impl CheckMetadataHash { + /// Creates new `SignedExtension` to check metadata hash. + pub fn new(enable: bool) -> Self { + Self { + _phantom: core::marker::PhantomData, + mode: if enable { Mode::Enabled } else { Mode::Disabled }, + metadata_hash: MetadataHash::FetchFromEnv, + } + } + + /// Create an instance that uses the given `metadata_hash`. + /// + /// This is useful for testing the extension. + pub fn new_with_custom_hash(metadata_hash: [u8; 32]) -> Self { + Self { + _phantom: core::marker::PhantomData, + mode: Mode::Enabled, + metadata_hash: MetadataHash::Custom(metadata_hash), + } + } +} + +impl SignedExtension for CheckMetadataHash { + type AccountId = T::AccountId; + type Call = ::RuntimeCall; + type AdditionalSigned = Option<[u8; 32]>; + type Pre = (); + const IDENTIFIER: &'static str = "CheckMetadataHash"; + + fn additional_signed(&self) -> Result { + let signed = match self.mode { + Mode::Disabled => None, + Mode::Enabled => match self.metadata_hash.hash() { + Some(hash) => Some(hash), + None => return Err(UnknownTransaction::CannotLookup.into()), + }, + }; + + log::debug!( + target: "runtime::metadata-hash", + "CheckMetadataHash::additional_signed => {:?}", + signed.as_ref().map(|h| array_bytes::bytes2hex("0x", h)), + ); + + Ok(signed) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } +} diff --git a/substrate/frame/metadata-hash-extension/src/tests.rs b/substrate/frame/metadata-hash-extension/src/tests.rs new file mode 100644 index 000000000000..f13eecfd94bf --- /dev/null +++ b/substrate/frame/metadata-hash-extension/src/tests.rs @@ -0,0 +1,179 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +use crate::CheckMetadataHash; +use codec::{Decode, Encode}; +use frame_metadata::RuntimeMetadataPrefixed; +use frame_support::{ + derive_impl, + pallet_prelude::{InvalidTransaction, TransactionValidityError}, +}; +use merkleized_metadata::{generate_metadata_digest, ExtraInfo}; +use sp_api::{Metadata, ProvideRuntimeApi}; +use sp_runtime::{ + traits::{Extrinsic as _, SignedExtension}, + transaction_validity::{TransactionSource, UnknownTransaction}, +}; +use sp_transaction_pool::runtime_api::TaggedTransactionQueue; +use substrate_test_runtime_client::{ + prelude::*, + runtime::{self, ExtrinsicBuilder}, + DefaultTestClientBuilderExt, TestClientBuilder, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime! { + pub enum Test { + System: frame_system, + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; +} + +#[test] +fn rejects_when_no_metadata_hash_was_passed() { + let ext = CheckMetadataHash::::decode(&mut &1u8.encode()[..]).unwrap(); + assert_eq!(Err(UnknownTransaction::CannotLookup.into()), ext.additional_signed()); +} + +#[test] +fn rejects_unknown_mode() { + assert!(CheckMetadataHash::::decode(&mut &50u8.encode()[..]).is_err()); +} + +/// Generate the metadata hash for the `test-runtime`. +fn generate_metadata_hash(metadata: RuntimeMetadataPrefixed) -> [u8; 32] { + let runtime_version = runtime::VERSION; + let base58_prefix = 0; + + let extra_info = ExtraInfo { + spec_version: runtime_version.spec_version, + spec_name: runtime_version.spec_name.into(), + base58_prefix, + decimals: 10, + token_symbol: "TOKEN".into(), + }; + + generate_metadata_digest(&metadata.1, extra_info).unwrap().hash() +} + +#[test] +fn ensure_check_metadata_works_on_real_extrinsics() { + sp_tracing::try_init_simple(); + + let client = TestClientBuilder::new().build(); + let runtime_api = client.runtime_api(); + let best_hash = client.chain_info().best_hash; + + let metadata = RuntimeMetadataPrefixed::decode( + &mut &runtime_api.metadata_at_version(best_hash, 15).unwrap().unwrap()[..], + ) + .unwrap(); + + let valid_transaction = ExtrinsicBuilder::new_include_data(vec![1, 2, 3]) + .metadata_hash(generate_metadata_hash(metadata)) + .build(); + // Ensure that the transaction is signed. + assert!(valid_transaction.is_signed().unwrap()); + + runtime_api + .validate_transaction(best_hash, TransactionSource::External, valid_transaction, best_hash) + .unwrap() + .unwrap(); + + // Including some random metadata hash should make the transaction invalid. + let invalid_transaction = ExtrinsicBuilder::new_include_data(vec![1, 2, 3]) + .metadata_hash([10u8; 32]) + .build(); + // Ensure that the transaction is signed. + assert!(invalid_transaction.is_signed().unwrap()); + + assert_eq!( + TransactionValidityError::from(InvalidTransaction::BadProof), + runtime_api + .validate_transaction( + best_hash, + TransactionSource::External, + invalid_transaction, + best_hash + ) + .unwrap() + .unwrap_err() + ); +} + +#[allow(unused)] +mod docs { + use super::*; + + #[docify::export] + mod add_metadata_hash_extension { + frame_support::construct_runtime! { + pub enum Runtime { + System: frame_system, + } + } + + /// The `SignedExtension` to the basic transaction logic. + pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckMortality, + frame_system::CheckNonce, + frame_system::CheckWeight, + // Add the `CheckMetadataHash` extension. + // The position in this list is not important, so we could also add it to beginning. + frame_metadata_hash_extension::CheckMetadataHash, + ); + + /// In your runtime this will be your real address type. + type Address = (); + /// In your runtime this will be your real signature type. + type Signature = (); + + /// Unchecked extrinsic type as expected by this runtime. + pub type UncheckedExtrinsic = + sp_runtime::generic::UncheckedExtrinsic; + } + + // Put here to not have it in the docs as well. + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for add_metadata_hash_extension::Runtime { + type Block = Block; + type RuntimeEvent = add_metadata_hash_extension::RuntimeEvent; + type RuntimeOrigin = add_metadata_hash_extension::RuntimeOrigin; + type RuntimeCall = add_metadata_hash_extension::RuntimeCall; + type PalletInfo = add_metadata_hash_extension::PalletInfo; + } + + #[docify::export] + fn enable_metadata_hash_in_wasm_builder() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + // Requires the `metadata-hash` feature to be activated. + // You need to pass the main token symbol and its number of decimals. + .enable_metadata_hash("TOKEN", 12) + // The runtime will be build twice and the second time the `RUNTIME_METADATA_HASH` + // environment variable will be set for the `CheckMetadataHash` extension. + .build() + } +} diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index ffbd59f39ad2..366b4c959d59 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -37,6 +37,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false, feat pallet-babe = { path = "../../frame/babe", default-features = false } pallet-balances = { path = "../../frame/balances", default-features = false } frame-executive = { path = "../../frame/executive", default-features = false } +frame-metadata-hash-extension = { path = "../../frame/metadata-hash-extension", default-features = false } frame-system = { path = "../../frame/system", default-features = false } frame-system-rpc-runtime-api = { path = "../../frame/system/rpc/runtime-api", default-features = false } pallet-timestamp = { path = "../../frame/timestamp", default-features = false } @@ -65,7 +66,7 @@ serde = { features = ["alloc", "derive"], workspace = true } serde_json = { features = ["alloc"], workspace = true } [build-dependencies] -substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true } +substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true, features = ["metadata-hash"] } [features] default = ["std"] @@ -74,6 +75,7 @@ std = [ "array-bytes", "codec/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", @@ -110,5 +112,6 @@ std = [ "substrate-wasm-builder", "trie-db/std", ] + # Special feature to disable logging disable-logging = ["sp-api/disable-logging"] diff --git a/substrate/test-utils/runtime/build.rs b/substrate/test-utils/runtime/build.rs index dd79ce2c5ae8..d38173fcfcb4 100644 --- a/substrate/test-utils/runtime/build.rs +++ b/substrate/test-utils/runtime/build.rs @@ -25,6 +25,7 @@ fn main() { // to this value by default. This is because some of our tests // (`restoration_of_globals`) depend on the stack-size. .append_to_rust_flags("-Clink-arg=-zstack-size=1048576") + .enable_metadata_hash("TOKEN", 10) .import_memory() .build(); } diff --git a/substrate/test-utils/runtime/src/extrinsic.rs b/substrate/test-utils/runtime/src/extrinsic.rs index e355e5d099ad..5ae0d8f8f6ec 100644 --- a/substrate/test-utils/runtime/src/extrinsic.rs +++ b/substrate/test-utils/runtime/src/extrinsic.rs @@ -22,10 +22,11 @@ use crate::{ CheckSubstrateCall, Extrinsic, Nonce, Pair, RuntimeCall, SignedPayload, TransferData, }; use codec::Encode; +use frame_metadata_hash_extension::CheckMetadataHash; use frame_system::{CheckNonce, CheckWeight}; use sp_core::crypto::Pair as TraitPair; use sp_keyring::AccountKeyring; -use sp_runtime::{transaction_validity::TransactionPriority, Perbill}; +use sp_runtime::{traits::SignedExtension, transaction_validity::TransactionPriority, Perbill}; /// Transfer used in test substrate pallet. Extrinsic is created and signed using this data. #[derive(Clone)] @@ -81,17 +82,23 @@ pub struct ExtrinsicBuilder { function: RuntimeCall, signer: Option, nonce: Option, + metadata_hash: Option<[u8; 32]>, } impl ExtrinsicBuilder { /// Create builder for given `RuntimeCall`. By default `Extrinsic` will be signed by `Alice`. pub fn new(function: impl Into) -> Self { - Self { function: function.into(), signer: Some(AccountKeyring::Alice.pair()), nonce: None } + Self { + function: function.into(), + signer: Some(AccountKeyring::Alice.pair()), + nonce: None, + metadata_hash: None, + } } /// Create builder for given `RuntimeCall`. `Extrinsic` will be unsigned. pub fn new_unsigned(function: impl Into) -> Self { - Self { function: function.into(), signer: None, nonce: None } + Self { function: function.into(), signer: None, nonce: None, metadata_hash: None } } /// Create builder for `pallet_call::bench_transfer` from given `TransferData`. @@ -105,6 +112,7 @@ impl ExtrinsicBuilder { Self { nonce: Some(transfer.nonce), signer: Some(transfer.from.clone()), + metadata_hash: None, ..Self::new(BalancesCall::transfer_allow_death { dest: transfer.to, value: transfer.amount, @@ -186,6 +194,12 @@ impl ExtrinsicBuilder { self } + /// Metadata hash to put into the signed data of the extrinsic. + pub fn metadata_hash(mut self, metadata_hash: [u8; 32]) -> Self { + self.metadata_hash = Some(metadata_hash); + self + } + /// Build `Extrinsic` using embedded parameters pub fn build(self) -> Extrinsic { if let Some(signer) = self.signer { @@ -193,9 +207,15 @@ impl ExtrinsicBuilder { CheckNonce::from(self.nonce.unwrap_or(0)), CheckWeight::new(), CheckSubstrateCall {}, + self.metadata_hash + .map(CheckMetadataHash::new_with_custom_hash) + .unwrap_or_else(|| CheckMetadataHash::new(false)), + ); + let raw_payload = SignedPayload::from_raw( + self.function.clone(), + extra.clone(), + extra.additional_signed().unwrap(), ); - let raw_payload = - SignedPayload::from_raw(self.function.clone(), extra.clone(), ((), (), ())); let signature = raw_payload.using_encoded(|e| signer.sign(e)); Extrinsic::new_signed(self.function, signer.public(), signature, extra) diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 7148d2b2fc0f..52bcf4269284 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -146,7 +146,12 @@ pub type Signature = sr25519::Signature; pub type Pair = sp_core::sr25519::Pair; /// The SignedExtension to the basic transaction logic. -pub type SignedExtra = (CheckNonce, CheckWeight, CheckSubstrateCall); +pub type SignedExtra = ( + CheckNonce, + CheckWeight, + CheckSubstrateCall, + frame_metadata_hash_extension::CheckMetadataHash, +); /// The payload being signed in transactions. pub type SignedPayload = sp_runtime::generic::SignedPayload; /// Unchecked extrinsic type as expected by this runtime. @@ -491,14 +496,14 @@ impl_runtime_apis! { impl sp_api::Metadata for Runtime { fn metadata() -> OpaqueMetadata { - unimplemented!() + OpaqueMetadata::new(Runtime::metadata().into()) } - fn metadata_at_version(_version: u32) -> Option { - unimplemented!() + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) } fn metadata_versions() -> alloc::vec::Vec { - unimplemented!() + Runtime::metadata_versions() } } diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index bac323e2e6a0..090955494f0a 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -27,3 +27,34 @@ filetime = "0.2.16" wasm-opt = "0.116" parity-wasm = "0.45" polkavm-linker = { workspace = true } + +# Dependencies required for the `metadata-hash` feature. +merkleized-metadata = { version = "0.1.0", optional = true } +sc-executor = { path = "../../client/executor", optional = true } +sp-core = { path = "../../primitives/core", optional = true } +sp-io = { path = "../../primitives/io", optional = true } +sp-version = { path = "../../primitives/version", optional = true } +frame-metadata = { version = "16.0.0", features = ["current"], optional = true } +codec = { package = "parity-scale-codec", version = "3.1.5", optional = true } +array-bytes = { version = "6.1", optional = true } +sp-tracing = { path = "../../primitives/tracing", optional = true } + +[features] +# Enable support for generating the metadata hash. +# +# To generate the metadata hash the runtime is build once, executed to build the metadata and then +# build a second time with the `RUNTIME_METADATA_HASH` environment variable set. The environment +# variable then contains the hash and can be used inside the runtime. +# +# This pulls in quite a lot of dependencies and thus, is disabled by default. +metadata-hash = [ + "array-bytes", + "codec", + "frame-metadata", + "merkleized-metadata", + "sc-executor", + "sp-core", + "sp-io", + "sp-tracing", + "sp-version", +] diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index 163703fbec62..37c6c4aa7431 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -23,6 +23,13 @@ use std::{ use crate::RuntimeTarget; +/// Extra information when generating the `metadata-hash`. +#[cfg(feature = "metadata-hash")] +pub(crate) struct MetadataExtraInfo { + pub decimals: u8, + pub token_symbol: String, +} + /// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env. fn get_manifest_dir() -> PathBuf { env::var("CARGO_MANIFEST_DIR") @@ -53,6 +60,8 @@ impl WasmBuilderSelectProject { disable_runtime_version_section_check: false, export_heap_base: false, import_memory: false, + #[cfg(feature = "metadata-hash")] + enable_metadata_hash: None, } } @@ -71,6 +80,8 @@ impl WasmBuilderSelectProject { disable_runtime_version_section_check: false, export_heap_base: false, import_memory: false, + #[cfg(feature = "metadata-hash")] + enable_metadata_hash: None, }) } else { Err("Project path must point to the `Cargo.toml` of the project") @@ -108,6 +119,10 @@ pub struct WasmBuilder { export_heap_base: bool, /// Whether `--import-memory` should be added to the link args (WASM-only). import_memory: bool, + + /// Whether to enable the metadata hash generation. + #[cfg(feature = "metadata-hash")] + enable_metadata_hash: Option, } impl WasmBuilder { @@ -191,6 +206,22 @@ impl WasmBuilder { self } + /// Enable generation of the metadata hash. + /// + /// This will compile the runtime once, fetch the metadata, build the metadata hash and + /// then compile again with the env `RUNTIME_METADATA_HASH` set. For more information + /// about the metadata hash see [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). + /// + /// - `token_symbol`: The symbol of the main native token of the chain. + /// - `decimals`: The number of decimals of the main native token. + #[cfg(feature = "metadata-hash")] + pub fn enable_metadata_hash(mut self, token_symbol: impl Into, decimals: u8) -> Self { + self.enable_metadata_hash = + Some(MetadataExtraInfo { token_symbol: token_symbol.into(), decimals }); + + self + } + /// Disable the check for the `runtime_version` wasm section. /// /// By default the `wasm-builder` will ensure that the `runtime_version` section will @@ -237,6 +268,8 @@ impl WasmBuilder { self.features_to_enable, self.file_name, !self.disable_runtime_version_section_check, + #[cfg(feature = "metadata-hash")] + self.enable_metadata_hash, ); // As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't @@ -311,6 +344,7 @@ fn build_project( features_to_enable: Vec, wasm_binary_name: Option, check_for_runtime_version_section: bool, + #[cfg(feature = "metadata-hash")] enable_metadata_hash: Option, ) { let cargo_cmd = match crate::prerequisites::check(target) { Ok(cmd) => cmd, @@ -328,6 +362,8 @@ fn build_project( features_to_enable, wasm_binary_name, check_for_runtime_version_section, + #[cfg(feature = "metadata-hash")] + enable_metadata_hash, ); let (wasm_binary, wasm_binary_bloaty) = if let Some(wasm_binary) = wasm_binary { diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 9ebab38b9cb2..07de4c15831b 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -116,6 +116,8 @@ use std::{ use version::Version; mod builder; +#[cfg(feature = "metadata-hash")] +mod metadata_hash; mod prerequisites; mod version; mod wasm_project; @@ -238,7 +240,7 @@ fn get_rustup_command(target: RuntimeTarget) -> Option { } /// Wraps a specific command which represents a cargo invocation. -#[derive(Debug)] +#[derive(Debug, Clone)] struct CargoCommand { program: String, args: Vec, @@ -350,6 +352,7 @@ impl CargoCommand { } /// Wraps a [`CargoCommand`] and the version of `rustc` the cargo command uses. +#[derive(Clone)] struct CargoCommandVersioned { command: CargoCommand, version: String, diff --git a/substrate/utils/wasm-builder/src/metadata_hash.rs b/substrate/utils/wasm-builder/src/metadata_hash.rs new file mode 100644 index 000000000000..1003f2d18eaf --- /dev/null +++ b/substrate/utils/wasm-builder/src/metadata_hash.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +use crate::builder::MetadataExtraInfo; +use codec::{Decode, Encode}; +use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed}; +use merkleized_metadata::{generate_metadata_digest, ExtraInfo}; +use sc_executor::WasmExecutor; +use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode, WrappedRuntimeCode}; +use std::path::Path; + +/// The host functions that we provide when calling into the wasm file. +/// +/// Any other host function will return an error. +type HostFunctions = ( + // The allocator functions. + sp_io::allocator::HostFunctions, + // Logging is good to have for debugging issues. + sp_io::logging::HostFunctions, + // Give access to the "state", actually the state will be empty, but some chains put constants + // into the state and this would panic at metadata generation. Thus, we give them an empty + // state to not panic. + sp_io::storage::HostFunctions, + // The hashing functions. + sp_io::hashing::HostFunctions, +); + +/// Generate the metadata hash. +/// +/// The metadata hash is generated as specced in +/// [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). +/// +/// Returns the metadata hash. +pub fn generate_metadata_hash(wasm: &Path, extra_info: MetadataExtraInfo) -> [u8; 32] { + sp_tracing::try_init_simple(); + + let wasm = std::fs::read(wasm).expect("Wasm file was just created and should be readable."); + + let executor = WasmExecutor::::builder() + .with_allow_missing_host_functions(true) + .build(); + + let runtime_code = RuntimeCode { + code_fetcher: &WrappedRuntimeCode(wasm.into()), + heap_pages: None, + // The hash is only used for caching and thus, not that important for our use case here. + hash: vec![1, 2, 3], + }; + + let metadata = executor + .call( + &mut sp_io::TestExternalities::default().ext(), + &runtime_code, + "Metadata_metadata_at_version", + &15u32.encode(), + CallContext::Offchain, + ) + .0 + .expect("`Metadata::metadata_at_version` should exist."); + + let metadata = Option::>::decode(&mut &metadata[..]) + .ok() + .flatten() + .expect("Metadata V15 support is required."); + + let metadata = RuntimeMetadataPrefixed::decode(&mut &metadata[..]) + .expect("Invalid encoded metadata?") + .1; + + let runtime_version = executor + .call( + &mut sp_io::TestExternalities::default().ext(), + &runtime_code, + "Core_version", + &[], + CallContext::Offchain, + ) + .0 + .expect("`Core_version` should exist."); + let runtime_version = sp_version::RuntimeVersion::decode(&mut &runtime_version[..]) + .expect("Invalid `RuntimeVersion` encoding"); + + let base58_prefix = extract_ss58_prefix(&metadata); + + let extra_info = ExtraInfo { + spec_version: runtime_version.spec_version, + spec_name: runtime_version.spec_name.into(), + base58_prefix, + decimals: extra_info.decimals, + token_symbol: extra_info.token_symbol, + }; + + generate_metadata_digest(&metadata, extra_info) + .expect("Failed to generate the metadata digest") + .hash() +} + +/// Extract the `SS58` from the constants in the given `metadata`. +fn extract_ss58_prefix(metadata: &RuntimeMetadata) -> u16 { + let RuntimeMetadata::V15(ref metadata) = metadata else { + panic!("Metadata version 15 required") + }; + + let system = metadata + .pallets + .iter() + .find(|p| p.name == "System") + .expect("Each FRAME runtime has the `System` pallet; qed"); + + system + .constants + .iter() + .find_map(|c| { + (c.name == "SS58Prefix") + .then(|| u16::decode(&mut &c.value[..]).expect("SS58 is an `u16`; qed")) + }) + .expect("`SS58PREFIX` exists in the `System` constants; qed") +} diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index b58e6bfa36b4..ff6c8e38a332 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -15,6 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "metadata-hash")] +use crate::builder::MetadataExtraInfo; use crate::{write_file_if_changed, CargoCommandVersioned, RuntimeTarget, OFFLINE}; use build_helper::rerun_if_changed; @@ -113,57 +115,103 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { /// The path to the compact runtime binary and the bloaty runtime binary. pub(crate) fn create_and_compile( target: RuntimeTarget, - project_cargo_toml: &Path, + orig_project_cargo_toml: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, features_to_enable: Vec, - bloaty_blob_out_name_override: Option, + blob_out_name_override: Option, check_for_runtime_version_section: bool, + #[cfg(feature = "metadata-hash")] enable_metadata_hash: Option, ) -> (Option, WasmBinaryBloaty) { let runtime_workspace_root = get_wasm_workspace_root(); let runtime_workspace = runtime_workspace_root.join(target.build_subdirectory()); - let crate_metadata = crate_metadata(project_cargo_toml); + let crate_metadata = crate_metadata(orig_project_cargo_toml); let project = create_project( target, - project_cargo_toml, + orig_project_cargo_toml, &runtime_workspace, &crate_metadata, crate_metadata.workspace_root.as_ref(), features_to_enable, ); + let wasm_project_cargo_toml = project.join("Cargo.toml"); let build_config = BuildConfiguration::detect(target, &project); - // Build the bloaty runtime blob - let raw_blob_path = build_bloaty_blob( - target, - &build_config.blob_build_profile, - &project, - default_rustflags, - cargo_cmd, - ); + #[cfg(feature = "metadata-hash")] + let raw_blob_path = match enable_metadata_hash { + Some(extra_info) => { + // When the metadata hash is enabled we need to build the runtime twice. + let raw_blob_path = build_bloaty_blob( + target, + &build_config.blob_build_profile, + &project, + default_rustflags, + cargo_cmd.clone(), + None, + ); - let (final_blob_binary, bloaty_blob_binary) = match target { - RuntimeTarget::Wasm => compile_wasm( - project_cargo_toml, + let hash = crate::metadata_hash::generate_metadata_hash(&raw_blob_path, extra_info); + + build_bloaty_blob( + target, + &build_config.blob_build_profile, + &project, + default_rustflags, + cargo_cmd, + Some(hash), + ) + }, + None => build_bloaty_blob( + target, + &build_config.blob_build_profile, &project, - bloaty_blob_out_name_override, - check_for_runtime_version_section, - &build_config, + default_rustflags, + cargo_cmd, + None, ), + }; + + // If the feature is not enabled, we only need to do it once. + #[cfg(not(feature = "metadata-hash"))] + let raw_blob_path = { + build_bloaty_blob( + target, + &build_config.blob_build_profile, + &project, + default_rustflags, + cargo_cmd, + ) + }; + + let blob_name = + blob_out_name_override.unwrap_or_else(|| get_blob_name(target, &wasm_project_cargo_toml)); + + let (final_blob_binary, bloaty_blob_binary) = match target { + RuntimeTarget::Wasm => { + let out_path = project.join(format!("{blob_name}.wasm")); + fs::copy(raw_blob_path, &out_path).expect("copying the runtime blob should never fail"); + + maybe_compact_and_compress_wasm( + &wasm_project_cargo_toml, + &project, + WasmBinaryBloaty(out_path), + &blob_name, + check_for_runtime_version_section, + &build_config, + ) + }, RuntimeTarget::Riscv => { - let out_name = bloaty_blob_out_name_override - .unwrap_or_else(|| get_blob_name(target, project_cargo_toml)); - let out_path = project.join(format!("{out_name}.polkavm")); + let out_path = project.join(format!("{blob_name}.polkavm")); fs::copy(raw_blob_path, &out_path).expect("copying the runtime blob should never fail"); (None, WasmBinaryBloaty(out_path)) }, }; generate_rerun_if_changed_instructions( - project_cargo_toml, + orig_project_cargo_toml, &project, &runtime_workspace, final_blob_binary.as_ref(), @@ -177,25 +225,14 @@ pub(crate) fn create_and_compile( (final_blob_binary, bloaty_blob_binary) } -fn compile_wasm( - project_cargo_toml: &Path, +fn maybe_compact_and_compress_wasm( + wasm_project_cargo_toml: &Path, project: &Path, - bloaty_blob_out_name_override: Option, + bloaty_blob_binary: WasmBinaryBloaty, + blob_name: &str, check_for_runtime_version_section: bool, build_config: &BuildConfiguration, ) -> (Option, WasmBinaryBloaty) { - // Get the name of the bloaty runtime blob. - let bloaty_blob_default_name = get_blob_name(RuntimeTarget::Wasm, project_cargo_toml); - let bloaty_blob_out_name = - bloaty_blob_out_name_override.unwrap_or_else(|| bloaty_blob_default_name.clone()); - - let bloaty_blob_binary = copy_bloaty_blob( - &project, - &build_config.blob_build_profile, - &bloaty_blob_default_name, - &bloaty_blob_out_name, - ); - // Try to compact and compress the bloaty blob, if the *outer* profile wants it. // // This is because, by default the inner profile will be set to `Release` even when the outer @@ -203,15 +240,9 @@ fn compile_wasm( // development activities. let (compact_blob_path, compact_compressed_blob_path) = if build_config.outer_build_profile.wants_compact() { - let compact_blob_path = compact_wasm( - &project, - &build_config.blob_build_profile, - project_cargo_toml, - &bloaty_blob_out_name, - ); - let compact_compressed_blob_path = compact_blob_path - .as_ref() - .and_then(|p| try_compress_blob(&p.0, &bloaty_blob_out_name)); + let compact_blob_path = compact_wasm(&project, blob_name, &bloaty_blob_binary); + let compact_compressed_blob_path = + compact_blob_path.as_ref().and_then(|p| try_compress_blob(&p.0, blob_name)); (compact_blob_path, compact_compressed_blob_path) } else { (None, None) @@ -221,15 +252,12 @@ fn compile_wasm( ensure_runtime_version_wasm_section_exists(bloaty_blob_binary.bloaty_path()); } - compact_blob_path - .as_ref() - .map(|wasm_binary| copy_blob_to_target_directory(project_cargo_toml, wasm_binary)); + let final_blob_binary = compact_compressed_blob_path.or(compact_blob_path); - compact_compressed_blob_path.as_ref().map(|wasm_binary_compressed| { - copy_blob_to_target_directory(project_cargo_toml, wasm_binary_compressed) - }); + final_blob_binary + .as_ref() + .map(|binary| copy_blob_to_target_directory(wasm_project_cargo_toml, binary)); - let final_blob_binary = compact_compressed_blob_path.or(compact_blob_path); (final_blob_binary, bloaty_blob_binary) } @@ -347,12 +375,25 @@ fn get_crate_name(cargo_manifest: &Path) -> String { .expect("Package name exists; qed") } +/// Extract the `lib.name` from the given `Cargo.toml`. +fn get_lib_name(cargo_manifest: &Path) -> Option { + let cargo_toml: Table = toml::from_str( + &fs::read_to_string(cargo_manifest).expect("File exists as checked before; qed"), + ) + .expect("Cargo manifest is a valid toml file; qed"); + + let lib = cargo_toml.get("lib").and_then(|t| t.as_table())?; + + lib.get("name").and_then(|p| p.as_str()).map(ToOwned::to_owned) +} + /// Returns the name for the blob binary. fn get_blob_name(target: RuntimeTarget, cargo_manifest: &Path) -> String { - let crate_name = get_crate_name(cargo_manifest); match target { - RuntimeTarget::Wasm => crate_name.replace('-', "_"), - RuntimeTarget::Riscv => crate_name, + RuntimeTarget::Wasm => get_lib_name(cargo_manifest) + .expect("The wasm project should have a `lib.name`; qed") + .replace('-', "_"), + RuntimeTarget::Riscv => get_crate_name(cargo_manifest), } } @@ -379,7 +420,6 @@ fn create_project_cargo_toml( workspace_root_path: &Path, crate_name: &str, crate_path: &Path, - wasm_binary: &str, enabled_features: impl Iterator, ) { let mut workspace_toml: Table = toml::from_str( @@ -443,7 +483,7 @@ fn create_project_cargo_toml( if target == RuntimeTarget::Wasm { let mut lib = Table::new(); - lib.insert("name".into(), wasm_binary.into()); + lib.insert("name".into(), crate_name.replace("-", "_").into()); lib.insert("crate-type".into(), vec!["cdylib".to_string()].into()); wasm_workspace_toml.insert("lib".into(), lib.into()); } @@ -588,7 +628,6 @@ fn create_project( ) -> PathBuf { let crate_name = get_crate_name(project_cargo_toml); let crate_path = project_cargo_toml.parent().expect("Parent path exists; qed"); - let wasm_binary = get_blob_name(target, project_cargo_toml); let wasm_project_folder = wasm_workspace.join(&crate_name); fs::create_dir_all(wasm_project_folder.join("src")) @@ -610,7 +649,6 @@ fn create_project( workspace_root_path, &crate_name, crate_path, - &wasm_binary, enabled_features.into_iter(), ); @@ -775,12 +813,15 @@ fn offline_build() -> bool { } /// Build the project and create the bloaty runtime blob. +/// +/// Returns the path to the generated bloaty runtime blob. fn build_bloaty_blob( target: RuntimeTarget, blob_build_profile: &Profile, project: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, + #[cfg(feature = "metadata-hash")] metadata_hash: Option<[u8; 32]>, ) -> PathBuf { let manifest_path = project.join("Cargo.toml"); let mut build_cmd = cargo_cmd.command(); @@ -820,6 +861,11 @@ fn build_bloaty_blob( // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); + #[cfg(feature = "metadata-hash")] + if let Some(hash) = metadata_hash { + build_cmd.env("RUNTIME_METADATA_HASH", array_bytes::bytes2hex("0x", &hash)); + } + if super::color_output_enabled() { build_cmd.arg("--color=always"); } @@ -908,23 +954,16 @@ fn build_bloaty_blob( fn compact_wasm( project: &Path, - inner_profile: &Profile, - cargo_manifest: &Path, - out_name: &str, + blob_name: &str, + bloaty_binary: &WasmBinaryBloaty, ) -> Option { - let default_out_name = get_blob_name(RuntimeTarget::Wasm, cargo_manifest); - let in_path = project - .join("target/wasm32-unknown-unknown") - .join(inner_profile.directory()) - .join(format!("{}.wasm", default_out_name)); - - let wasm_compact_path = project.join(format!("{}.compact.wasm", out_name)); + let wasm_compact_path = project.join(format!("{blob_name}.compact.wasm")); let start = std::time::Instant::now(); wasm_opt::OptimizationOptions::new_opt_level_0() .mvp_features_only() .debug_info(true) .add_pass(wasm_opt::Pass::StripDwarf) - .run(&in_path, &wasm_compact_path) + .run(bloaty_binary.bloaty_path(), &wasm_compact_path) .expect("Failed to compact generated WASM binary."); println!( "{} {}", @@ -934,22 +973,6 @@ fn compact_wasm( Some(WasmBinary(wasm_compact_path)) } -fn copy_bloaty_blob( - project: &Path, - inner_profile: &Profile, - in_name: &str, - out_name: &str, -) -> WasmBinaryBloaty { - let in_path = project - .join("target/wasm32-unknown-unknown") - .join(inner_profile.directory()) - .join(format!("{}.wasm", in_name)); - - let bloaty_path = project.join(format!("{}.wasm", out_name)); - fs::copy(in_path, &bloaty_path).expect("Copying the bloaty file to the project dir."); - WasmBinaryBloaty(bloaty_path) -} - fn try_compress_blob(compact_blob_path: &Path, out_name: &str) -> Option { use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT; diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index 0d985796a11e..b7e527302884 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optional = true } +docify = "0.2.8" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ @@ -35,6 +36,7 @@ pallet-parachain-template = { path = "../pallets/template", default-features = f # Substrate / FRAME frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-executive = { path = "../../../substrate/frame/executive", default-features = false } +frame-metadata-hash-extension = { path = "../../../substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } frame-system-benchmarking = { path = "../../../substrate/frame/system/benchmarking", default-features = false, optional = true } @@ -103,6 +105,7 @@ std = [ "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-benchmarking?/std", "frame-system-rpc-runtime-api/std", @@ -194,3 +197,16 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] + +# Enable the metadata hash generation. +# +# This is hidden behind a feature because it increases the compile time. +# The wasm binary needs to be compiled twice, once to fetch the metadata, +# generate the metadata hash and then a second time with the +# `RUNTIME_METADATA_HASH` environment variable set for the `CheckMetadataHash` +# extension. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + +# A convenience feature for enabling things when doing a build +# for an on-chain release. +on-chain-release-build = ["metadata-hash"] diff --git a/templates/parachain/runtime/build.rs b/templates/parachain/runtime/build.rs index bb05afe02b1f..4f33752ca6b2 100644 --- a/templates/parachain/runtime/build.rs +++ b/templates/parachain/runtime/build.rs @@ -1,4 +1,12 @@ -#[cfg(feature = "std")] +#[cfg(all(feature = "std", feature = "metadata-hash"))] +#[docify::export(template_enable_metadata_hash)] +fn main() { + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("UNIT", 12) + .build(); +} + +#[cfg(all(feature = "std", not(feature = "metadata-hash")))] fn main() { substrate_wasm_builder::WasmBuilder::build_using_defaults(); } diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index e2da9309ecc7..3e0ab1508902 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -85,6 +85,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, + frame_metadata_hash_extension::CheckMetadataHash, ); /// Unchecked extrinsic type as expected by this runtime.