diff --git a/.github/scripts/build-try-runtime.sh b/.github/scripts/build-try-runtime.sh new file mode 100755 index 0000000000000..f7a78c181765e --- /dev/null +++ b/.github/scripts/build-try-runtime.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Build try-runtime-cli from source with polkadot-sdk dependencies patched to use the local checkout. +# +# Usage: build-try-runtime.sh +# Example: build-try-runtime.sh /workspace v0.10.1 ./try-runtime + +set -eu -o pipefail + +SDK_PATH="$(realpath "$1")" +VERSION="${2}" +OUTPUT="$(realpath -m "$3")" + +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT + +echo "::group::Clone try-runtime-cli ${VERSION}" +git clone --depth 1 --branch "${VERSION}" \ + https://github.com/paritytech/try-runtime-cli.git "$WORK_DIR/try-runtime-cli" +# try-runtime-cli ships a rust-toolchain.toml that pins `channel = "stable"`. +# Building try-runtime-cli against polkadot-sdk only needs whatever stable is already +# installed in the image, so drop the pin and use the preinstalled toolchain as-is. +rm -f "$WORK_DIR/try-runtime-cli/rust-toolchain.toml" \ + "$WORK_DIR/try-runtime-cli/rust-toolchain" +echo "::endgroup::" + +echo "::group::Generate Cargo patches" +# Parse the polkadot-sdk workspace Cargo.toml to extract all crates with path = "..." +# and generate [patch] entries so try-runtime-cli uses our local code. +# Uses the actual package name from each crate's Cargo.toml as the patch key, +# since some workspace aliases differ from the published crate name +# (e.g. workspace key "xcm" -> package name "staging-xcm"). +python3 - "$SDK_PATH" "$WORK_DIR/try-runtime-cli/Cargo.toml" <<'PYEOF' +import re, sys, os + +sdk_path = sys.argv[1] +target_cargo_toml = sys.argv[2] + +with open(f"{sdk_path}/Cargo.toml") as f: + content = f.read() + +pattern = r'^(\S+)\s*=\s*\{[^}]*path\s*=\s*"([^"]+)"[^}]*\}' +matches = re.findall(pattern, content, re.MULTILINE) + +# Extract `name = "..."` from the [package] section of a crate's Cargo.toml. +# Avoids the tomllib stdlib module (Python 3.11+) since older CI images may +# ship an earlier interpreter. Cargo does not allow inheriting `name` from +# the workspace, so the field is always a literal string. +pkg_section_re = re.compile(r'^\[package\]\s*\n(.*?)(?=^\[|\Z)', re.MULTILINE | re.DOTALL) +name_re = re.compile(r'^name\s*=\s*"([^"]+)"', re.MULTILINE) + +def read_pkg_name(path): + try: + with open(path) as f: + text = f.read() + except OSError: + return None + section = pkg_section_re.search(text) + if not section: + return None + m = name_re.search(section.group(1)) + return m.group(1) if m else None + +patches = {} # pkg_name -> abs_path (deduplicated by actual package name) +for _, rel_path in matches: + crate_toml = os.path.join(sdk_path, rel_path, "Cargo.toml") + if not os.path.isfile(crate_toml): + continue + pkg_name = read_pkg_name(crate_toml) + if not pkg_name: + continue + patches[pkg_name] = f"{sdk_path}/{rel_path}" + +patch_lines = ['\n[patch."https://github.com/paritytech/polkadot-sdk"]'] +for name in sorted(patches): + patch_lines.append(f'{name} = {{ path = "{patches[name]}" }}') + +with open(target_cargo_toml, "a") as f: + f.write("\n".join(patch_lines) + "\n") + +print(f"Added {len(patches)} patch entries") +PYEOF +echo "::endgroup::" + +echo "::group::Apply source compatibility patches" +# BackendRuntimeCode::new now takes a TryPendingCode argument. +# try-runtime doesn't use pending code, so we pass TryPendingCode::No. +sed -i 's/BackendRuntimeCode::new(\([^)]*\))/BackendRuntimeCode::new(\1, sp_state_machine::backend::TryPendingCode::No)/g' \ + "$WORK_DIR/try-runtime-cli/core/src/common/state.rs" +echo "Patched BackendRuntimeCode::new calls" +echo "::endgroup::" + +echo "::group::Build try-runtime" +cd "$WORK_DIR/try-runtime-cli" +# Seed the lockfile from polkadot-sdk so yanked-but-already-locked registry +# versions in polkadot-sdk's transitive graph stay resolvable. +cp "$SDK_PATH/Cargo.lock" Cargo.lock +cargo build --release -p try-runtime-cli +cp target/release/try-runtime "$OUTPUT" +echo "::endgroup::" + +echo "Built try-runtime at $OUTPUT" +"$OUTPUT" --version diff --git a/.github/workflows/check-runtime-compatibility.yml b/.github/workflows/check-runtime-compatibility.yml index 5b48cbb21f3f1..ae01ca90b9b10 100644 --- a/.github/workflows/check-runtime-compatibility.yml +++ b/.github/workflows/check-runtime-compatibility.yml @@ -24,7 +24,11 @@ jobs: check-runtime-compatibility: runs-on: ${{ needs.preflight.outputs.RUNNER }} - if: ${{ needs.preflight.outputs.changes_rust }} + # Tests based on @acala-network/chopsticks-core are temporarily disabled + # as Chopsticks doesn't have an idea about new host functions exposed + # by RFC-145. Enable back when Chopsticks catches up. + # if: ${{ needs.preflight.outputs.changes_rust }} + if: false timeout-minutes: 30 needs: [preflight] container: diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 34a02bc264746..b4af4b59ed6a4 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -155,12 +155,12 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Download CLI + - name: Build try-runtime-cli from source run: | - curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.10.1/try-runtime-x86_64-unknown-linux-musl -o try-runtime - chmod +x ./try-runtime - echo "Using try-runtime-cli version:" - ./try-runtime --version + echo "Building try-runtime-cli from source with local polkadot-sdk dependencies" + .github/scripts/build-try-runtime.sh "$GITHUB_WORKSPACE" v0.10.1 ./try-runtime + env: + CARGO_INCREMENTAL: 0 - name: Restore snapshot from cache uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 diff --git a/Cargo.lock b/Cargo.lock index 9ad9e595f77d6..83e35e5f825e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7618,6 +7618,7 @@ dependencies = [ "cumulus-pallet-parachain-system", "parity-scale-codec", "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-trie 29.0.0", @@ -15964,6 +15965,12 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "picoalloc" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1912f9b1d3aea43590e3986afdcf4ed1d9662edae24744a095738157d65b6a" + [[package]] name = "picosimd" version = "0.9.2" @@ -24507,6 +24514,7 @@ dependencies = [ "sp-api-proc-macro 15.0.0", "sp-core 28.0.0", "sp-externalities 0.25.0", + "sp-io 30.0.0", "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", @@ -24878,6 +24886,7 @@ dependencies = [ "blake2 0.10.6", "bounded-collections 0.3.2", "bs58", + "byte-slice-cast", "criterion", "dyn-clone", "ed25519-zebra", @@ -25191,12 +25200,15 @@ dependencies = [ name = "sp-io" version = "30.0.0" dependencies = [ + "anyhow", + "byte-slice-cast", "bytes", "docify", "ed25519-dalek", "libsecp256k1", "log", "parity-scale-codec", + "picoalloc", "polkavm-derive 0.33.0", "rustversion", "secp256k1 0.28.2", @@ -25208,6 +25220,7 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-trie 29.0.0", + "strum 0.26.3", "tracing", "tracing-core", ] @@ -25468,6 +25481,7 @@ dependencies = [ name = "sp-runtime-interface" version = "24.0.0" dependencies = [ + "byte-slice-cast", "bytes", "impl-trait-for-tuples", "parity-scale-codec", @@ -25562,6 +25576,7 @@ dependencies = [ name = "sp-runtime-interface-test-wasm-deprecated" version = "2.0.0" dependencies = [ + "bytes", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime-interface 24.0.0", diff --git a/Cargo.toml b/Cargo.toml index 81832c623461b..35e068f1ff410 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1109,6 +1109,7 @@ penpal-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/ penpal-runtime = { path = "cumulus/parachains/runtimes/testing/penpal" } people-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend" } people-westend-runtime = { path = "cumulus/parachains/runtimes/people/people-westend" } +picoalloc = { version = "5.2.0" } pin-project = { version = "1.1.3" } polkadot-approval-distribution = { path = "polkadot/node/network/approval-distribution", default-features = false } polkadot-availability-bitfield-distribution = { path = "polkadot/node/network/bitfield-distribution", default-features = false } diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs index ba332be58acfd..2057c8765319d 100644 --- a/cumulus/pallets/dmp-queue/src/tests.rs +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -101,19 +101,16 @@ fn migration_works() { ); }); ext.commit_all().unwrap(); - // Then it cleans up the remaining storage items: + // Then it cleans up the remaining storage items. + // With cursor-based iteration, the cleanup now completes in 2 blocks + // (2 keys per block, 4 total remaining keys). ext.execute_with(|| { run_to_block(21); assert_only_event(Event::CleanedSome { keys_removed: 2 }); }); ext.commit_all().unwrap(); ext.execute_with(|| { - run_to_block(22); - assert_only_event(Event::CleanedSome { keys_removed: 2 }); - }); - ext.commit_all().unwrap(); - ext.execute_with(|| { - run_to_block(24); + run_to_block(23); assert_eq!( System::events().into_iter().map(|e| e.event).collect::>(), vec![ @@ -141,7 +138,7 @@ fn migration_works() { assert_eq!(MigrationStatus::::get(), MigrationState::Completed); assert!(System::events().is_empty()); // ... besides the block number - System::set_block_number(24); + System::set_block_number(23); } }); } diff --git a/cumulus/pallets/parachain-system/proc-macro/src/lib.rs b/cumulus/pallets/parachain-system/proc-macro/src/lib.rs index d7eda6a022a31..de8f75ba0dfad 100644 --- a/cumulus/pallets/parachain-system/proc-macro/src/lib.rs +++ b/cumulus/pallets/parachain-system/proc-macro/src/lib.rs @@ -111,14 +111,10 @@ pub fn register_validate_block(input: proc_macro::TokenStream) -> proc_macro::To target_arch = "riscv64", #crate_::validate_block::sp_api::__private::polkavm_export(abi = #crate_::validate_block::sp_api::__private::polkavm_abi) )] - unsafe fn validate_block(arguments: *mut u8, arguments_len: usize) -> u64 { - // We convert the `arguments` into a boxed slice and then into `Bytes`. - let args = #crate_::validate_block::Box::from_raw( - #crate_::validate_block::slice::from_raw_parts_mut( - arguments, - arguments_len, - ) - ); + unsafe fn validate_block(arguments_len: usize) -> u64 { + let mut args = #crate_::validate_block::vec![0u8; arguments_len]; + #crate_::validate_block::sp_io::input::read(&mut args[..]); + let args = #crate_::validate_block::bytes::Bytes::from(args); // Then we decode from these bytes the `MemoryOptimizedValidationParams`. diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index 788e4cd26960d..66ca58f9bd746 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -31,9 +31,9 @@ use frame_support::{ BoundedVec, }; use polkadot_parachain_primitives::primitives::{HeadData, ValidationResult}; -use sp_core::storage::{well_known_keys, ChildInfo, StateVersion}; +use sp_core::storage::{well_known_keys, ChildInfo}; use sp_externalities::{set_and_run_with_externalities, Externalities}; -use sp_io::{hashing::blake2_128, KillStorageResult}; +use sp_io::{hashing::blake2_128, StorageIterations}; use sp_runtime::traits::{ Block as BlockT, ExtrinsicCall, Hash as HashT, HashingFor, Header as HeaderT, LazyBlock, }; @@ -95,7 +95,6 @@ where // Replace storage calls with our own implementations sp_io::storage::host_read.replace_implementation(host_storage_read), sp_io::storage::host_set.replace_implementation(host_storage_set), - sp_io::storage::host_get.replace_implementation(host_storage_get), sp_io::storage::host_exists.replace_implementation(host_storage_exists), sp_io::storage::host_clear.replace_implementation(host_storage_clear), sp_io::storage::host_root.replace_implementation(host_storage_root), @@ -108,8 +107,6 @@ where .replace_implementation(host_storage_rollback_transaction), sp_io::storage::host_commit_transaction .replace_implementation(host_storage_commit_transaction), - sp_io::default_child_storage::host_get - .replace_implementation(host_default_child_storage_get), sp_io::default_child_storage::host_read .replace_implementation(host_default_child_storage_read), sp_io::default_child_storage::host_set @@ -170,6 +167,7 @@ where let mut head_data = None; let mut new_validation_code = None; let num_blocks = blocks.len(); + let state_version = ::Version::get().state_version(); // Create the db let mut db = match proof.to_memory_db(Some(parent_header.state_root())) { @@ -209,6 +207,7 @@ where &backend, &mut Default::default(), &mut Default::default(), + state_version, || { E::verify_and_remove_seal(&mut block); }, @@ -222,6 +221,7 @@ where // mismatches in later blocks. &mut execute_recorder, &mut overlay, + state_version, || { E::execute_verified_block(block); }, @@ -244,6 +244,7 @@ where // We are only reading here, but need to know what the old block has written. Thus, we // are passing here the overlay. &mut overlay, + state_version, || { // Ensure the validation data is correct. validate_validation_data( @@ -298,10 +299,7 @@ where if block_index + 1 != num_blocks { let mut changes = overlay - .drain_storage_changes( - &backend, - ::Version::get().state_version(), - ) + .drain_storage_changes(&backend, state_version) .expect("Failed to get drain storage changes from the overlay."); drop(backend); @@ -492,21 +490,29 @@ fn run_with_externalities_and_recorder R>( backend: &impl sp_state_machine::Backend>, recorder: &mut SizeOnlyRecorderProvider>, overlay: &mut OverlayedChanges>, + state_version: sp_core::storage::StateVersion, execute: F, ) -> R { - let mut ext = Ext::::new(overlay, backend); + let mut ext = Ext::::new(overlay, backend).with_state_version(state_version); recorder::using(recorder, || set_and_run_with_externalities(&mut ext, || execute())) } -fn host_storage_read(key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option { +fn host_storage_read( + key: &[u8], + value_out: &mut [u8], + value_offset: u32, + allow_partial: u32, +) -> Option { match with_externalities(|ext| ext.storage(key)) { Some(value) => { let value_offset = value_offset as usize; let data = &value[value_offset.min(value.len())..]; - let written = core::cmp::min(data.len(), value_out.len()); - value_out[..written].copy_from_slice(&data[..written]); - Some(value.len() as u32) + let out_len = core::cmp::min(data.len(), value_out.len()); + if value_out.len() >= data.len() || allow_partial != 0 { + value_out[..out_len].copy_from_slice(&data[..out_len]); + } + Some(data.len() as u32) }, None => None, } @@ -516,10 +522,6 @@ fn host_storage_set(key: &[u8], value: &[u8]) { with_externalities(|ext| ext.place_storage(key.to_vec(), Some(value.to_vec()))) } -fn host_storage_get(key: &[u8]) -> Option { - with_externalities(|ext| ext.storage(key).map(|value| value.into())) -} - fn host_storage_exists(key: &[u8]) -> bool { with_externalities(|ext| ext.exists_storage(key)) } @@ -532,20 +534,51 @@ fn host_storage_proof_size() -> u64 { recorder::with(|rec| rec.estimate_encoded_size()).expect("Recorder is always set; qed") as _ } -fn host_storage_root(version: StateVersion) -> Vec { - with_externalities(|ext| ext.storage_root(version)) +fn host_storage_root(out: &mut [u8]) { + with_externalities(|ext| { + let root = ext.storage_root(); + let encoded = root.encode(); + let write_len = encoded.len().min(out.len()); + out[..write_len].copy_from_slice(&encoded[..write_len]); + }) } -fn host_storage_clear_prefix(prefix: &[u8], limit: Option) -> KillStorageResult { - with_externalities(|ext| ext.clear_prefix(prefix, limit, None).into()) +fn host_storage_clear_prefix( + prefix: &[u8], + maybe_limit: Option, + maybe_cursor_in: Option<&[u8]>, + maybe_cursor_out: &mut [u8], + counters: &mut StorageIterations, +) -> u32 { + with_externalities(|ext| { + let removal_results = + ext.clear_prefix(prefix, maybe_limit, maybe_cursor_in.as_ref().map(|c| &c[..])); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + let write_len = cursor_out_len.min(maybe_cursor_out.len()); + maybe_cursor_out[..write_len].copy_from_slice(&cursor_out[..write_len]); + } + counters.backend = removal_results.backend; + counters.unique = removal_results.unique; + counters.loops = removal_results.loops; + cursor_out_len as u32 + }) } fn host_storage_append(key: &[u8], value: Vec) { with_externalities(|ext| ext.storage_append(key.to_vec(), value)) } -fn host_storage_next_key(key: &[u8]) -> Option> { - with_externalities(|ext| ext.next_storage_key(key)) +fn host_storage_next_key(key_in: &[u8], key_out: &mut [u8]) -> u32 { + with_externalities(|ext| { + let next_key = ext.next_storage_key(key_in); + let next_key_len = next_key.as_ref().map(|k| k.len()).unwrap_or(0); + if let Some(next_key) = next_key { + let write_len = next_key.len().min(key_out.len()); + key_out[..write_len].copy_from_slice(&next_key[..write_len]); + } + next_key_len as u32 + }) } fn host_storage_start_transaction() { @@ -562,25 +595,23 @@ fn host_storage_commit_transaction() { .expect("No open transaction that can be committed."); } -fn host_default_child_storage_get(storage_key: &[u8], key: &[u8]) -> Option> { - let child_info = ChildInfo::new_default(storage_key); - with_externalities(|ext| ext.child_storage(&child_info, key)) -} - fn host_default_child_storage_read( storage_key: &[u8], key: &[u8], value_out: &mut [u8], value_offset: u32, + allow_partial: u32, ) -> Option { let child_info = ChildInfo::new_default(storage_key); match with_externalities(|ext| ext.child_storage(&child_info, key)) { Some(value) => { let value_offset = value_offset as usize; let data = &value[value_offset.min(value.len())..]; - let written = core::cmp::min(data.len(), value_out.len()); - value_out[..written].copy_from_slice(&data[..written]); - Some(value.len() as u32) + let out_len = core::cmp::min(data.len(), value_out.len()); + if value_out.len() >= data.len() || allow_partial != 0 { + value_out[..out_len].copy_from_slice(&data[..out_len]); + } + Some(data.len() as u32) }, None => None, } @@ -600,10 +631,24 @@ fn host_default_child_storage_clear(storage_key: &[u8], key: &[u8]) { fn host_default_child_storage_storage_kill( storage_key: &[u8], - limit: Option, -) -> KillStorageResult { + maybe_limit: Option, + maybe_cursor_in: Option<&[u8]>, + maybe_cursor_out: &mut [u8], + counters: &mut StorageIterations, +) -> u32 { let child_info = ChildInfo::new_default(storage_key); - with_externalities(|ext| ext.kill_child_storage(&child_info, limit, None).into()) + with_externalities(|ext| { + let removal_results = ext.kill_child_storage(&child_info, maybe_limit, maybe_cursor_in); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + let write_len = cursor_out_len.min(maybe_cursor_out.len()); + maybe_cursor_out[..write_len].copy_from_slice(&cursor_out[..write_len]); + } + counters.backend = removal_results.backend; + counters.unique = removal_results.unique; + counters.loops = removal_results.loops; + cursor_out_len as u32 + }) } fn host_default_child_storage_exists(storage_key: &[u8], key: &[u8]) -> bool { @@ -614,20 +659,52 @@ fn host_default_child_storage_exists(storage_key: &[u8], key: &[u8]) -> bool { fn host_default_child_storage_clear_prefix( storage_key: &[u8], prefix: &[u8], - limit: Option, -) -> KillStorageResult { + maybe_limit: Option, + maybe_cursor_in: Option<&[u8]>, + maybe_cursor_out: &mut [u8], + counters: &mut StorageIterations, +) -> u32 { let child_info = ChildInfo::new_default(storage_key); - with_externalities(|ext| ext.clear_child_prefix(&child_info, prefix, limit, None).into()) + with_externalities(|ext| { + let removal_results = + ext.clear_child_prefix(&child_info, prefix, maybe_limit, maybe_cursor_in); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + let write_len = cursor_out_len.min(maybe_cursor_out.len()); + maybe_cursor_out[..write_len].copy_from_slice(&cursor_out[..write_len]); + } + counters.backend = removal_results.backend; + counters.unique = removal_results.unique; + counters.loops = removal_results.loops; + cursor_out_len as u32 + }) } -fn host_default_child_storage_root(storage_key: &[u8], version: StateVersion) -> Vec { +fn host_default_child_storage_root(storage_key: &[u8], out: &mut [u8]) { let child_info = ChildInfo::new_default(storage_key); - with_externalities(|ext| ext.child_storage_root(&child_info, version)) + with_externalities(|ext| { + let root = ext.child_storage_root(&child_info); + let encoded = root.encode(); + let write_len = encoded.len().min(out.len()); + out[..write_len].copy_from_slice(&encoded[..write_len]); + }) } -fn host_default_child_storage_next_key(storage_key: &[u8], key: &[u8]) -> Option> { +fn host_default_child_storage_next_key( + storage_key: &[u8], + key_in: &[u8], + key_out: &mut [u8], +) -> u32 { let child_info = ChildInfo::new_default(storage_key); - with_externalities(|ext| ext.next_child_storage_key(&child_info, key)) + with_externalities(|ext| { + let next_key = ext.next_child_storage_key(&child_info, key_in); + let next_key_len = next_key.as_ref().map(|k| k.len()).unwrap_or(0); + if let Some(next_key) = next_key { + let write_len = next_key.len().min(key_out.len()); + key_out[..write_len].copy_from_slice(&next_key[..write_len]); + } + next_key_len as u32 + }) } fn host_offchain_index_set(_key: &[u8], _value: &[u8]) {} diff --git a/cumulus/pallets/parachain-system/src/validate_block/mod.rs b/cumulus/pallets/parachain-system/src/validate_block/mod.rs index 3f42f6bf049be..89ae58ab5a7e4 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/mod.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/mod.rs @@ -37,7 +37,7 @@ pub mod trie_recorder; #[cfg(not(feature = "std"))] #[doc(hidden)] -pub use alloc::{boxed::Box, slice}; +pub use alloc::{boxed::Box, slice, vec}; #[cfg(not(feature = "std"))] #[doc(hidden)] pub use bytes; @@ -47,9 +47,10 @@ pub use codec::decode_from_bytes; #[cfg(not(feature = "std"))] #[doc(hidden)] pub use polkadot_parachain_primitives; +pub use sp_api; #[cfg(not(feature = "std"))] #[doc(hidden)] -pub use sp_api; +pub use sp_io; #[cfg(not(feature = "std"))] #[doc(hidden)] pub use sp_runtime::traits::GetRuntimeBlockType; diff --git a/polkadot/node/core/pvf/common/src/executor_interface.rs b/polkadot/node/core/pvf/common/src/executor_interface.rs index 92c07b9d2d63f..9a5e7cf5db0cf 100644 --- a/polkadot/node/core/pvf/common/src/executor_interface.rs +++ b/polkadot/node/core/pvf/common/src/executor_interface.rs @@ -226,6 +226,7 @@ type HostFunctions = ( sp_io::allocator::HostFunctions, sp_io::logging::HostFunctions, sp_io::trie::HostFunctions, + sp_io::input::HostFunctions, ); /// Host functions with ECC (elliptic curve cryptography) support. @@ -289,11 +290,11 @@ impl sp_externalities::Externalities for ValidationExternalities { panic!("place_child_storage: unsupported feature for parachain validation") } - fn storage_root(&mut self, _: sp_core::storage::StateVersion) -> Vec { + fn storage_root(&mut self) -> Vec { panic!("storage_root: unsupported feature for parachain validation") } - fn child_storage_root(&mut self, _: &ChildInfo, _: sp_core::storage::StateVersion) -> Vec { + fn child_storage_root(&mut self, _: &ChildInfo) -> Vec { panic!("child_storage_root: unsupported feature for parachain validation") } @@ -321,6 +322,14 @@ impl sp_externalities::Externalities for ValidationExternalities { panic!("storage_commit_transaction: unsupported feature for parachain validation") } + fn store_last_cursor(&mut self, _cursor: &[u8]) { + panic!("store_last_cursor: unsupported feature for parachain validation") + } + + fn take_last_cursor(&mut self) -> Option> { + panic!("take_last_cursor: unsupported feature for parachain validation") + } + fn wipe(&mut self) { panic!("wipe: unsupported feature for parachain validation") } diff --git a/polkadot/parachain/test-parachains/adder/src/wasm_validation.rs b/polkadot/parachain/test-parachains/adder/src/wasm_validation.rs index 9c3c77f7350b9..4baa3833d7256 100644 --- a/polkadot/parachain/test-parachains/adder/src/wasm_validation.rs +++ b/polkadot/parachain/test-parachains/adder/src/wasm_validation.rs @@ -17,14 +17,18 @@ //! WASM validation for adder parachain. use crate::{BlockData, HeadData}; -use alloc::vec::Vec; +use alloc::vec; use codec::{Decode, Encode}; use core::panic; -use polkadot_parachain_primitives::primitives::{HeadData as GenericHeadData, ValidationResult}; +use polkadot_parachain_primitives::primitives::{ + HeadData as GenericHeadData, ValidationParams, ValidationResult, +}; #[no_mangle] -pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 { - let params = unsafe { polkadot_parachain_primitives::load_params(params, len) }; +pub extern "C" fn validate_block(arguments_len: usize) -> u64 { + let mut buf = vec![0u8; arguments_len]; + sp_io::input::read(&mut buf[..]); + let params = ValidationParams::decode(&mut &buf[..]).expect("Invalid input data"); let parent_head = HeadData::decode(&mut ¶ms.parent_head.0[..]).expect("invalid parent head format."); diff --git a/polkadot/parachain/test-parachains/halt/src/lib.rs b/polkadot/parachain/test-parachains/halt/src/lib.rs index 4b8197a423369..a9c11e42c5141 100644 --- a/polkadot/parachain/test-parachains/halt/src/lib.rs +++ b/polkadot/parachain/test-parachains/halt/src/lib.rs @@ -47,6 +47,6 @@ pub fn oom(_: core::alloc::Layout) -> ! { #[cfg(not(feature = "std"))] #[no_mangle] -pub extern "C" fn validate_block(_params: *const u8, _len: usize) -> u64 { +pub extern "C" fn validate_block(_arguments_len: usize) -> u64 { loop {} } diff --git a/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs b/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs index 42917484cfdc2..91d662364255e 100644 --- a/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs +++ b/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs @@ -17,12 +17,17 @@ //! WASM validation for the `Undying` parachain. use crate::{BlockData, HeadData}; +use alloc::vec; use codec::{Decode, Encode}; -use polkadot_parachain_primitives::primitives::{HeadData as GenericHeadData, ValidationResult}; +use polkadot_parachain_primitives::primitives::{ + HeadData as GenericHeadData, ValidationParams, ValidationResult, +}; #[no_mangle] -pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 { - let params = unsafe { polkadot_parachain_primitives::load_params(params, len) }; +pub extern "C" fn validate_block(arguments_len: usize) -> u64 { + let mut buf = vec![0u8; arguments_len]; + sp_io::input::read(&mut buf[..]); + let params = ValidationParams::decode(&mut &buf[..]).expect("Invalid input data"); let parent_head = HeadData::decode(&mut ¶ms.parent_head.0[..]).expect("invalid parent head format."); diff --git a/polkadot/primitives/src/v9/mod.rs b/polkadot/primitives/src/v9/mod.rs index 215d8ea3ca00d..1f0ef19036c59 100644 --- a/polkadot/primitives/src/v9/mod.rs +++ b/polkadot/primitives/src/v9/mod.rs @@ -1657,7 +1657,7 @@ impl WellKnownKey { /// Gets the value or `None` if it does not exist or decoding failed. pub fn get(&self) -> Option { sp_io::storage::get(&self.key) - .and_then(|raw| codec::DecodeAll::decode_all(&mut raw.as_ref()).ok()) + .and_then(|raw| codec::DecodeAll::decode_all(&mut raw.as_slice()).ok()) } } diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 3410802d674d5..699ee9a4e9d8b 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -704,9 +704,8 @@ impl Pallet { who.using_encoded(|b| child::kill(&Self::id_from_index(index), b)); } - pub fn crowdloan_kill(index: FundIndex) -> child::KillStorageResult { - #[allow(deprecated)] - child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get())) + pub fn crowdloan_kill(index: FundIndex) -> child::MultiRemovalResults { + child::clear_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get()), None) } pub fn contribution_iterator( diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v13.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v13.rs index a4189055e2a83..04bd27d8e0975 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v13.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v13.rs @@ -164,9 +164,8 @@ pub fn availability_cores() -> Vec( ) -> (BlockNumberFor, ::Hash) { use codec::Decode as _; - let state_version = frame_system::Pallet::::runtime_version().state_version(); let relay_parent_number = frame_system::Pallet::::block_number(); - let relay_parent_storage_root = T::Hash::decode(&mut &sp_io::storage::root(state_version)[..]) + let relay_parent_storage_root = T::Hash::decode(&mut &sp_io::storage::root()[..]) .expect("storage root must decode to the Hash type; qed"); (relay_parent_number, relay_parent_storage_root) } diff --git a/polkadot/zombienet-sdk-tests/build.rs b/polkadot/zombienet-sdk-tests/build.rs index c66723e19df47..b2e43923f7078 100644 --- a/polkadot/zombienet-sdk-tests/build.rs +++ b/polkadot/zombienet-sdk-tests/build.rs @@ -86,6 +86,7 @@ type HostFunctions = ( sp_io::logging::HostFunctions, sp_io::storage::HostFunctions, sp_io::hashing::HostFunctions, + sp_io::input::HostFunctions, ); fn generate_metadata_file(wasm_path: &Path, output_path: &Path) { diff --git a/prdoc/pr_8641.prdoc b/prdoc/pr_8641.prdoc new file mode 100644 index 0000000000000..9818b4e6b6de3 --- /dev/null +++ b/prdoc/pr_8641.prdoc @@ -0,0 +1,96 @@ +title: RFC-145 (former RFC-4) host functions implementation +doc: +- audience: Runtime Dev + description: |- + This implements the host functions introduced by [RFC-145](https://github.com/polkadot-fellows/RFCs/pull/145). +crates: +- name: sp-io + bump: major +- name: sp-runtime-interface + bump: minor +- name: sp-externalities + bump: major +- name: polkadot-node-core-pvf-common + bump: patch +- name: sc-executor-polkavm + bump: patch +- name: sc-executor-wasmtime + bump: patch +- name: sp-wasm-interface + bump: minor +- name: sp-core + bump: minor +- name: sp-state-machine + bump: minor +- name: frame-support + bump: major +- name: sp-runtime + bump: patch +- name: sp-runtime-interface-proc-macro + bump: patch +- name: substrate-wasm-builder + bump: patch +- name: sc-executor + bump: patch +- name: sc-executor-common + bump: minor +- name: sp-virtualization + bump: patch +- name: sp-api + bump: minor +- name: sp-api-proc-macro + bump: patch +- name: frame-executive + bump: patch +- name: frame-system + bump: patch +- name: frame-benchmarking + bump: minor +- name: frame-support-procedural + bump: patch +- name: pallet-migrations + bump: patch +- name: polkadot-primitives + bump: patch +- name: pallet-recovery + bump: patch +- name: pallet-revive + bump: patch +- name: pallet-salary + bump: patch +- name: polkadot-runtime-parachains + bump: patch +- name: polkadot-runtime-common + bump: patch +- name: cumulus-pallet-parachain-system + bump: minor +- name: cumulus-pallet-parachain-system-proc-macro + bump: patch +- name: frame-storage-access-test-runtime + bump: minor +- name: pallet-democracy + bump: patch +- name: cumulus-pallet-dmp-queue + bump: patch +- name: pallet-contracts + bump: patch +- name: pallet-core-fellowship + bump: patch +- name: pallet-beefy-mmr + bump: patch +- name: pallet-node-authorization + bump: patch +- name: pallet-collective + bump: patch +- name: pallet-staking + bump: patch +- name: pallet-grandpa + bump: patch +- name: frame-remote-externalities + bump: patch +- name: pallet-bounties + bump: patch +- name: pallet-elections-phragmen + bump: patch +- name: pallet-state-trie-migration + bump: patch diff --git a/substrate/client/executor/common/src/lib.rs b/substrate/client/executor/common/src/lib.rs index ff9fab2c8889f..4eba92811b14d 100644 --- a/substrate/client/executor/common/src/lib.rs +++ b/substrate/client/executor/common/src/lib.rs @@ -21,6 +21,8 @@ #![warn(missing_docs)] #![deny(unused_crate_dependencies)] +use std::collections::HashMap; + pub mod error; pub mod runtime_blob; pub mod util; @@ -29,3 +31,116 @@ pub mod wasm_runtime; pub(crate) fn is_polkavm_enabled() -> bool { std::env::var_os("SUBSTRATE_ENABLE_POLKAVM").map_or(false, |value| value == "1") } + +// Defines the divide between host-allocating host functions and runtime-allocating host functions. +// Each tuple consists of the function name and the version where the runtime-side allocation +// was first introduced. For obsolete host-allocating function the version specified must be the +// last version defined plus one. Importing functions from different sides of the divide into the +// same runtime is considered an error and shall result in a runtime construction failure. +static RUNTIME_ALLOC_IMPORTS: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { + [ + ("storage_get", 2), + ("storage_read", 2), + ("storage_clear_prefix", 4), + ("storage_root", 3), + ("storage_changes_root", 2), + ("storage_next_key", 2), + ("default_child_storage_get", 2), + ("default_child_storage_read", 2), + ("default_child_storage_storage_kill", 5), + ("default_child_storage_clear_prefix", 4), + ("default_child_storage_root", 3), + ("default_child_storage_next_key", 2), + ("trie_blake2_256_root", 3), + ("trie_blake2_256_ordered_root", 3), + ("trie_keccak_256_root", 3), + ("trie_keccak_256_ordered_root", 3), + ("misc_runtime_version", 2), + ("misc_last_cursor", 1), + ("crypto_ed25519_public_keys", 2), + ("crypto_ed25519_num_public_keys", 1), + ("crypto_ed25519_public_key", 1), + ("crypto_ed25519_generate", 2), + ("crypto_ed25519_sign", 2), + ("crypto_sr25519_public_keys", 2), + ("crypto_sr25519_num_public_keys", 1), + ("crypto_sr25519_public_key", 1), + ("crypto_sr25519_generate", 2), + ("crypto_sr25519_sign", 2), + ("crypto_ecdsa_public_keys", 2), + ("crypto_ecdsa_num_public_keys", 1), + ("crypto_ecdsa_public_key", 1), + ("crypto_ecdsa_generate", 2), + ("crypto_ecdsa_sign", 2), + ("crypto_ecdsa_sign_prehashed", 2), + ("crypto_secp256k1_ecdsa_recover", 3), + ("crypto_secp256k1_ecdsa_recover_compressed", 3), + ("hashing_keccak_256", 2), + ("hashing_keccak_512", 2), + ("hashing_sha2_256", 2), + ("hashing_blake2_128", 2), + ("hashing_blake2_256", 2), + ("hashing_twox_256", 2), + ("hashing_twox_128", 2), + ("hashing_twox_64", 2), + ("offchain_submit_transaction", 2), + ("offchain_network_state", 2), + ("offchain_network_peer_id", 1), + ("offchain_random_seed", 2), + ("offchain_local_storage_get", 2), + ("offchain_local_storage_read", 1), + ("offchain_http_request_start", 2), + ("offchain_http_request_add_header", 2), + ("offchain_http_request_write_body", 2), + ("offchain_http_response_wait", 2), + ("offchain_http_response_headers", 2), + ("offchain_http_response_header_name", 1), + ("offchain_http_response_header_value", 1), + ("offchain_http_response_read_body", 2), + ("allocator_malloc", 2), + ("allocator_free", 2), + ("input_read", 1), + ] + .iter() + .cloned() + .collect() + }); + +/// Checks if the runtime only imports functions that allocate either on the host or the runtime +/// side, but not both. +pub struct RuntimeAllocSanityChecker(u8); + +impl RuntimeAllocSanityChecker { + /// Creates a new checker. + pub fn new() -> Self { + Self(0) + } + + /// Checks a function import. + pub fn check(&mut self, name: &str) { + let parts = name.split('_').collect::>(); + if parts.len() < 3 { + return; + } + if parts[0] != "ext" { + return; + } + let name = parts[1..parts.len() - 2].join("_"); + if let Some(divide_version) = RUNTIME_ALLOC_IMPORTS.get(name.as_str()) { + if let Ok(imported_version) = parts[parts.len() - 1].parse::() { + if imported_version < *divide_version { + self.0 |= 1; + } else { + self.0 |= 2; + } + } + } + } + + /// Returns true if all the functions checked only allocate on the host side or only on the + /// runtime side, but not both. + pub fn check_result(&self) -> bool { + self.0 < 3 + } +} diff --git a/substrate/client/executor/polkavm/src/lib.rs b/substrate/client/executor/polkavm/src/lib.rs index 44191015d0606..0444adbd8cd60 100644 --- a/substrate/client/executor/polkavm/src/lib.rs +++ b/substrate/client/executor/polkavm/src/lib.rs @@ -179,6 +179,10 @@ impl<'r, 'a> FunctionContext for Context<'r, 'a> { fn register_panic_error_message(&mut self, _message: &str) { unimplemented!("'register_panic_error_message' is never used when running under PolkaVM"); } + + fn take_input_data(&mut self) -> sp_wasm_interface::Result> { + todo!("Implement 'take_input_data' for PolkaVM"); + } } fn call_host_function(caller: &mut Caller<()>, function: &dyn Function) -> Result<(), String> { @@ -301,6 +305,20 @@ where call_host_function(&mut caller, function) })?; } + + // Temporary shim: `sbrk` was removed from the `jam_v1` instruction set (GP 0.8.0) + // and replaced with a `grow_heap` host call. The guest-side allocator in + // `sp-io` imports this symbol. + linker.define_untyped("grow_heap", |caller: Caller<()>| { + let size = caller.instance.reg(Reg::A0) as u32; + match caller.instance.sbrk(size) { + Ok(Some(ptr)) => caller.instance.set_reg(Reg::A0, ptr as u64), + Ok(None) => caller.instance.set_reg(Reg::A0, 0), + Err(e) => return Err(e.to_string()), + } + Ok(()) + })?; + let instance_pre = linker.instantiate_pre(&module)?; Ok(Box::new(InstancePre(instance_pre))) } diff --git a/substrate/client/executor/runtime-test/src/lib.rs b/substrate/client/executor/runtime-test/src/lib.rs index 8139dff114789..4d913eac908e9 100644 --- a/substrate/client/executor/runtime-test/src/lib.rs +++ b/substrate/client/executor/runtime-test/src/lib.rs @@ -100,7 +100,7 @@ sp_core::wasm_export_functions! { } fn test_clear_prefix(input: Vec) -> Vec { - storage::clear_prefix(&input, None); + storage::clear_prefix(&input, None, None); b"all ok!".to_vec() } @@ -244,7 +244,7 @@ sp_core::wasm_export_functions! { let id = sp_io::offchain::http_request_start( "POST", "http://localhost:12345", - &[], + vec![], ).ok()?; sp_io::offchain::http_request_add_header(id, "X-Auth", "test").ok()?; sp_io::offchain::http_request_write_body(id, &[1, 2, 3, 4], None).ok()?; @@ -388,14 +388,14 @@ mod output_validity { // Returns a huge len. It should result in an error, and not an allocation. #[no_mangle] #[cfg(not(feature = "std"))] - pub extern "C" fn test_return_huge_len(_params: *const u8, _len: usize) -> u64 { + pub extern "C" fn test_return_huge_len(_len: usize) -> u64 { pack_ptr_and_len(0, u32::MAX) } // Returns an offset right before the edge of the wasm memory boundary. It should succeed. #[no_mangle] #[cfg(not(feature = "std"))] - pub extern "C" fn test_return_max_memory_offset(_params: *const u8, _len: usize) -> u64 { + pub extern "C" fn test_return_max_memory_offset(_len: usize) -> u64 { let output_ptr = (core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32 - 1; let ptr = output_ptr as *mut u8; unsafe { @@ -407,17 +407,14 @@ mod output_validity { // Returns an offset right after the edge of the wasm memory boundary. It should fail. #[no_mangle] #[cfg(not(feature = "std"))] - pub extern "C" fn test_return_max_memory_offset_plus_one( - _params: *const u8, - _len: usize, - ) -> u64 { + pub extern "C" fn test_return_max_memory_offset_plus_one(_len: usize) -> u64 { pack_ptr_and_len((core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32, 1) } // Returns an output that overflows the u32 range. It should result in an error. #[no_mangle] #[cfg(not(feature = "std"))] - pub extern "C" fn test_return_overflow(_params: *const u8, _len: usize) -> u64 { + pub extern "C" fn test_return_overflow(_len: usize) -> u64 { pack_ptr_and_len(u32::MAX, 1) } } diff --git a/substrate/client/executor/src/executor.rs b/substrate/client/executor/src/executor.rs index 39064e748d5d9..769e694705c79 100644 --- a/substrate/client/executor/src/executor.rs +++ b/substrate/client/executor/src/executor.rs @@ -356,6 +356,9 @@ where heap_alloc_strategy, self.allow_missing_host_functions, |module, instance, version, ext| { + if let Some(v) = version { + ext.set_runtime_state_version(v.state_version()); + } let module = AssertUnwindSafe(module); let instance = AssertUnwindSafe(instance); let ext = AssertUnwindSafe(ext); diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index 91dcd4bc1cd91..b2264a840c109 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -430,12 +430,11 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { .unwrap_err(); match err { - Error::AbortedDueToTrap(error) - if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) => - { - assert_eq!( + Error::AbortedDueToPanic(error) => { + assert!( + error.message.contains("memory allocation of"), + "unexpected panic message: {}", error.message, - r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""# ); }, error => panic!("unexpected error: {:?}", error), diff --git a/substrate/client/executor/wasmtime/src/host.rs b/substrate/client/executor/wasmtime/src/host.rs index a822200815c6c..7f2297bab1640 100644 --- a/substrate/client/executor/wasmtime/src/host.rs +++ b/substrate/client/executor/wasmtime/src/host.rs @@ -33,14 +33,22 @@ pub struct HostState { /// This is stored as an `Option` as we need to temporarily set this to `None` when we are /// allocating/deallocating memory. The problem being that we can only mutable access `caller` /// once. - allocator: Option, + pub(crate) allocator: Option, panic_message: Option, + pub(crate) input_data: Option>, } impl HostState { /// Constructs a new `HostState`. - pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { allocator: Some(allocator), panic_message: None } + /// + /// `allocator` is `Some` for old-style runtimes that use host-side allocation + /// (`FreeingBumpHeapAllocator`), and `None` for new runtimes that manage their own + /// heap with a runtime-side allocator (picoalloc). + pub fn new( + allocator: Option, + input_data: impl Into>, + ) -> Self { + HostState { allocator, panic_message: None, input_data: Some(input_data.into()) } } /// Takes the error message out of the host state, leaving a `None` in its place. @@ -49,9 +57,7 @@ impl HostState { } pub(crate) fn allocation_stats(&self) -> AllocationStats { - self.allocator.as_ref() - .expect("Allocator is always set and only unavailable when doing an allocation/deallocation; qed") - .stats() + self.allocator.as_ref().map(|a| a.stats()).unwrap_or_default() } } @@ -86,11 +92,10 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { let memory = self.caller.data().memory(); - let mut allocator = self - .host_state_mut() - .allocator - .take() - .expect("allocator is not empty when calling a function in wasm; qed"); + let mut allocator = self.host_state_mut().allocator.take().expect( + "host-side allocator is not available; this runtime uses runtime-side allocation \ + and must not call host functions that allocate guest memory; qed", + ); // We can not return on error early, as we need to store back allocator. let res = allocator @@ -104,11 +109,10 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { let memory = self.caller.data().memory(); - let mut allocator = self - .host_state_mut() - .allocator - .take() - .expect("allocator is not empty when calling a function in wasm; qed"); + let mut allocator = self.host_state_mut().allocator.take().expect( + "host-side allocator is not available; this runtime uses runtime-side allocation \ + and must not call host functions that deallocate guest memory; qed", + ); // We can not return on error early, as we need to store back allocator. let res = allocator @@ -123,4 +127,11 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn register_panic_error_message(&mut self, message: &str) { self.host_state_mut().panic_message = Some(message.to_owned()); } + + fn take_input_data(&mut self) -> sp_wasm_interface::Result> { + self.host_state_mut() + .input_data + .take() + .ok_or_else(|| "Input data already taken".into()) + } } diff --git a/substrate/client/executor/wasmtime/src/imports.rs b/substrate/client/executor/wasmtime/src/imports.rs index 828c689c6f7ce..9657f357b114a 100644 --- a/substrate/client/executor/wasmtime/src/imports.rs +++ b/substrate/client/executor/wasmtime/src/imports.rs @@ -17,13 +17,14 @@ // along with this program. If not, see . use crate::{host::HostContext, runtime::StoreData}; -use sc_executor_common::error::WasmError; +use sc_executor_common::{error::WasmError, RuntimeAllocSanityChecker}; use sp_wasm_interface::{FunctionContext, HostFunctions}; use std::collections::HashMap; use wasmtime::{ExternType, FuncType, ImportType, Linker, Module}; /// Goes over all imports of a module and prepares the given linker for instantiation of the module. -/// Returns an error if there are imports that cannot be satisfied. +/// Returns an error if there are imports that cannot be satisfied or if the runtime mixes both +/// allocation sides. pub(crate) fn prepare_imports( linker: &mut Linker, module: &Module, @@ -32,6 +33,7 @@ pub(crate) fn prepare_imports( where H: HostFunctions, { + let mut alloc_sanity_checker = RuntimeAllocSanityChecker::new(); let mut pending_func_imports = HashMap::new(); for import_ty in module.imports() { let name = import_ty.name(); @@ -44,6 +46,8 @@ where ))); } + alloc_sanity_checker.check(name); + match import_ty.ty() { ExternType::Func(func_ty) => { pending_func_imports.insert(name.to_owned(), (import_ty, func_ty)); @@ -85,6 +89,13 @@ where } } + if !alloc_sanity_checker.check_result() { + return Err(WasmError::Other( + "runtime imports functions that allocate on both the host and the runtime side" + .to_string(), + )); + } + Ok(()) } diff --git a/substrate/client/executor/wasmtime/src/instance_wrapper.rs b/substrate/client/executor/wasmtime/src/instance_wrapper.rs index b3183982cfc45..6be7ea85e4894 100644 --- a/substrate/client/executor/wasmtime/src/instance_wrapper.rs +++ b/substrate/client/executor/wasmtime/src/instance_wrapper.rs @@ -30,21 +30,41 @@ use sp_wasm_interface::{Pointer, WordSize}; use wasmtime::{AsContext, AsContextMut, Engine, Instance, InstancePre, Memory}; /// Wasm blob entry point. -pub struct EntryPoint(wasmtime::TypedFunc<(u32, u32), u64>); +pub enum EntryPoint { + V1(wasmtime::TypedFunc<(u32, u32), u64>), + V2(wasmtime::TypedFunc<(u32,), u64>), +} impl EntryPoint { /// Call this entry point. - pub(crate) fn call( - &self, - store: &mut Store, - data_ptr: Pointer, - data_len: WordSize, - ) -> Result { - let data_ptr = u32::from(data_ptr); - let data_len = u32::from(data_len); - - self.0.call(&mut *store, (data_ptr, data_len)).map_err(|trap| { - let host_state = store + pub(crate) fn call(&self, instance: &mut InstanceWrapper) -> Result { + let result = + match self { + Self::V1(func) => { + // SAFETY: Entry point signature has been checked statically and represents a + // V1 entry point. The V1 code is known to use the host-side allocator. + let (data_ptr, data_len) = unsafe { instance.inject_input_data()? }; + let data_ptr = u32::from(data_ptr); + let data_len = u32::from(data_len); + func.call(instance.store_mut(), (data_ptr, data_len)) + }, + Self::V2(func) => { + let host_state = + instance.store().data().host_state.as_ref().expect( + "host state cannot be empty while a function is being called; qed", + ); + let data_len = host_state + .input_data + .as_ref() + .expect("input data cannot be empty while a function is being called; qed") + .len() as u32; + func.call(instance.store_mut(), (data_len,)) + }, + }; + + result.map_err(|trap| { + let host_state = instance + .store_mut() .data_mut() .host_state .as_mut() @@ -69,10 +89,18 @@ impl EntryPoint { func: wasmtime::Func, ctx: impl AsContext, ) -> std::result::Result { - let entrypoint = func - .typed::<(u32, u32), u64>(ctx) - .map_err(|_| "Invalid signature for direct entry point")?; - Ok(Self(entrypoint)) + let ty = func.ty(ctx.as_context()); + if ty.params().len() == 1 { + let entrypoint = func + .typed::<(u32,), u64>(ctx) + .map_err(|_| "Invalid signature for direct V2 entry point")?; + Ok(Self::V2(entrypoint)) + } else { + let entrypoint = func + .typed::<(u32, u32), u64>(ctx) + .map_err(|_| "Invalid signature for direct V1 entry point")?; + Ok(Self::V1(entrypoint)) + } } } @@ -226,6 +254,52 @@ impl InstanceWrapper { Ok(heap_base as u32) } + + /// Injects the input data into the guest's memory. + /// + /// Should only be used for code using the host-side allocator. Otherwise will corrupt + /// guest's memory + pub(crate) unsafe fn inject_input_data(&mut self) -> Result<(Pointer, WordSize)> { + let store = self.store_mut(); + let mut allocator = store + .data_mut() + .host_state + .as_mut() + .expect("host state cannot be empty while a function is being called; qed") + .allocator + .take() + .expect("allocator cannot be empty while a function is being called; qed"); + + let result = { + let mut ctx = store.as_context_mut(); + let host_data = ctx.data_mut(); + let memory = host_data.memory(); + let data = host_data + .host_state + .as_mut() + .expect("host state cannot be empty while a function is being called; qed") + .input_data + .take() + .expect("input data cannot be empty while a function is being called; qed"); + + let data_len = data.len() as WordSize; + match allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len) { + Ok(data_ptr) => crate::util::write_memory_from(&mut ctx, data_ptr, &data[..]) + .map(|_| (data_ptr, data_len)) + .map_err(Into::into), + Err(e) => Err(e.into()), + } + }; + + store + .data_mut() + .host_state + .as_mut() + .expect("host state cannot be empty while a function is being called; qed") + .allocator = Some(allocator); + + result + } } /// Extract linear memory instance from the given instance. diff --git a/substrate/client/executor/wasmtime/src/runtime.rs b/substrate/client/executor/wasmtime/src/runtime.rs index 49a96a61c9d5d..34093a23af16d 100644 --- a/substrate/client/executor/wasmtime/src/runtime.rs +++ b/substrate/client/executor/wasmtime/src/runtime.rs @@ -20,7 +20,7 @@ use crate::{ host::HostState, - instance_wrapper::{EntryPoint, InstanceWrapper, MemoryWrapper}, + instance_wrapper::{EntryPoint, InstanceWrapper}, util::{self, replace_strategy_if_broken}, }; @@ -33,7 +33,7 @@ use sc_executor_common::{ wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{HostFunctions, Pointer, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer}; use std::{ path::{Path, PathBuf}, sync::{ @@ -183,9 +183,13 @@ impl WasmtimeInstance { match &mut self.strategy { Strategy::RecreateInstance(ref mut instance_creator) => { let mut instance_wrapper = instance_creator.instantiate()?; - let heap_base = instance_wrapper.extract_heap_base()?; let entrypoint = instance_wrapper.resolve_entrypoint(method)?; - let allocator = FreeingBumpHeapAllocator::new(heap_base); + let allocator = if matches!(entrypoint, EntryPoint::V1(_)) { + let heap_base = instance_wrapper.extract_heap_base()?; + Some(FreeingBumpHeapAllocator::new(heap_base)) + } else { + None + }; perform_call(data, &mut instance_wrapper, entrypoint, allocator, allocation_stats) }, @@ -685,19 +689,15 @@ fn perform_call( data: &[u8], instance_wrapper: &mut InstanceWrapper, entrypoint: EntryPoint, - mut allocator: FreeingBumpHeapAllocator, + allocator: Option, allocation_stats: &mut Option, ) -> Result> { - let (data_ptr, data_len) = inject_input_data(instance_wrapper, &mut allocator, data)?; - - let host_state = HostState::new(allocator); + let host_state = HostState::new(allocator, data); // Set the host state before calling into wasm. instance_wrapper.store_mut().data_mut().host_state = Some(host_state); - let ret = entrypoint - .call(instance_wrapper.store_mut(), data_ptr, data_len) - .map(unpack_ptr_and_len); + let ret = entrypoint.call(instance_wrapper).map(unpack_ptr_and_len); // Reset the host state let host_state = instance_wrapper.store_mut().data_mut().host_state.take().expect( @@ -711,19 +711,6 @@ fn perform_call( Ok(output) } -fn inject_input_data( - instance: &mut InstanceWrapper, - allocator: &mut FreeingBumpHeapAllocator, - data: &[u8], -) -> Result<(Pointer, WordSize)> { - let mut ctx = instance.store_mut(); - let memory = ctx.data().memory(); - let data_len = data.len() as WordSize; - let data_ptr = allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len)?; - util::write_memory_from(instance.store_mut(), data_ptr, data)?; - Ok((data_ptr, data_len)) -} - fn extract_output_data( instance: &InstanceWrapper, output_ptr: u32, diff --git a/substrate/frame/beefy-mmr/src/tests.rs b/substrate/frame/beefy-mmr/src/tests.rs index 6f1ca8620115c..b8714d21880df 100644 --- a/substrate/frame/beefy-mmr/src/tests.rs +++ b/substrate/frame/beefy-mmr/src/tests.rs @@ -218,7 +218,7 @@ fn extract_validation_context_should_work_correctly() { // Check the MMR root log let expected_mmr_root: [u8; 32] = array_bytes::hex_n_into_unchecked( - "322c6a46ac00d3455c87bd9af42ebafb388f589a1b562f5e39b1d0d71bcbe8e0", + "e28211a87f4c92db61e44b411c94f396d2ed34daab0e653deea932b9ffd5bc21", ); assert_eq!( diff --git a/substrate/frame/benchmarking/src/utils.rs b/substrate/frame/benchmarking/src/utils.rs index e65b1b55a54ab..d030ff62039df 100644 --- a/substrate/frame/benchmarking/src/utils.rs +++ b/substrate/frame/benchmarking/src/utils.rs @@ -22,13 +22,13 @@ use frame_support::{dispatch::DispatchErrorWithPostInfo, pallet_prelude::*, trai use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_io::hashing::blake2_256; +use sp_io::{hashing::blake2_256, RIIntOption}; use sp_runtime::{ traits::TrailingZeroInput, transaction_validity::TransactionValidityError, DispatchError, }; use sp_runtime_interface::pass_by::{ - AllocateAndReturnByCodec, AllocateAndReturnPointer, PassFatPointerAndDecode, - PassFatPointerAndRead, + AllocateAndReturnByCodec, AllocateAndReturnPointer, ConvertAndReturnAs, + PassFatPointerAndDecode, PassFatPointerAndRead, PassFatPointerAndWrite, PassPointerAndWrite, }; use sp_storage::TrackedStorageKey; @@ -282,6 +282,26 @@ pub trait Benchmarking { .to_le_bytes() } + /// Same as version 1 but avoids host-side allocation. + #[version(2)] + #[raw_api] + fn current_time(out: PassPointerAndWrite<&mut [u8; 16], 16>) { + let time = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Unix time doesn't go backwards; qed") + .as_nanos() + .to_le_bytes(); + out.copy_from_slice(&time); + } + + /// Wrapper for `current_time`. + #[wrapper] + fn current_time() -> [u8; 16] { + let mut out = [0u8; 16]; + current_time__raw(&mut out); + out + } + /// Reset the trie database to the genesis state. fn wipe_db(&mut self) { self.wipe() @@ -297,6 +317,30 @@ pub trait Benchmarking { self.read_write_count() } + /// Same as version 1 but avoids host-side allocation. + #[version(2)] + #[raw_api] + fn read_write_count(&self, out: PassPointerAndWrite<&mut [u8; 16], 16>) { + let (a, b, c, d) = self.read_write_count(); + out[0..4].copy_from_slice(&a.to_le_bytes()); + out[4..8].copy_from_slice(&b.to_le_bytes()); + out[8..12].copy_from_slice(&c.to_le_bytes()); + out[12..16].copy_from_slice(&d.to_le_bytes()); + } + + /// Wrapper for `read_write_count`. + #[wrapper] + fn read_write_count() -> (u32, u32, u32, u32) { + let mut out = [0u8; 16]; + read_write_count__raw(&mut out); + ( + u32::from_le_bytes(out[0..4].try_into().expect("slice is 4 bytes")), + u32::from_le_bytes(out[4..8].try_into().expect("slice is 4 bytes")), + u32::from_le_bytes(out[8..12].try_into().expect("slice is 4 bytes")), + u32::from_le_bytes(out[12..16].try_into().expect("slice is 4 bytes")), + ) + } + /// Reset the read/write count. fn reset_read_write_count(&mut self) { self.reset_read_write_count() @@ -307,6 +351,30 @@ pub trait Benchmarking { self.get_whitelist() } + /// Same as version 1 but avoids host-side allocation. + #[version(2)] + #[raw_api] + fn get_whitelist(&self, out: PassFatPointerAndWrite<&mut [u8]>) -> u32 { + let whitelist = self.get_whitelist(); + let encoded = codec::Encode::encode(&whitelist); + let copy_len = encoded.len().min(out.len()); + out[..copy_len].copy_from_slice(&encoded[..copy_len]); + encoded.len() as u32 + } + + /// Wrapper for `get_whitelist`. + #[wrapper] + fn get_whitelist() -> Vec { + let mut buf = alloc::vec![0u8; 1024 * 1024]; + let len = get_whitelist__raw(&mut buf) as usize; + if len > buf.len() { + buf.resize(len, 0); + let len2 = get_whitelist__raw(&mut buf) as usize; + debug_assert_eq!(len, len2); + } + codec::Decode::decode(&mut &buf[..len]).expect("get_whitelist: decoding should not fail") + } + /// Set the DB whitelist. fn set_whitelist(&mut self, new: PassFatPointerAndDecode>) { self.set_whitelist(new) @@ -347,10 +415,48 @@ pub trait Benchmarking { self.get_read_and_written_keys() } + /// Same as version 1 but avoids host-side allocation. + #[version(2)] + #[raw_api] + fn get_read_and_written_keys(&self, out: PassFatPointerAndWrite<&mut [u8]>) -> u32 { + let keys = self.get_read_and_written_keys(); + let encoded = codec::Encode::encode(&keys); + let copy_len = encoded.len().min(out.len()); + out[..copy_len].copy_from_slice(&encoded[..copy_len]); + encoded.len() as u32 + } + + /// Wrapper for `get_read_and_written_keys`. + #[wrapper] + fn get_read_and_written_keys() -> Vec<(Vec, u32, u32, bool)> { + let mut buf = alloc::vec![0u8; 4 * 1024 * 1024]; + let len = get_read_and_written_keys__raw(&mut buf) as usize; + if len > buf.len() { + buf.resize(len, 0); + let len2 = get_read_and_written_keys__raw(&mut buf) as usize; + debug_assert_eq!(len, len2); + } + codec::Decode::decode(&mut &buf[..len]) + .expect("get_read_and_written_keys: decoding should not fail") + } + /// Get current estimated proof size. fn proof_size(&self) -> AllocateAndReturnByCodec> { self.proof_size() } + + /// Same as version 1 but avoids host-side allocation. + #[version(2)] + #[raw_api] + fn proof_size(&self) -> ConvertAndReturnAs, RIIntOption, i64> { + self.proof_size() + } + + /// Wrapper for `proof_size`. + #[wrapper] + fn proof_size() -> Option { + proof_size__raw() + } } /// The pallet benchmarking trait. diff --git a/substrate/frame/benchmarking/src/v1.rs b/substrate/frame/benchmarking/src/v1.rs index e039cd05f3c4f..80d8c079b257a 100644 --- a/substrate/frame/benchmarking/src/v1.rs +++ b/substrate/frame/benchmarking/src/v1.rs @@ -1144,7 +1144,7 @@ macro_rules! impl_benchmark { // Time the storage root recalculation. let start_storage_root = $crate::current_time(); - $crate::__private::storage_root($crate::__private::StateVersion::V1); + $crate::__private::storage_root(); let finish_storage_root = $crate::current_time(); let elapsed_storage_root = finish_storage_root - start_storage_root; diff --git a/substrate/frame/bounties/src/migrations/v4.rs b/substrate/frame/bounties/src/migrations/v4.rs index f35c600147189..89cb24954a541 100644 --- a/substrate/frame/bounties/src/migrations/v4.rs +++ b/substrate/frame/bounties/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use alloc::vec::Vec; use core::str; use frame_support::{ storage::{generator::StorageValue, StoragePrefixedMap}, @@ -144,21 +145,21 @@ pub fn pre_migration::on_chain_storage_version() < 4); } @@ -206,14 +207,23 @@ pub fn post_migration::on_chain_storage_version(), 4); } diff --git a/substrate/frame/collective/src/lib.rs b/substrate/frame/collective/src/lib.rs index 7b98a317134f0..7c8f534d45139 100644 --- a/substrate/frame/collective/src/lib.rs +++ b/substrate/frame/collective/src/lib.rs @@ -797,7 +797,7 @@ pub mod pallet { /// + `proposal_weight_bound`: The maximum amount of weight consumed by executing the closed /// proposal. /// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via - /// `storage::read` so it is `size_of::() == 4` larger than the pure length. + /// `storage::read_exact` so it is `size_of::() == 4` larger than the pure length. /// /// ## Complexity /// - `O(B + M + P1 + P2)` where: @@ -1118,8 +1118,8 @@ impl, I: 'static> Pallet { /// Ensure that the right proposal bounds were passed and get the proposal from storage. /// - /// Checks the length in storage via `storage::read` which adds an extra `size_of::() == 4` - /// to the length. + /// Checks the length in storage via `storage::read_exact` which adds an extra `size_of::() + /// == 4` to the length. fn validate_and_get_proposal( hash: &T::Hash, length_bound: u32, @@ -1128,7 +1128,7 @@ impl, I: 'static> Pallet { let key = ProposalOf::::hashed_key_for(hash); // read the length of the proposal storage entry directly let proposal_len = - storage::read(&key, &mut [0; 0], 0).ok_or(Error::::ProposalMissing)?; + storage::read_exact(&key, &mut [0; 0], 0).ok_or(Error::::ProposalMissing)?; ensure!(proposal_len <= length_bound, Error::::WrongProposalLength); let proposal = ProposalOf::::get(hash).ok_or(Error::::ProposalMissing)?; let proposal_weight = proposal.get_dispatch_info().call_weight; diff --git a/substrate/frame/contracts/src/storage.rs b/substrate/frame/contracts/src/storage.rs index 9522dee78f3c7..70a6cc3c9937e 100644 --- a/substrate/frame/contracts/src/storage.rs +++ b/substrate/frame/contracts/src/storage.rs @@ -35,7 +35,6 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::Get; -use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, BoundedBTreeMap, Debug, DispatchError, DispatchResult, @@ -329,23 +328,20 @@ impl ContractInfo { let Some(entry) = queue.next() else { break }; #[allow(deprecated)] - let outcome = child::kill_storage( + let outcome = child::clear_storage( &ChildInfo::new_default(&entry.trie_id), Some(remaining_key_budget), + None, ); - match outcome { - // This happens when our budget wasn't large enough to remove all keys. - KillStorageResult::SomeRemaining(keys_removed) => { - remaining_key_budget.saturating_reduce(keys_removed); - break; - }, - KillStorageResult::AllRemoved(keys_removed) => { - entry.remove(); - // charge at least one key even if none were removed. - remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1)); - }, - }; + if outcome.maybe_cursor.is_some() { + remaining_key_budget.saturating_reduce(outcome.backend); + break; + } else { + entry.remove(); + // charge at least one key even if none were removed. + remaining_key_budget = remaining_key_budget.saturating_sub(outcome.backend.max(1)); + } } meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget))) diff --git a/substrate/frame/core-fellowship/src/tests/integration.rs b/substrate/frame/core-fellowship/src/tests/integration.rs index 37a9620376ccb..27f950682bfe3 100644 --- a/substrate/frame/core-fellowship/src/tests/integration.rs +++ b/substrate/frame/core-fellowship/src/tests/integration.rs @@ -235,7 +235,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); let root_swap = hypothetically!({ @@ -248,7 +248,7 @@ fn swap_exhaustive_works() { assert_ok!(Club::exchange_member(RuntimeOrigin::root(), 0, 1)); System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); assert_eq!(root_add, root_swap); diff --git a/substrate/frame/core-fellowship/src/tests/unit.rs b/substrate/frame/core-fellowship/src/tests/unit.rs index f8ae5afcc1f23..53b362bad2971 100644 --- a/substrate/frame/core-fellowship/src/tests/unit.rs +++ b/substrate/frame/core-fellowship/src/tests/unit.rs @@ -270,12 +270,12 @@ fn import_member_same_as_import() { let import_root = hypothetically!({ assert_ok!(CoreFellowship::import(signed(0))); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); let import_member_root = hypothetically!({ assert_ok!(CoreFellowship::import_member(signed(1), 0)); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); // `import` and `import_member` do exactly the same thing. @@ -394,7 +394,7 @@ fn promote_fast_identical_to_promote() { let root_promote = hypothetically!({ assert_ok!(CoreFellowship::promote(signed(alice), alice, 1)); // Don't clean the events since they should emit the same events: - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); // This is using thread locals instead of storage... @@ -403,7 +403,7 @@ fn promote_fast_identical_to_promote() { let root_promote_fast = hypothetically!({ assert_ok!(CoreFellowship::promote_fast(signed(alice), alice, 1)); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }); assert_eq!(root_promote, root_promote_fast); diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index f3b3f888792be..e35fba8ea880d 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/frame/democracy/src/lib.rs @@ -1740,7 +1740,7 @@ impl Pallet { fn decode_compact_u32_at(key: &[u8]) -> Option { // `Compact` takes at most 5 bytes. let mut buf = [0u8; 5]; - let bytes = sp_io::storage::read(key, &mut buf, 0)?; + let bytes = sp_io::storage::read_partial(key, &mut buf, 0)?; // The value may be smaller than 5 bytes. let mut input = &buf[0..buf.len().min(bytes as usize)]; match codec::Compact::::decode(&mut input) { diff --git a/substrate/frame/elections-phragmen/src/migrations/v4.rs b/substrate/frame/elections-phragmen/src/migrations/v4.rs index 991b4c2a33449..bbf9c16a7168e 100644 --- a/substrate/frame/elections-phragmen/src/migrations/v4.rs +++ b/substrate/frame/elections-phragmen/src/migrations/v4.rs @@ -18,6 +18,7 @@ //! Migrations to version `4.0.0`, as denoted by the changelog. use super::super::LOG_TARGET; +use alloc::vec::Vec; use frame_support::{ traits::{Get, StorageVersion}, weights::Weight, @@ -77,20 +78,19 @@ pub fn pre_migration>(new: N) { log::info!("pre-migration elections-phragmen test with new = {}", new); // the next key must exist, and start with the hash of `OLD_PREFIX`. - let next_key = sp_io::storage::next_key(OLD_PREFIX).unwrap(); + let mut next_key = Vec::new(); + assert!(sp_io::storage::next_key(OLD_PREFIX, &mut next_key)); assert!(next_key.starts_with(&sp_io::hashing::twox_128(OLD_PREFIX))); // ensure nothing is stored in the new prefix. + let new_prefix = sp_io::hashing::twox_128(new.as_bytes()); + let has_next = sp_io::storage::next_key(new.as_bytes(), &mut next_key); assert!( - sp_io::storage::next_key(new.as_bytes()).map_or( - // either nothing is there - true, - // or we ensure that it has no common prefix with twox_128(new). - |next_key| !next_key.starts_with(&sp_io::hashing::twox_128(new.as_bytes())) - ), + // either nothing is there, or we ensure that it has no common prefix with twox_128(new). + !has_next || !next_key.starts_with(&new_prefix), "unexpected next_key({}) = {:?}", new, - sp_core::hexdisplay::HexDisplay::from(&sp_io::storage::next_key(new.as_bytes()).unwrap()) + sp_core::hexdisplay::HexDisplay::from(&next_key) ); // ensure storage version is 3. assert_eq!(StorageVersion::get::>(), 3); diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 1027cb8bb2e8b..0f4562e0c7315 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -116,7 +116,7 @@ mod custom { } pub fn calculate_storage_root(_origin: OriginFor) -> DispatchResult { - let root = sp_io::storage::root(sp_runtime::StateVersion::V1); + let root = sp_io::storage::root(); sp_io::storage::set("storage_root".as_bytes(), &root); Ok(()) } diff --git a/substrate/frame/grandpa/src/migrations/v4.rs b/substrate/frame/grandpa/src/migrations/v4.rs index 330d5ac4426fb..48bb88878e942 100644 --- a/substrate/frame/grandpa/src/migrations/v4.rs +++ b/substrate/frame/grandpa/src/migrations/v4.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::LOG_TARGET; +use alloc::vec::Vec; use frame_support::{ traits::{Get, StorageVersion}, weights::Weight, @@ -71,28 +72,24 @@ pub fn pre_migration>(new: N) { log::info!("pre-migration grandpa test with new = {}", new); // the next key must exist, and start with the hash of `OLD_PREFIX`. - let next_key = sp_io::storage::next_key(&twox_128(OLD_PREFIX)).unwrap(); + let mut next_key = Vec::new(); + assert!(sp_io::storage::next_key(&twox_128(OLD_PREFIX), &mut next_key)); assert!(next_key.starts_with(&twox_128(OLD_PREFIX))); // The pallet version is already stored using the pallet name let storage_key = StorageVersion::storage_key::>(); // ensure nothing is stored in the new prefix. + let new_prefix = twox_128(new.as_bytes()); + let has_next = sp_io::storage::next_key(&new_prefix, &mut next_key); assert!( - sp_io::storage::next_key(&twox_128(new.as_bytes())).map_or( - // either nothing is there - true, - // or we ensure that it has no common prefix with twox_128(new), - // or isn't the pallet version that is already stored using the pallet name - |next_key| { - !next_key.starts_with(&twox_128(new.as_bytes())) || next_key == storage_key - }, - ), + // either nothing is there, + // or we ensure that it has no common prefix with twox_128(new), + // or isn't the pallet version that is already stored using the pallet name + !has_next || !next_key.starts_with(&new_prefix) || next_key == storage_key, "unexpected next_key({}) = {:?}", new, - sp_core::hexdisplay::HexDisplay::from( - &sp_io::storage::next_key(&twox_128(new.as_bytes())).unwrap() - ), + sp_core::hexdisplay::HexDisplay::from(&next_key), ); // ensure storage version is 3. assert_eq!(StorageVersion::get::>(), 3); @@ -106,6 +103,9 @@ pub fn post_migration() { log::info!("post-migration grandpa"); // Assert that nothing remains at the old prefix - assert!(sp_io::storage::next_key(&twox_128(OLD_PREFIX)) - .map_or(true, |next_key| !next_key.starts_with(&twox_128(OLD_PREFIX)))); + let mut next_key = Vec::new(); + let old_prefix = twox_128(OLD_PREFIX); + assert!( + !sp_io::storage::next_key(&old_prefix, &mut next_key) || !next_key.starts_with(&old_prefix) + ); } diff --git a/substrate/frame/migrations/src/benchmarking.rs b/substrate/frame/migrations/src/benchmarking.rs index eaec864405306..b364baa43df28 100644 --- a/substrate/frame/migrations/src/benchmarking.rs +++ b/substrate/frame/migrations/src/benchmarking.rs @@ -23,8 +23,7 @@ use core::array; use frame_benchmarking::{v2::*, BenchmarkError}; use frame_system::{Pallet as System, RawOrigin}; use sp_core::Get; -use sp_crypto_hashing::twox_128; -use sp_io::{storage, KillStorageResult}; +use sp_io::storage; use sp_runtime::traits::One; fn assert_has_event(generic_event: ::RuntimeEvent) { @@ -214,7 +213,7 @@ mod benches { #[benchmark(skip_meta, pov_mode = Measured)] fn reset_pallet_migration(n: Linear<0, 2048>) -> Result<(), BenchmarkError> { - let prefix: [u8; 16] = twox_128(b"__ResetPalletBenchmarkPrefix__"); + let prefix: [u8; 16] = sp_io::hashing::twox_128(b"__ResetPalletBenchmarkPrefix__"); for i in 0..n { // we need to avoid allocations here @@ -228,19 +227,19 @@ mod benches { let result; #[block] { - result = storage::clear_prefix(&prefix, None); + result = storage::clear_prefix(prefix, None, None); } // It will always reports no keys removed because they are still in the overlay. // However, the benchmarking PoV results are correctly dependent on the amount of // keys removed. - match result { - KillStorageResult::AllRemoved(_i) => { - // during the test the storage is not comitted and `i` will always be 0 - #[cfg(not(test))] - ensure!(_i == n, "Not all keys are removed"); - }, - _ => Err("Not all keys were removed")?, + + if result.maybe_cursor.is_none() { + // All the keys removed + #[cfg(not(test))] + ensure!(result.backend == n, "Not all keys are removed"); + } else { + Err("Not all keys were removed")? } Ok(()) diff --git a/substrate/frame/migrations/src/migrations.rs b/substrate/frame/migrations/src/migrations.rs index 1ff6b30ad4088..45354404844d6 100644 --- a/substrate/frame/migrations/src/migrations.rs +++ b/substrate/frame/migrations/src/migrations.rs @@ -24,8 +24,7 @@ use frame_support::{ weights::WeightMeter, }; use sp_core::Get; -use sp_crypto_hashing::twox_128; -use sp_io::{storage::clear_prefix, KillStorageResult}; +use sp_io::storage::clear_prefix; use sp_runtime::SaturatedConversion; /// Remove all of a pallet's state and re-initializes it to the current in-code storage version. @@ -65,7 +64,7 @@ where type Identifier = [u8; 16]; fn id() -> Self::Identifier { - ("RemovePallet::", P::name()).using_encoded(twox_128) + ("RemovePallet::", P::name()).using_encoded(sp_io::hashing::twox_128) } fn step( @@ -97,14 +96,11 @@ where }); } - let (keys_removed, is_done) = match clear_prefix(&P::name_hash(), Some(key_budget)) { - KillStorageResult::AllRemoved(value) => (value, true), - KillStorageResult::SomeRemaining(value) => (value, false), - }; + let outcome = clear_prefix(P::name_hash(), Some(key_budget), None); - meter.consume(T::WeightInfo::reset_pallet_migration(keys_removed)); + meter.consume(T::WeightInfo::reset_pallet_migration(outcome.backend)); - Ok(Some(is_done)) + Ok(Some(outcome.maybe_cursor.is_none())) } #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/node-authorization/src/lib.rs b/substrate/frame/node-authorization/src/lib.rs index 7e62dbc57e208..8d60d31e36eea 100644 --- a/substrate/frame/node-authorization/src/lib.rs +++ b/substrate/frame/node-authorization/src/lib.rs @@ -175,26 +175,20 @@ pub mod pallet { /// Set reserved node every block. It may not be enabled depends on the offchain /// worker settings when starting the node. fn offchain_worker(now: BlockNumberFor) { - let network_state = sp_io::offchain::network_state(); - match network_state { - Err(_) => log::error!( - target: "runtime::node-authorization", - "Error: failed to get network state of node at {:?}", - now, - ), - Ok(state) => { - let encoded_peer = state.peer_id.0; - match Decode::decode(&mut &encoded_peer[..]) { - Err(_) => log::error!( - target: "runtime::node-authorization", - "Error: failed to decode PeerId at {:?}", - now, - ), - Ok(node) => sp_io::offchain::set_authorized_nodes( - Self::get_authorized_nodes(&PeerId(node)), - true, - ), - } + let mut peer_id = sp_io::NetworkPeerId::default(); + match sp_io::offchain::network_peer_id(&mut peer_id) { + Ok(_) => { + sp_io::offchain::set_authorized_nodes( + Self::get_authorized_nodes(&PeerId(peer_id.0.to_vec())), + true, + ); + }, + Err(_) => { + log::error!( + target: "runtime::node-authorization", + "Error: failed to get network peer id at {:?}", + now, + ); }, } } diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 722d6034b7e83..22e882a4f4554 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -240,7 +240,7 @@ pub fn can_control_account( pub fn root_without_events() -> Vec { hypothetically!({ clear_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root() }) } diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index 19c69de94b029..a5af29a48f748 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -40,7 +40,6 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::{Get, H160}; -use sp_io::KillStorageResult; use sp_runtime::{ Debug, DispatchError, traits::{Hash, Saturating, Zero}, @@ -455,24 +454,21 @@ impl ContractInfo { if key_budget == 0 { break; } - #[allow(deprecated)] - let outcome = child::kill_storage( + let outcome = child::clear_storage( &ChildInfo::new_default(&entry.value.trie_id), Some(key_budget), + None, ); - match outcome { - KillStorageResult::SomeRemaining(keys_removed) => { - remaining = remaining - .saturating_sub(weight_per_trie_key.saturating_mul(keys_removed.into())); - break; - }, - KillStorageResult::AllRemoved(keys_removed) => { - remaining = remaining.saturating_sub( - weight_per_trie_key.saturating_mul(u64::from(keys_removed)), - ); - entry.remove(); - }, - }; + + if outcome.maybe_cursor.is_some() { + remaining = remaining + .saturating_sub(weight_per_trie_key.saturating_mul(outcome.backend.into())); + break; + } else { + remaining = remaining + .saturating_sub(weight_per_trie_key.saturating_mul(outcome.backend.into())); + entry.remove(); + } } meter.consume(budget.saturating_sub(remaining)); diff --git a/substrate/frame/salary/src/tests/integration.rs b/substrate/frame/salary/src/tests/integration.rs index e54c7519a7684..8789b14e86349 100644 --- a/substrate/frame/salary/src/tests/integration.rs +++ b/substrate/frame/salary/src/tests/integration.rs @@ -183,7 +183,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(StateVersion::V1) + sp_io::storage::root() }); let root_swap = hypothetically!({ @@ -196,7 +196,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(StateVersion::V1) + sp_io::storage::root() }); assert_eq!(root_add, root_swap); diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 891cdcbd5112e..92a3806922e22 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -364,8 +364,9 @@ pub mod v11 { ); let old_pallet_prefix = twox_128(N::get().as_bytes()); + let mut next_key = Vec::new(); frame_support::ensure!( - sp_io::storage::next_key(&old_pallet_prefix).is_some(), + sp_io::storage::next_key(&old_pallet_prefix, &mut next_key), "no data for the old pallet name has been detected" ); @@ -421,15 +422,16 @@ pub mod v11 { } let old_pallet_prefix = twox_128(N::get().as_bytes()); + let mut next_key = Vec::new(); frame_support::ensure!( - sp_io::storage::next_key(&old_pallet_prefix).is_none(), + !sp_io::storage::next_key(&old_pallet_prefix, &mut next_key), "old pallet data hasn't been removed" ); let new_pallet_name =

::name(); let new_pallet_prefix = twox_128(new_pallet_name.as_bytes()); frame_support::ensure!( - sp_io::storage::next_key(&new_pallet_prefix).is_some(), + sp_io::storage::next_key(&new_pallet_prefix, &mut next_key), "new pallet data hasn't been created" ); diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index c86b3f40ddd46..4b6566017d8cc 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -325,8 +325,9 @@ pub mod pallet { { (Progress::LastKey(last_child), Progress::LastKey(last_top)) => { let child_root = Pallet::::transform_child_key_or_halt(last_top); + let mut next = Vec::new(); let maybe_current_child: Option> = - if let Some(next) = child_io::next_key(child_root, last_child) { + if child_io::next_key(child_root, last_child, &mut next) { Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) } else { None @@ -371,8 +372,9 @@ pub mod pallet { fn migrate_top(&mut self) -> Result<(), Error> { let maybe_current_top = match &self.progress_top { Progress::LastKey(last_top) => { + let mut next = Vec::new(); let maybe_top: Option> = - if let Some(next) = sp_io::storage::next_key(last_top) { + if sp_io::storage::next_key(last_top, &mut next) { Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) } else { None @@ -1147,7 +1149,8 @@ mod benchmarks { { let data = sp_io::storage::get(KEY).unwrap(); sp_io::storage::set(KEY, &data); - let _next = sp_io::storage::next_key(KEY); + let mut next = alloc::vec::Vec::new(); + sp_io::storage::next_key(KEY, &mut next); assert_eq!(data, value); } diff --git a/substrate/frame/support/procedural/src/benchmark.rs b/substrate/frame/support/procedural/src/benchmark.rs index 231e22ff45110..690d45918e9c9 100644 --- a/substrate/frame/support/procedural/src/benchmark.rs +++ b/substrate/frame/support/procedural/src/benchmark.rs @@ -834,7 +834,7 @@ pub fn benchmarks( // Time the storage root recalculation. let start_storage_root = #krate::current_time(); - #krate::__private::storage_root(#krate::__private::StateVersion::V1); + #krate::__private::storage_root(); let finish_storage_root = #krate::current_time(); let elapsed_storage_root = finish_storage_root - start_storage_root; diff --git a/substrate/frame/support/src/macros.rs b/substrate/frame/support/src/macros.rs index 7260a9d0bd1bf..7579546f00bc4 100644 --- a/substrate/frame/support/src/macros.rs +++ b/substrate/frame/support/src/macros.rs @@ -370,13 +370,9 @@ macro_rules! assert_noop { $x:expr, $y:expr $(,)? ) => { - let h = $crate::__private::storage_root($crate::__private::StateVersion::V1); + let h = $crate::__private::storage_root(); $crate::assert_err!($x, $y); - assert_eq!( - h, - $crate::__private::storage_root($crate::__private::StateVersion::V1), - "storage has been mutated" - ); + assert_eq!(h, $crate::__private::storage_root(), "storage has been mutated"); }; } @@ -389,9 +385,9 @@ macro_rules! assert_storage_noop { ( $x:expr ) => { - let h = $crate::__private::storage_root($crate::__private::StateVersion::V1); + let h = $crate::__private::storage_root(); $x; - assert_eq!(h, $crate::__private::storage_root($crate::__private::StateVersion::V1)); + assert_eq!(h, $crate::__private::storage_root()); }; } diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs index 37af6bc95ad16..7d3297ecd2c8d 100644 --- a/substrate/frame/support/src/migrations.rs +++ b/substrate/frame/support/src/migrations.rs @@ -30,7 +30,7 @@ use core::marker::PhantomData; use impl_trait_for_tuples::impl_for_tuples; use sp_arithmetic::traits::Bounded; use sp_core::Get; -use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; +use sp_io::{hashing::twox_128, storage::clear_prefix}; use sp_runtime::traits::Zero; /// Handles storage migration pallet versioning. @@ -320,15 +320,16 @@ impl, DbWeight: Get> frame_support::traits { fn on_runtime_upgrade() -> frame_support::weights::Weight { let hashed_prefix = twox_128(P::get().as_bytes()); - let keys_removed = match clear_prefix(&hashed_prefix, None) { - KillStorageResult::AllRemoved(value) => value, - KillStorageResult::SomeRemaining(value) => { + let r = clear_prefix(&hashed_prefix, None, None); + let keys_removed = match r.maybe_cursor { + Some(_) => { log::error!( "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! 🚨", P::get() ); - value + r.backend }, + None => r.backend, } as u64; log::info!("Removed {} {} keys 🧹", keys_removed, P::get()); @@ -427,15 +428,16 @@ impl, S: Get<&'static str>, DbWeight: Get> { fn on_runtime_upgrade() -> frame_support::weights::Weight { let hashed_prefix = storage_prefix(P::get().as_bytes(), S::get().as_bytes()); - let keys_removed = match clear_prefix(&hashed_prefix, None) { - KillStorageResult::AllRemoved(value) => value, - KillStorageResult::SomeRemaining(value) => { + let r = clear_prefix(&hashed_prefix, None, None); + let keys_removed = match r.maybe_cursor { + Some(_) => { log::error!( "`clear_prefix` failed to remove all keys for storage `{}` from pallet `{}`. THIS SHOULD NEVER HAPPEN! 🚨", S::get(), P::get() ); - value + r.backend }, + None => r.backend, } as u64; log::info!("Removed `{}` `{}` `{}` keys 🧹", keys_removed, P::get(), S::get()); diff --git a/substrate/frame/support/src/storage/child.rs b/substrate/frame/support/src/storage/child.rs index 34085208bdc28..de7a41b1daf49 100644 --- a/substrate/frame/support/src/storage/child.rs +++ b/substrate/frame/support/src/storage/child.rs @@ -118,34 +118,6 @@ pub fn exists(child_info: &ChildInfo, key: &[u8]) -> bool { } } -/// Remove all `storage_key` key/values -/// -/// Deletes all keys from the overlay and up to `limit` keys from the backend if -/// it is set to `Some`. No limit is applied when `limit` is set to `None`. -/// -/// The limit can be used to partially delete a child trie in case it is too large -/// to delete in one go (block). -/// -/// # Note -/// -/// Please note that keys that are residing in the overlay for that child trie when -/// issuing this call are all deleted without counting towards the `limit`. Only keys -/// written during the current block are part of the overlay. Deleting with a `limit` -/// mostly makes sense with an empty overlay for that child trie. -/// -/// Calling this function multiple times per block for the same `storage_key` does -/// not make much sense because it is not cumulative when called inside the same block. -/// Use this function to distribute the deletion of a single child trie across multiple -/// blocks. -#[deprecated = "Use `clear_storage` instead"] -pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageResult { - match child_info.child_type() { - ChildType::ParentKeyId => { - sp_io::default_child_storage::storage_kill(child_info.storage_key(), limit) - }, - } -} - /// Partially clear the child storage of each key-value pair. /// /// # Limit @@ -181,22 +153,15 @@ pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageRe pub fn clear_storage( child_info: &ChildInfo, maybe_limit: Option, - _maybe_cursor: Option<&[u8]>, + maybe_cursor: Option<&[u8]>, ) -> MultiRemovalResults { - // TODO: Once the network has upgraded to include the new host functions, this code can be - // enabled. - // sp_io::default_child_storage::storage_kill(prefix, maybe_limit, maybe_cursor) - let r = match child_info.child_type() { - ChildType::ParentKeyId => { - sp_io::default_child_storage::storage_kill(child_info.storage_key(), maybe_limit) - }, - }; - use sp_io::KillStorageResult::*; - let (maybe_cursor, backend) = match r { - AllRemoved(db) => (None, db), - SomeRemaining(db) => (Some(child_info.storage_key().to_vec()), db), - }; - MultiRemovalResults { maybe_cursor, backend, unique: backend, loops: backend } + match child_info.child_type() { + ChildType::ParentKeyId => sp_io::default_child_storage::storage_kill( + child_info.storage_key(), + maybe_limit, + maybe_cursor, + ), + } } /// Ensure `key` has no explicit entry in storage. @@ -225,11 +190,9 @@ pub fn put_raw(child_info: &ChildInfo, key: &[u8], value: &[u8]) { } /// Calculate current child root value. -pub fn root(child_info: &ChildInfo, version: StateVersion) -> Vec { +pub fn root(child_info: &ChildInfo) -> Vec { match child_info.child_type() { - ChildType::ParentKeyId => { - sp_io::default_child_storage::root(child_info.storage_key(), version) - }, + ChildType::ParentKeyId => sp_io::default_child_storage::root(child_info.storage_key()), } } @@ -238,7 +201,7 @@ pub fn len(child_info: &ChildInfo, key: &[u8]) -> Option { match child_info.child_type() { ChildType::ParentKeyId => { let mut buffer = [0; 0]; - sp_io::default_child_storage::read(child_info.storage_key(), key, &mut buffer, 0) + sp_io::default_child_storage::read_exact(child_info.storage_key(), key, &mut buffer, 0) }, } } diff --git a/substrate/frame/support/src/storage/generator/double_map.rs b/substrate/frame/support/src/storage/generator/double_map.rs index b566819b5a1ac..d76123effb480 100644 --- a/substrate/frame/support/src/storage/generator/double_map.rs +++ b/substrate/frame/support/src/storage/generator/double_map.rs @@ -247,6 +247,7 @@ where previous_key: prefix, drain: false, closure: |_raw_key, mut raw_value| V::decode(&mut raw_value), + next_key: Vec::new(), phantom: Default::default(), } } @@ -373,6 +374,7 @@ where let mut key_material = G::Hasher2::reverse(raw_key_without_prefix); Ok((K2::decode(&mut key_material)?, V::decode(&mut raw_value)?)) }, + next_key: Vec::new(), phantom: Default::default(), } } @@ -396,6 +398,7 @@ where let mut key_material = G::Hasher2::reverse(raw_key_without_prefix); K2::decode(&mut key_material) }, + next_key: Vec::new(), } } @@ -427,6 +430,7 @@ where let k2 = K2::decode(&mut k2_material)?; Ok((k1, k2, V::decode(&mut raw_value)?)) }, + next_key: Vec::new(), phantom: Default::default(), } } @@ -450,6 +454,7 @@ where let k2 = K2::decode(&mut k2_material)?; Ok((k1, k2)) }, + next_key: Vec::new(), } } @@ -468,10 +473,9 @@ where fn translate Option>(mut f: F) { let prefix = G::prefix_hash().to_vec(); let mut previous_key = prefix.clone(); - while let Some(next) = - sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix)) - { - previous_key = next; + let mut next = Vec::new(); + while sp_io::storage::next_key(&previous_key, &mut next) && next.starts_with(&prefix) { + core::mem::swap(&mut previous_key, &mut next); let value = match unhashed::get::(&previous_key) { Some(value) => value, None => { diff --git a/substrate/frame/support/src/storage/generator/map.rs b/substrate/frame/support/src/storage/generator/map.rs index 2d1f6c9f73a29..2a4b1d2226dc2 100644 --- a/substrate/frame/support/src/storage/generator/map.rs +++ b/substrate/frame/support/src/storage/generator/map.rs @@ -92,6 +92,7 @@ where let mut key_material = G::Hasher::reverse(raw_key_without_prefix); Ok((K::decode(&mut key_material)?, V::decode(&mut raw_value)?)) }, + next_key: Vec::new(), phantom: Default::default(), } } @@ -114,6 +115,7 @@ where let mut key_material = G::Hasher::reverse(raw_key_without_prefix); K::decode(&mut key_material) }, + next_key: Vec::new(), } } @@ -148,8 +150,12 @@ where let prefix = G::prefix_hash().to_vec(); let previous_key = previous_key.unwrap_or_else(|| prefix.clone()); - let current_key = - sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix))?; + let mut current_key = Vec::new(); + if !sp_io::storage::next_key(&previous_key, &mut current_key) || + !current_key.starts_with(&prefix) + { + return None; + } let value = match unhashed::get::(¤t_key) { Some(value) => value, diff --git a/substrate/frame/support/src/storage/generator/nmap.rs b/substrate/frame/support/src/storage/generator/nmap.rs index d0f91d0e247d1..021da750a3764 100755 --- a/substrate/frame/support/src/storage/generator/nmap.rs +++ b/substrate/frame/support/src/storage/generator/nmap.rs @@ -221,6 +221,7 @@ where previous_key: prefix, drain: false, closure: |_raw_key, mut raw_value| V::decode(&mut raw_value), + next_key: Vec::new(), phantom: Default::default(), } } @@ -330,6 +331,7 @@ impl> let partial_key = K::decode_partial_key(raw_key_without_prefix)?; Ok((partial_key, V::decode(&mut raw_value)?)) }, + next_key: Vec::new(), phantom: Default::default(), } } @@ -356,6 +358,7 @@ impl> previous_key: prefix, drain: false, closure: K::decode_partial_key, + next_key: Vec::new(), } } @@ -394,6 +397,7 @@ impl> let (final_key, _) = K::decode_final_key(raw_key_without_prefix)?; Ok((final_key, V::decode(&mut raw_value)?)) }, + next_key: Vec::new(), phantom: Default::default(), } } @@ -412,6 +416,7 @@ impl> let (final_key, _) = K::decode_final_key(raw_key_without_prefix)?; Ok(final_key) }, + next_key: Vec::new(), } } @@ -424,10 +429,9 @@ impl> fn translate Option>(mut f: F) { let prefix = G::prefix_hash().to_vec(); let mut previous_key = prefix.clone(); - while let Some(next) = - sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix)) - { - previous_key = next; + let mut next = Vec::new(); + while sp_io::storage::next_key(&previous_key, &mut next) && next.starts_with(&prefix) { + core::mem::swap(&mut previous_key, &mut next); let value = match unhashed::get::(&previous_key) { Some(value) => value, None => { diff --git a/substrate/frame/support/src/storage/migration.rs b/substrate/frame/support/src/storage/migration.rs index 531f410303475..63c786d81d598 100644 --- a/substrate/frame/support/src/storage/migration.rs +++ b/substrate/frame/support/src/storage/migration.rs @@ -32,6 +32,7 @@ pub struct StorageIterator { prefix: Vec, previous_key: Vec, drain: bool, + next_key: Vec, _phantom: ::core::marker::PhantomData, } @@ -53,7 +54,13 @@ impl StorageIterator { prefix.extend_from_slice(&storage_prefix); prefix.extend_from_slice(suffix); let previous_key = prefix.clone(); - Self { prefix, previous_key, drain: false, _phantom: Default::default() } + Self { + prefix, + previous_key, + drain: false, + next_key: Vec::new(), + _phantom: Default::default(), + } } /// Mutate this iterator into a draining iterator; items iterated are removed from storage. @@ -68,24 +75,22 @@ impl Iterator for StorageIterator { fn next(&mut self) -> Option<(Vec, T)> { loop { - let maybe_next = sp_io::storage::next_key(&self.previous_key) - .filter(|n| n.starts_with(&self.prefix)); - break match maybe_next { - Some(next) => { - self.previous_key = next.clone(); - let maybe_value = frame_support::storage::unhashed::get::(&next); - match maybe_value { - Some(value) => { - if self.drain { - frame_support::storage::unhashed::kill(&next); - } - Some((self.previous_key[self.prefix.len()..].to_vec(), value)) - }, - None => continue, + if !sp_io::storage::next_key(&self.previous_key, &mut self.next_key) || + !self.next_key.starts_with(&self.prefix) + { + return None; + } + core::mem::swap(&mut self.previous_key, &mut self.next_key); + let maybe_value = frame_support::storage::unhashed::get::(&self.previous_key); + match maybe_value { + Some(value) => { + if self.drain { + frame_support::storage::unhashed::kill(&self.previous_key); } + return Some((self.previous_key[self.prefix.len()..].to_vec(), value)); }, - None => None, - }; + None => continue, + } } } } @@ -95,6 +100,7 @@ pub struct StorageKeyIterator { prefix: Vec, previous_key: Vec, drain: bool, + next_key: Vec, _phantom: ::core::marker::PhantomData<(K, T, H)>, } @@ -116,7 +122,13 @@ impl StorageKeyIterator { prefix.extend_from_slice(&storage_prefix); prefix.extend_from_slice(suffix); let previous_key = prefix.clone(); - Self { prefix, previous_key, drain: false, _phantom: Default::default() } + Self { + prefix, + previous_key, + drain: false, + next_key: Vec::new(), + _phantom: Default::default(), + } } /// Mutate this iterator into a draining iterator; items iterated are removed from storage. @@ -133,30 +145,29 @@ impl Iterator fn next(&mut self) -> Option<(K, T)> { loop { - let maybe_next = sp_io::storage::next_key(&self.previous_key) - .filter(|n| n.starts_with(&self.prefix)); - break match maybe_next { - Some(next) => { - self.previous_key = next.clone(); - let mut key_material = H::reverse(&next[self.prefix.len()..]); - match K::decode(&mut key_material) { - Ok(key) => { - let maybe_value = frame_support::storage::unhashed::get::(&next); - match maybe_value { - Some(value) => { - if self.drain { - frame_support::storage::unhashed::kill(&next); - } - Some((key, value)) - }, - None => continue, + if !sp_io::storage::next_key(&self.previous_key, &mut self.next_key) || + !self.next_key.starts_with(&self.prefix) + { + return None; + } + core::mem::swap(&mut self.previous_key, &mut self.next_key); + let mut key_material = H::reverse(&self.previous_key[self.prefix.len()..]); + match K::decode(&mut key_material) { + Ok(key) => { + let maybe_value = + frame_support::storage::unhashed::get::(&self.previous_key); + match maybe_value { + Some(value) => { + if self.drain { + frame_support::storage::unhashed::kill(&self.previous_key); } + return Some((key, value)); }, - Err(_) => continue, + None => continue, } }, - None => None, - }; + Err(_) => continue, + } } } } @@ -182,7 +193,14 @@ pub fn storage_iter_with_suffix( Ok((raw_key_without_prefix.to_vec(), value)) }; - PrefixIterator { prefix, previous_key, drain: false, closure, phantom: Default::default() } + PrefixIterator { + prefix, + previous_key, + drain: false, + closure, + next_key: Vec::new(), + phantom: Default::default(), + } } /// Construct iterator to iterate over map items in `module` for the map called `item`. @@ -215,7 +233,14 @@ pub fn storage_key_iter_with_suffix< let value = T::decode(&mut raw_value)?; Ok((key, value)) }; - PrefixIterator { prefix, previous_key, drain: false, closure, phantom: Default::default() } + PrefixIterator { + prefix, + previous_key, + drain: false, + closure, + next_key: Vec::new(), + phantom: Default::default(), + } } /// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`. @@ -376,6 +401,7 @@ pub fn move_prefix(from_prefix: &[u8], to_prefix: &[u8]) { previous_key: from_prefix.to_vec(), drain: true, closure: |key, value| Ok((key.to_vec(), value.to_vec())), + next_key: Vec::new(), phantom: Default::default(), }; diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs index 4e399a0d5510f..70e094c9588bb 100644 --- a/substrate/frame/support/src/storage/mod.rs +++ b/substrate/frame/support/src/storage/mod.rs @@ -1012,6 +1012,8 @@ pub struct PrefixIterator { /// Function that take `(raw_key_without_prefix, raw_value)` and decode `T`. /// `raw_key_without_prefix` is the raw storage key without the prefix iterated on. closure: fn(&[u8], &[u8]) -> Result, + /// Reusable scratch buffer for the next key fetched on each iteration. + next_key: Vec, phantom: core::marker::PhantomData, } @@ -1023,6 +1025,7 @@ impl PrefixIterator { previous_key: self.previous_key, drain: self.drain, closure: self.closure, + next_key: self.next_key, phantom: Default::default(), } } @@ -1058,6 +1061,7 @@ impl PrefixIterator { previous_key, drain: false, closure: decode_fn, + next_key: Vec::new(), phantom: Default::default(), } } @@ -1089,42 +1093,37 @@ impl Iterator for PrefixIterator Option { loop { - let maybe_next = sp_io::storage::next_key(&self.previous_key) - .filter(|n| n.starts_with(&self.prefix)); - break match maybe_next { - Some(next) => { - self.previous_key = next; - let raw_value = match unhashed::get_raw(&self.previous_key) { - Some(raw_value) => raw_value, - None => { - log::error!( - "next_key returned a key with no value at {:?}", - self.previous_key, - ); - continue; - }, - }; - if self.drain { - unhashed::kill(&self.previous_key); - OnRemoval::on_removal(&self.previous_key, &raw_value); - } - let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; - let item = match (self.closure)(raw_key_without_prefix, &raw_value[..]) { - Ok(item) => item, - Err(e) => { - log::error!( - "(key, value) failed to decode at {:?}: {:?}", - self.previous_key, - e, - ); - continue; - }, - }; - - Some(item) + if !sp_io::storage::next_key(&self.previous_key, &mut self.next_key) || + !self.next_key.starts_with(&self.prefix) + { + return None; + } + core::mem::swap(&mut self.previous_key, &mut self.next_key); + let raw_value = match unhashed::get_raw(&self.previous_key) { + Some(raw_value) => raw_value, + None => { + log::error!("next_key returned a key with no value at {:?}", self.previous_key,); + continue; + }, + }; + if self.drain { + unhashed::kill(&self.previous_key); + OnRemoval::on_removal(&self.previous_key, &raw_value); + } + let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; + let item = match (self.closure)(raw_key_without_prefix, &raw_value[..]) { + Ok(item) => item, + Err(e) => { + log::error!( + "(key, value) failed to decode at {:?}: {:?}", + self.previous_key, + e, + ); + continue; }, - None => None, }; + + return Some(item); } } } @@ -1140,6 +1139,8 @@ pub struct KeyPrefixIterator { /// Function that take `raw_key_without_prefix` and decode `T`. /// `raw_key_without_prefix` is the raw storage key without the prefix iterated on. closure: fn(&[u8]) -> Result, + /// Reusable scratch buffer for the next key fetched on each iteration. + next_key: Vec, } impl KeyPrefixIterator { @@ -1155,7 +1156,13 @@ impl KeyPrefixIterator { previous_key: Vec, decode_fn: fn(&[u8]) -> Result, ) -> Self { - KeyPrefixIterator { prefix, previous_key, drain: false, closure: decode_fn } + KeyPrefixIterator { + prefix, + previous_key, + drain: false, + closure: decode_fn, + next_key: Vec::new(), + } } /// Get the last key that has been iterated upon and return it. @@ -1185,26 +1192,24 @@ impl Iterator for KeyPrefixIterator { fn next(&mut self) -> Option { loop { - let maybe_next = sp_io::storage::next_key(&self.previous_key) - .filter(|n| n.starts_with(&self.prefix)); - - if let Some(next) = maybe_next { - self.previous_key = next; - if self.drain { - unhashed::kill(&self.previous_key); - } - let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; - - match (self.closure)(raw_key_without_prefix) { - Ok(item) => return Some(item), - Err(e) => { - log::error!("key failed to decode at {:?}: {:?}", self.previous_key, e); - continue; - }, - } + if !sp_io::storage::next_key(&self.previous_key, &mut self.next_key) || + !self.next_key.starts_with(&self.prefix) + { + return None; } + core::mem::swap(&mut self.previous_key, &mut self.next_key); + if self.drain { + unhashed::kill(&self.previous_key); + } + let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; - return None; + match (self.closure)(raw_key_without_prefix) { + Ok(item) => return Some(item), + Err(e) => { + log::error!("key failed to decode at {:?}: {:?}", self.previous_key, e); + continue; + }, + } } } } @@ -1226,6 +1231,8 @@ pub struct ChildTriePrefixIterator { /// Function that takes `(raw_key_without_prefix, raw_value)` and decode `T`. /// `raw_key_without_prefix` is the raw storage key without the prefix iterated on. closure: fn(&[u8], &[u8]) -> Result, + /// Reusable scratch buffer for the next key fetched on each iteration. + next_key: Vec, } impl ChildTriePrefixIterator { @@ -1256,6 +1263,7 @@ impl ChildTriePrefixIterator<(Vec, T)> { drain: false, fetch_previous_key: true, closure, + next_key: Vec::new(), } } } @@ -1285,6 +1293,7 @@ impl ChildTriePrefixIterator<(K, T)> { drain: false, fetch_previous_key: true, closure, + next_key: Vec::new(), } } } @@ -1294,49 +1303,43 @@ impl Iterator for ChildTriePrefixIterator { fn next(&mut self) -> Option { loop { - let maybe_next = if self.fetch_previous_key { + if self.fetch_previous_key { self.fetch_previous_key = false; - Some(self.previous_key.clone()) } else { - sp_io::default_child_storage::next_key( + if !sp_io::default_child_storage::next_key( self.child_info.storage_key(), &self.previous_key, - ) - .filter(|n| n.starts_with(&self.prefix)) + &mut self.next_key, + ) || !self.next_key.starts_with(&self.prefix) + { + return None; + } + core::mem::swap(&mut self.previous_key, &mut self.next_key); + } + let raw_value = match child::get_raw(&self.child_info, &self.previous_key) { + Some(raw_value) => raw_value, + None => { + log::error!("next_key returned a key with no value at {:?}", self.previous_key,); + continue; + }, }; - break match maybe_next { - Some(next) => { - self.previous_key = next; - let raw_value = match child::get_raw(&self.child_info, &self.previous_key) { - Some(raw_value) => raw_value, - None => { - log::error!( - "next_key returned a key with no value at {:?}", - self.previous_key, - ); - continue; - }, - }; - if self.drain { - child::kill(&self.child_info, &self.previous_key) - } - let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; - let item = match (self.closure)(raw_key_without_prefix, &raw_value[..]) { - Ok(item) => item, - Err(e) => { - log::error!( - "(key, value) failed to decode at {:?}: {:?}", - self.previous_key, - e, - ); - continue; - }, - }; - - Some(item) + if self.drain { + child::kill(&self.child_info, &self.previous_key) + } + let raw_key_without_prefix = &self.previous_key[self.prefix.len()..]; + let item = match (self.closure)(raw_key_without_prefix, &raw_value[..]) { + Ok(item) => item, + Err(e) => { + log::error!( + "(key, value) failed to decode at {:?}: {:?}", + self.previous_key, + e, + ); + continue; }, - None => None, }; + + return Some(item); } } } @@ -1426,6 +1429,7 @@ pub trait StoragePrefixedMap { previous_key: prefix.to_vec(), drain: false, closure: |_raw_key, mut raw_value| Value::decode(&mut raw_value), + next_key: Vec::new(), phantom: Default::default(), } } @@ -1446,10 +1450,9 @@ pub trait StoragePrefixedMap { fn translate_values Option>(mut f: F) { let prefix = Self::final_prefix(); let mut previous_key = prefix.clone().to_vec(); - while let Some(next) = - sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix)) - { - previous_key = next; + let mut next = Vec::new(); + while sp_io::storage::next_key(&previous_key, &mut next) && next.starts_with(&prefix) { + core::mem::swap(&mut previous_key, &mut next); let maybe_value = unhashed::get::(&previous_key); match maybe_value { Some(value) => match f(value) { @@ -1484,7 +1487,7 @@ pub trait StorageDecodeLength: private::Sealed + codec::DecodeLength { fn decode_len(key: &[u8]) -> Option { // `Compact` is 5 bytes in maximum. let mut data = [0u8; 5]; - let len = sp_io::storage::read(key, &mut data, 0)?; + let len = sp_io::storage::read_partial(key, &mut data, 0)?; let len = data.len().min(len as usize); ::len(&data[..len]).ok() } @@ -1507,7 +1510,7 @@ pub trait StorageDecodeNonDedupLength: private::Sealed + codec::DecodeLength { /// Returns `None` if the storage value does not exist or the decoding failed. fn decode_non_dedup_len(key: &[u8]) -> Option { let mut data = [0u8; 5]; - let len = sp_io::storage::read(key, &mut data, 0)?; + let len = sp_io::storage::read_partial(key, &mut data, 0)?; let len = data.len().min(len as usize); ::len(&data[..len]).ok() } diff --git a/substrate/frame/support/src/storage/storage_noop_guard.rs b/substrate/frame/support/src/storage/storage_noop_guard.rs index 2bf60d7c1c327..7456d4cc1afcb 100644 --- a/substrate/frame/support/src/storage/storage_noop_guard.rs +++ b/substrate/frame/support/src/storage/storage_noop_guard.rs @@ -45,7 +45,7 @@ pub struct StorageNoopGuard<'a> { impl<'a> Default for StorageNoopGuard<'a> { fn default() -> Self { Self { - storage_root: sp_io::storage::root(sp_runtime::StateVersion::V1), + storage_root: sp_io::storage::root(), error_message: "`StorageNoopGuard` detected an attempted storage change.", } } @@ -59,7 +59,7 @@ impl<'a> StorageNoopGuard<'a> { /// Creates a new [`StorageNoopGuard`] with a custom error message. pub fn from_error_message(error_message: &'a str) -> Self { - Self { storage_root: sp_io::storage::root(sp_runtime::StateVersion::V1), error_message } + Self { storage_root: sp_io::storage::root(), error_message } } /// Sets a custom error message for a [`StorageNoopGuard`]. @@ -75,12 +75,7 @@ impl<'a> Drop for StorageNoopGuard<'a> { if std::thread::panicking() { return; } - assert_eq!( - sp_io::storage::root(sp_runtime::StateVersion::V1), - self.storage_root, - "{}", - self.error_message, - ); + assert_eq!(sp_io::storage::root(), self.storage_root, "{}", self.error_message,); } } diff --git a/substrate/frame/support/src/storage/stream_iter.rs b/substrate/frame/support/src/storage/stream_iter.rs index ceba8f1c24a4f..e488466df03ff 100644 --- a/substrate/frame/support/src/storage/stream_iter.rs +++ b/substrate/frame/support/src/storage/stream_iter.rs @@ -214,7 +214,7 @@ impl core::iter::Iterator for ScaleContainerStreamIter { /// state for every access is too slow. const STORAGE_INPUT_BUFFER_CAPACITY: usize = 2 * 1024; -/// Implementation of [`codec::Input`] using [`sp_io::storage::read`]. +/// Implementation of [`codec::Input`] using [`sp_io::storage::read_partial`]. /// /// Keeps an internal buffer with a size of [`STORAGE_INPUT_BUFFER_CAPACITY`]. All read accesses /// are tried to be served by this buffer. If the buffer doesn't hold enough bytes to fulfill the @@ -241,7 +241,7 @@ impl StorageInput { } let (total_length, exists) = - if let Some(total_length) = sp_io::storage::read(&key, &mut buffer, 0) { + if let Some(total_length) = sp_io::storage::read_partial(&key, &mut buffer, 0) { (total_length, true) } else { (0, false) @@ -267,7 +267,7 @@ impl StorageInput { } if let Some(length_minus_offset) = - sp_io::storage::read(&self.key, &mut self.buffer[present_bytes..], self.offset) + sp_io::storage::read_partial(&self.key, &mut self.buffer[present_bytes..], self.offset) { let bytes_read = core::cmp::min(length_minus_offset as usize, self.buffer.len() - present_bytes); @@ -311,7 +311,7 @@ impl StorageInput { } if let Some(length_minus_offset) = - sp_io::storage::read(&self.key, &mut out_remaining, self.offset) + sp_io::storage::read_partial(&self.key, &mut out_remaining, self.offset) { if (length_minus_offset as usize) < out_remaining.len() { return Err("Not enough data to fill the buffer".into()); diff --git a/substrate/frame/support/src/storage/unhashed.rs b/substrate/frame/support/src/storage/unhashed.rs index 54a9138e0ac49..e20ca4e235e6f 100644 --- a/substrate/frame/support/src/storage/unhashed.rs +++ b/substrate/frame/support/src/storage/unhashed.rs @@ -96,15 +96,6 @@ pub fn kill(key: &[u8]) { sp_io::storage::clear(key); } -/// Ensure keys with the given `prefix` have no entries in storage. -#[deprecated = "Use `clear_prefix` instead"] -pub fn kill_prefix(prefix: &[u8], limit: Option) -> sp_io::KillStorageResult { - // TODO: Once the network has upgraded to include the new host functions, this code can be - // enabled. - // clear_prefix(prefix, limit).into() - sp_io::storage::clear_prefix(prefix, limit) -} - /// Partially clear the storage of all keys under a common `prefix`. /// /// # Limit @@ -140,28 +131,17 @@ pub fn kill_prefix(prefix: &[u8], limit: Option) -> sp_io::KillStorageResul pub fn clear_prefix( prefix: &[u8], maybe_limit: Option, - _maybe_cursor: Option<&[u8]>, + maybe_cursor: Option<&[u8]>, ) -> sp_io::MultiRemovalResults { - // TODO: Once the network has upgraded to include the new host functions, this code can be - // enabled. - // sp_io::storage::clear_prefix(prefix, maybe_limit, maybe_cursor) - use sp_io::{KillStorageResult::*, MultiRemovalResults}; - #[allow(deprecated)] - let (maybe_cursor, i) = match kill_prefix(prefix, maybe_limit) { - AllRemoved(i) => (None, i), - SomeRemaining(i) => (Some(prefix.to_vec()), i), - }; - MultiRemovalResults { maybe_cursor, backend: i, unique: i, loops: i } + sp_io::storage::clear_prefix(prefix, maybe_limit, maybe_cursor) } /// Returns `true` if the storage contains any key, which starts with a certain prefix, /// and is longer than said prefix. /// This means that a key which equals the prefix will not be counted. pub fn contains_prefixed_key(prefix: &[u8]) -> bool { - match sp_io::storage::next_key(prefix) { - Some(key) => key.starts_with(prefix), - None => false, - } + let mut key = Vec::new(); + sp_io::storage::next_key(prefix, &mut key) && key.starts_with(prefix) } /// Get a Vec of bytes from storage. diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 43811ee4174d4..264c5b456abc4 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -512,12 +512,8 @@ fn double_map_basic_insert_remove_remove_prefix_should_work() { // all in overlay assert!(matches!( DoubleMap::clear_prefix(&key1, u32::max_value(), None), - MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 2, loops: 0 } )); - // Note this is the incorrect answer (for now), since we are using v2 of - // `clear_prefix`. - // When we switch to v3, then this will become: - // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, assert!(matches!( DoubleMap::clear_prefix(&key1, u32::max_value(), None), MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } diff --git a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs index e8a92259d561d..69512e1ff2484 100644 --- a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs +++ b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs @@ -128,20 +128,22 @@ fn decode_storage_info( }; let mut errors = vec![]; - let mut next_key = Some(info.prefix.clone()); + let mut key = info.prefix.clone(); + let mut next_key = Vec::new(); loop { - match next_key { - Some(key) if key.starts_with(&info.prefix) => { - match decode_key(&key) { - Ok(bytes) => { - decoded += bytes; - }, - Err(e) => errors.push(e), - }; - next_key = sp_io::storage::next_key(&key); + if !key.starts_with(&info.prefix) { + break; + } + match decode_key(&key) { + Ok(bytes) => { + decoded += bytes; }, - _ => break, + Err(e) => errors.push(e), + }; + if !sp_io::storage::next_key(&key, &mut next_key) { + break; } + core::mem::swap(&mut key, &mut next_key); } if errors.is_empty() { diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 449c80eedd552..e3d37748f9a0a 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -2106,8 +2106,7 @@ impl Pallet { >::remove(to_remove); } - let version = T::Version::get().state_version(); - let storage_root = T::Hash::decode(&mut &sp_io::storage::root(version)[..]) + let storage_root = T::Hash::decode(&mut &sp_io::storage::root()[..]) .expect("Node is configured to use the same hash; qed"); HeaderFor::::new(number, extrinsics_root, storage_root, parent_hash, digest) @@ -2499,7 +2498,7 @@ impl Pallet { /// as a facility to reduce the potential for precalculating results. pub fn unique(entropy: impl Encode) -> [u8; 32] { let mut last = [0u8; 32]; - sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut last[..], 0); + sp_io::storage::read_exact(well_known_keys::INTRABLOCK_ENTROPY, &mut last[..], 0); let next = (b"frame_system::unique", entropy, last).using_encoded(blake2_256); sp_io::storage::set(well_known_keys::INTRABLOCK_ENTROPY, &next); next diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index ebf333e52864d..d1ad3c424638a 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -67,26 +67,26 @@ fn unique_datum_works() { let h1 = unique(b""); assert_eq!( 32, - sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + sp_io::storage::read_exact(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() ); let h2 = unique(b""); assert_eq!( 32, - sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + sp_io::storage::read_exact(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() ); assert_ne!(h1, h2); let h3 = unique(b"Hello"); assert_eq!( 32, - sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + sp_io::storage::read_exact(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() ); assert_ne!(h2, h3); let h4 = unique(b"Hello"); assert_eq!( 32, - sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + sp_io::storage::read_exact(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() ); assert_ne!(h3, h4); diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index 006bbc059f915..64e5a866f46d3 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -24,6 +24,7 @@ scale-info = { features = ["derive"], workspace = true } sp-api-proc-macro = { workspace = true } sp-core = { workspace = true } sp-externalities = { optional = true, workspace = true } +sp-io = { workspace = true } sp-metadata-ir = { optional = true, workspace = true } sp-runtime = { workspace = true } sp-runtime-interface = { workspace = true } @@ -46,6 +47,7 @@ std = [ "sp-core/std", "sp-externalities", "sp-externalities?/std", + "sp-io/std", "sp-metadata-ir?/std", "sp-runtime-interface/std", "sp-runtime/std", diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 23d8131304d15..9b00a5333f15c 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -234,13 +234,13 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { #( #attrs )* #[no_mangle] #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))] - pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { - let mut #input = if input_len == 0 { - &[0u8; 0] - } else { - unsafe { - ::core::slice::from_raw_parts(input_data, input_len) - } + pub unsafe extern fn #fn_name(input_len: usize) -> u64 { + let mut input_vec = #c::__private::vec![0; input_len]; + let mut #input = { + if input_len != 0 { + #c::sp_io::input::read(&mut input_vec[..]); + } + &input_vec[..] }; #c::init_runtime_logger(); diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index 698e5a23ac67f..0ed3d3a63f054 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -99,6 +99,7 @@ pub mod __private { pub use sp_core::offchain; #[cfg(not(feature = "std"))] pub use sp_core::to_substrate_wasm_fn_return_value; + pub use sp_io; #[cfg(feature = "frame-metadata")] pub use sp_metadata_ir::{self as metadata_ir, frame_metadata as metadata}; pub use sp_runtime::{ diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 892990b0e293a..2ec5e01d5464d 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -27,6 +27,7 @@ bip39 = { workspace = true, default-features = false, features = ["alloc"] } bitflags = { workspace = true } bounded-collections = { workspace = true, features = ["scale-codec"] } bs58 = { optional = true, workspace = true } +byte-slice-cast = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } futures = { optional = true, workspace = true } hash-db = { workspace = true } @@ -90,6 +91,7 @@ std = [ "blake2/std", "bounded-collections/std", "bs58/std", + "byte-slice-cast/std", "codec/std", "ed25519-zebra/std", "full_crypto", diff --git a/substrate/primitives/core/src/crypto_bytes.rs b/substrate/primitives/core/src/crypto_bytes.rs index 0925c42b35642..0fd3d967e70d3 100644 --- a/substrate/primitives/core/src/crypto_bytes.rs +++ b/substrate/primitives/core/src/crypto_bytes.rs @@ -26,6 +26,8 @@ use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; use core::marker::PhantomData; use scale_info::TypeInfo; +use byte_slice_cast::FromByteSlice; + #[cfg(feature = "serde")] use crate::crypto::Ss58Codec; #[cfg(feature = "serde")] @@ -160,6 +162,46 @@ impl core::ops::Deref for CryptoBytes { } } +// SAFETY: CryptoBytes is #[repr(transparent)] over [u8; N] so it can be safely +// instantiated from a byte slice of the corresponding lenght +unsafe impl FromByteSlice for CryptoBytes { + fn from_byte_slice + ?Sized>( + slice: &U, + ) -> Result<&[Self], byte_slice_cast::Error> { + let slice = slice.as_ref(); + if slice.is_empty() { + return Ok(&[]); + } + if slice.len() % N != 0 { + return Err(byte_slice_cast::Error::LengthMismatch { + dst_type: "CryptoBytes", + src_slice_size: slice.len(), + dst_type_size: N, + }); + } + unsafe { Ok(core::slice::from_raw_parts(slice.as_ptr().cast::(), slice.len() / N)) } + } + + fn from_mut_byte_slice + ?Sized>( + slice: &mut U, + ) -> Result<&mut [Self], byte_slice_cast::Error> { + let slice = slice.as_mut(); + if slice.is_empty() { + return Ok(&mut []); + } + if slice.len() % N != 0 { + return Err(byte_slice_cast::Error::LengthMismatch { + dst_type: "CryptoBytes", + src_slice_size: slice.len(), + dst_type_size: N, + }); + } + unsafe { + Ok(core::slice::from_raw_parts_mut(slice.as_mut_ptr().cast::(), slice.len() / N)) + } + } +} + impl CryptoBytes { /// Construct from raw array. pub fn from_raw(inner: [u8; N]) -> Self { diff --git a/substrate/primitives/core/src/lib.rs b/substrate/primitives/core/src/lib.rs index 716c45267aa48..a86fe1fb64ddc 100644 --- a/substrate/primitives/core/src/lib.rs +++ b/substrate/primitives/core/src/lib.rs @@ -46,6 +46,12 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "serde")] pub use impl_serde::serialize as bytes; +// This re-export was removed in https://github.com/paritytech/polkadot-sdk/pull/12158 but it +// breaks `try-runtime-cli` in-CI build introduced by RFC-145 implementation +// https://github.com/paritytech/polkadot-sdk/pull/8641. May be safely removed after +// `try-runtime-cli` catches up. +pub use sp_crypto_hashing::twox_128; + pub mod const_hex2array; pub mod crypto; pub mod hexdisplay; diff --git a/substrate/primitives/core/src/testing.rs b/substrate/primitives/core/src/testing.rs index 378b3416db7c5..c1e94539f1a76 100644 --- a/substrate/primitives/core/src/testing.rs +++ b/substrate/primitives/core/src/testing.rs @@ -78,6 +78,83 @@ macro_rules! wasm_export_functions { } )* }; + (@IMPL + fn $name:ident ( + $( $arg_name:ident: $arg_ty:ty ),* + ) { $( $fn_impl:tt )* } + ) => { + #[no_mangle] + #[allow(unreachable_code)] + #[cfg(not(feature = "std"))] + pub fn $name(input_len: usize) -> u64 { + let mut input_buf = ::alloc::vec![0u8; input_len]; + if input_len > 0 { + sp_io::input::read(&mut input_buf[..]); + } + let input: &[u8] = &input_buf[..]; + + { + let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode( + &mut &input[..], + ).expect("Input data is correctly encoded"); + + (|| { $( $fn_impl )* })() + } + + $crate::to_substrate_wasm_fn_return_value(&()) + } + }; + (@IMPL + fn $name:ident ( + $( $arg_name:ident: $arg_ty:ty ),* + ) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* } + ) => { + #[no_mangle] + #[allow(unreachable_code)] + #[cfg(not(feature = "std"))] + pub fn $name(input_len: usize) -> u64 { + let mut input_buf = ::alloc::vec![0u8; input_len]; + if input_len > 0 { + sp_io::input::read(&mut input_buf[..]); + } + let input: &[u8] = &input_buf[..]; + + let output $( : $ret_ty )? = { + let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode( + &mut &input[..], + ).expect("Input data is correctly encoded"); + + (|| { $( $fn_impl )* })() + }; + + $crate::to_substrate_wasm_fn_return_value(&output) + } + }; +} + +/// Same as [`wasm_export_functions`] but generates V1 entry points that receive input data +/// via a host-provided pointer. Use this for tests that exercise host-side allocation +/// (e.g. `AllocateAndReturn*` marshalling strategies). +/// +/// V1 entry point signature: `fn(input_data: *mut u8, input_len: usize) -> u64` +#[macro_export] +macro_rules! wasm_export_functions_v1 { + ( + $( + fn $name:ident ( + $( $arg_name:ident: $arg_ty:ty ),* $(,)? + ) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* } + )* + ) => { + $( + $crate::wasm_export_functions_v1! { + @IMPL + fn $name ( + $( $arg_name: $arg_ty ),* + ) $( -> $ret_ty )? { $( $fn_impl )* } + } + )* + }; (@IMPL fn $name:ident ( $( $arg_name:ident: $arg_ty:ty ),* @@ -90,9 +167,7 @@ macro_rules! wasm_export_functions { let input: &[u8] = if input_len == 0 { &[0u8; 0] } else { - unsafe { - ::core::slice::from_raw_parts(input_data, input_len) - } + unsafe { ::core::slice::from_raw_parts(input_data, input_len) } }; { @@ -118,9 +193,7 @@ macro_rules! wasm_export_functions { let input: &[u8] = if input_len == 0 { &[0u8; 0] } else { - unsafe { - ::core::slice::from_raw_parts(input_data, input_len) - } + unsafe { ::core::slice::from_raw_parts(input_data, input_len) } }; let output $( : $ret_ty )? = { diff --git a/substrate/primitives/externalities/src/lib.rs b/substrate/primitives/externalities/src/lib.rs index a543b6758ee4f..e0fea15326a4e 100644 --- a/substrate/primitives/externalities/src/lib.rs +++ b/substrate/primitives/externalities/src/lib.rs @@ -52,7 +52,7 @@ pub enum Error { } /// Results concerning an operation to remove many keys. -#[derive(codec::Encode, codec::Decode)] +#[derive(codec::Encode, codec::Decode, Default)] #[must_use] pub struct MultiRemovalResults { /// A continuation cursor which, if `Some` must be provided to the subsequent removal call. @@ -190,12 +190,17 @@ pub trait Externalities: ExtensionStore { /// Set or clear a child storage entry. fn place_child_storage(&mut self, child_info: &ChildInfo, key: Vec, value: Option>); + /// Set the runtime's state version that subsequent calls to `storage_root` and + /// `child_storage_root` will use to compute the trie layout. The default implementation is a + /// no-op for externalities that don't compute roots. + fn set_runtime_state_version(&mut self, _state_version: StateVersion) {} + /// Get the trie root of the current storage map. /// /// This will also update all child storage keys in the top-level storage map. /// /// The returned hash is defined by the `Block` and is SCALE encoded. - fn storage_root(&mut self, state_version: StateVersion) -> Vec; + fn storage_root(&mut self) -> Vec; /// Get the trie root of a child storage map. /// @@ -203,11 +208,7 @@ pub trait Externalities: ExtensionStore { /// /// If the storage root equals the default hash as defined by the trie, the key in the top-level /// storage map will be removed. - fn child_storage_root( - &mut self, - child_info: &ChildInfo, - state_version: StateVersion, - ) -> Vec; + fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec; /// Append storage item. /// @@ -248,6 +249,16 @@ pub trait Externalities: ExtensionStore { unimplemented!("storage_renew_transaction_index"); } + /// Store the last cursor of a storage operation. + fn store_last_cursor(&mut self, _cursor: &[u8]) { + unimplemented!("store_last_cursor"); + } + + /// Take the last cursor of a storage operation. + fn take_last_cursor(&mut self) -> Option> { + unimplemented!("take_last_cursor"); + } + /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// Benchmarking related functionality and shouldn't be used anywhere else! /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 38ff09e0cdf09..903bf73cddc4e 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -18,6 +18,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +anyhow = { workspace = true } +byte-slice-cast = { workspace = true } bytes = { workspace = true } codec = { features = ["bytes"], workspace = true } secp256k1 = { features = ["global-context", "recovery"], optional = true, workspace = true, default-features = true } @@ -29,9 +31,13 @@ sp-runtime-interface = { workspace = true } sp-state-machine = { optional = true, workspace = true } sp-tracing = { workspace = true } sp-trie = { optional = true, workspace = true } +strum = { features = ["derive"], workspace = true } tracing = { workspace = true } tracing-core = { workspace = true } +[target.'cfg(substrate_runtime)'.dependencies] +picoalloc = { workspace = true } + [target.'cfg(not(substrate_runtime))'.dependencies] sp-keystore = { workspace = true, default-features = false } sp-trie = { workspace = true, default-features = false } @@ -54,6 +60,8 @@ rustversion = { workspace = true } [features] default = ["std"] std = [ + "anyhow/std", + "byte-slice-cast/std", "bytes/std", "codec/std", "ed25519-dalek/std", @@ -69,6 +77,7 @@ std = [ "sp-state-machine/std", "sp-tracing/std", "sp-trie/std", + "strum/std", "tracing-core/std", "tracing/std", ] diff --git a/substrate/primitives/io/src/global_alloc.rs b/substrate/primitives/io/src/global_alloc.rs deleted file mode 100644 index 4d41bd5832afd..0000000000000 --- a/substrate/primitives/io/src/global_alloc.rs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 core::{ - alloc::{GlobalAlloc, Layout}, - ptr, -}; - -/// The type used to store the offset between the real pointer and the returned pointer. -type Offset = u16; - -/// The length of [`Offset`]. -const OFFSET_LENGTH: usize = core::mem::size_of::(); - -/// Allocator used by Substrate from within the runtime. -/// -/// The allocator needs to align the returned pointer to given layout. We assume that on the host -/// side the freeing-bump allocator is used with a fixed alignment of `8` and a `HEADER_SIZE` of -/// `8`. The freeing-bump allocator is storing the header in the 8 bytes before the actual pointer -/// returned by `alloc`. The problem is that the runtime not only sees pointers allocated by this -/// `RuntimeAllocator`, but also pointers allocated by the host. The header is stored as a -/// little-endian `u64`. The allocation header consists of 8 bytes. The first four bytes (as written -/// in memory) are used to store the order of the allocation (or the link to the next slot, if -/// unallocated). Then the least significant bit of the next byte determines whether a given slot is -/// occupied or free, and the last three bytes are unused. -/// -/// The `RuntimeAllocator` aligns the pointer to the required alignment before returning it to the -/// user code. As we are assuming the freeing-bump allocator that already aligns by `8` by default, -/// we only need to take care of alignments above `8`. The offset is stored in two bytes before the -/// pointer that we return to the user. Depending on the alignment, we may write into the header, -/// but given the assumptions above this should be no problem. -struct RuntimeAllocator; - -#[global_allocator] -static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; - -unsafe impl GlobalAlloc for RuntimeAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let align = layout.align(); - let size = layout.size(); - - // Allocate for the required size, plus a potential alignment. - // - // As the host side already aligns the pointer by `8`, we only need to account for any - // excess. - let ptr = crate::allocator::malloc((size + align.saturating_sub(8)) as u32); - - // Calculate the required alignment. - let ptr_offset = ptr.align_offset(align); - - // Should never happen, but just to be sure. - if ptr_offset > u16::MAX as usize || ptr.is_null() { - return ptr::null_mut(); - } - - // Align the pointer. - let ptr = ptr.add(ptr_offset); - - unsafe { - (ptr.sub(OFFSET_LENGTH) as *mut Offset).write_unaligned(ptr_offset as Offset); - } - - ptr - } - - unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { - let offset = unsafe { (ptr.sub(OFFSET_LENGTH) as *const Offset).read_unaligned() }; - - crate::allocator::free(ptr.sub(offset as usize)) - } -} diff --git a/substrate/primitives/io/src/global_alloc_riscv.rs b/substrate/primitives/io/src/global_alloc_riscv.rs new file mode 100644 index 0000000000000..b19846f078d20 --- /dev/null +++ b/substrate/primitives/io/src/global_alloc_riscv.rs @@ -0,0 +1,50 @@ +// 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 core::alloc::{GlobalAlloc, Layout}; + +#[polkavm_derive::polkavm_import] +extern "C" { + /// Grows the heap by `size` bytes. Returns the previous heap end address, + /// or zero if the allocation failed. When called with `size == 0`, returns + /// the current heap end without growing. + fn grow_heap(size: usize) -> usize; +} + +/// A basic leaking allocator backed by the `grow_heap` host call. +/// +/// This is a temporary shim: `sbrk` was removed from the `jam_v1` instruction +/// set (GP 0.8.0) and replaced with a `grow_heap` host call. A proper +/// implementation will be provided later. +struct LeakingAllocator; + +#[global_allocator] +static ALLOCATOR: LeakingAllocator = LeakingAllocator; + +unsafe impl GlobalAlloc for LeakingAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let pointer = unsafe { grow_heap(0) }; + let padding = (-(pointer as isize)) as usize & (layout.align() - 1); + let size = layout.size().wrapping_add(padding); + if unsafe { grow_heap(size) } == 0 { + return core::ptr::null_mut(); + } + (pointer + padding) as *mut u8 + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} diff --git a/substrate/primitives/io/src/global_alloc_wasm.rs b/substrate/primitives/io/src/global_alloc_wasm.rs new file mode 100644 index 0000000000000..04105a166c30e --- /dev/null +++ b/substrate/primitives/io/src/global_alloc_wasm.rs @@ -0,0 +1,164 @@ +// 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 core::{ + alloc::{GlobalAlloc, Layout}, + arch::wasm32, + cell::UnsafeCell, + ptr::NonNull, +}; + +/// Allocator used by Substrate from within the runtime. +struct RuntimeAllocator; + +#[global_allocator] +static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; + +const WASM_PAGE_SIZE: usize = 64 * 1024; + +extern "C" { + static __heap_base: u8; +} + +#[inline(always)] +fn aligned_heap_base() -> *mut u8 { + // SAFETY: Wasmtime must export the symbol at correct address (end of data segment) + let base = unsafe { &__heap_base as *const u8 as usize }; + ((base + 31) & !31) as *mut u8 +} + +impl picoalloc::Env for RuntimeAllocator { + fn total_space(&self) -> picoalloc::Size { + // Compute the maximum virtual space available for the heap. + // We do not pre-grow memory here; we only advertise a virtual cap. Actual memory + // is grown on demand in `expand_memory_until` and will be clamped by the VM's + // configured maximum. + let base = aligned_heap_base() as usize; + // Keep the end strictly below 4GiB and aligned to 32 bytes. + let end = usize::MAX & !31; + if base >= end { + return picoalloc::Size::from_bytes_usize(0) + .expect("Conversion from u32 to Size never fails on wasm32"); + } + let total = (end - base) & !31; + picoalloc::Size::from_bytes_usize(total) + .expect("Conversion from u32 to Size never fails on wasm32") + } + + unsafe fn allocate_address_space(&mut self) -> *mut u8 { + aligned_heap_base() + } + + unsafe fn expand_memory_until(&mut self, base: *mut u8, size: picoalloc::Size) -> bool { + let base_offset = base as usize; + let Some(requested_end) = base_offset.checked_add(size.bytes() as usize) else { + return false; + }; + + let current_pages = wasm32::memory_size(0) as usize; + let current_end = current_pages * WASM_PAGE_SIZE; + + if requested_end <= current_end { + return true; + } + + let grow_bytes = requested_end - current_end; + let grow_pages = grow_bytes.div_ceil(WASM_PAGE_SIZE); + + wasm32::memory_grow(0, grow_pages) != usize::MAX + } + + unsafe fn free_address_space(&mut self, _base: *mut u8) {} +} + +/// The local allocator used to manage the local heap. +struct LocalAllocator(UnsafeCell>); + +// SAFETY: This is runtime-only, and runtimes are single-threaded, so this is safe. +unsafe impl Send for LocalAllocator {} + +// SAFETY: This is runtime-only, and runtimes are single-threaded, so this is safe. +unsafe impl Sync for LocalAllocator {} + +static LOCAL_ALLOCATOR: LocalAllocator = + LocalAllocator(UnsafeCell::new(picoalloc::Allocator::new(RuntimeAllocator))); + +fn local_allocator() -> &'static mut picoalloc::Allocator { + // SAFETY: This is only called when allocating memory, and the allocator + // doesn't trigger itself recursively, so only a single + // &mut will ever exist at the same time. + unsafe { &mut *LOCAL_ALLOCATOR.0.get() } +} + +unsafe impl GlobalAlloc for RuntimeAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // These should never fail, but let's do proper error checking anyway. + let Some(align) = picoalloc::Size::from_bytes_usize(layout.align()) else { + return core::ptr::null_mut(); + }; + + let Some(size) = picoalloc::Size::from_bytes_usize(layout.size()) else { + return core::ptr::null_mut(); + }; + + if let Some(pointer) = local_allocator().alloc(align, size) { + pointer.as_ptr() + } else { + core::ptr::null_mut() + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { + // SAFETY: Pointers only come from the local heap. + unsafe { local_allocator().free(NonNull::new_unchecked(ptr)) } + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let Some(align) = picoalloc::Size::from_bytes_usize(layout.align()) else { + return core::ptr::null_mut(); + }; + + let Some(size) = picoalloc::Size::from_bytes_usize(layout.size()) else { + return core::ptr::null_mut(); + }; + + if let Some(pointer) = local_allocator().alloc_zeroed(align, size) { + return pointer.as_ptr(); + } else { + core::ptr::null_mut() + } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let Some(align) = picoalloc::Size::from_bytes_usize(layout.align()) else { + return core::ptr::null_mut(); + }; + + let Some(new_size_s) = picoalloc::Size::from_bytes_usize(new_size) else { + return core::ptr::null_mut(); + }; + + // SAFETY: Pointers only come from the local heap. + if let Some(pointer) = + unsafe { local_allocator().realloc(NonNull::new_unchecked(ptr), align, new_size_s) } + { + pointer.as_ptr() + } else { + core::ptr::null_mut() + } + } +} diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 7a8fbb7da0166..d5c710eb00bda 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -79,7 +79,9 @@ extern crate alloc; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; + +use strum::{EnumCount, FromRepr}; #[cfg(not(substrate_runtime))] use tracing; @@ -115,9 +117,11 @@ use sp_trie::{LayoutV0, LayoutV1, TrieConfiguration}; use sp_runtime_interface::{ pass_by::{ - AllocateAndReturnByCodec, AllocateAndReturnFatPointer, AllocateAndReturnPointer, PassAs, - PassFatPointerAndDecode, PassFatPointerAndDecodeSlice, PassFatPointerAndRead, - PassFatPointerAndReadWrite, PassPointerAndRead, PassPointerAndReadCopy, ReturnAs, + AllocateAndReturnByCodec, AllocateAndReturnFatPointer, AllocateAndReturnPointer, + ConvertAndPassAs, ConvertAndReturnAs, PassAs, PassFatPointerAndDecode, + PassFatPointerAndDecodeSlice, PassFatPointerAndRead, PassFatPointerAndReadWrite, + PassFatPointerAndWrite, PassOptionalFatPointerAndRead, PassPointerAndRead, + PassPointerAndReadCopy, PassPointerAndWrite, ReturnAs, }, runtime_interface, Pointer, }; @@ -135,8 +139,11 @@ use sp_externalities::{Externalities, ExternalitiesExt}; pub use sp_externalities::MultiRemovalResults; -#[cfg(all(not(feature = "disable_allocator"), substrate_runtime))] -mod global_alloc; +#[cfg(all(not(feature = "disable_allocator"), substrate_runtime, target_family = "wasm"))] +mod global_alloc_wasm; + +#[cfg(all(not(feature = "disable_allocator"), substrate_runtime, target_arch = "riscv64"))] +mod global_alloc_riscv; #[cfg(not(substrate_runtime))] const LOG_TARGET: &str = "runtime::io"; @@ -152,6 +159,95 @@ pub enum EcdsaVerifyError { BadSignature, } +// The FFI representation of EcdsaVerifyError. +#[derive(EnumCount, FromRepr)] +#[repr(i16)] +#[allow(missing_docs)] +pub enum RIEcdsaVerifyError { + BadRS = -1_i16, + BadV = -2_i16, + BadSignature = -3_i16, +} + +impl From for i64 { + fn from(error: RIEcdsaVerifyError) -> Self { + error as i64 + } +} + +impl TryFrom for RIEcdsaVerifyError { + type Error = (); + fn try_from(value: i64) -> Result { + let value: i16 = value.try_into().map_err(|_| ())?; + RIEcdsaVerifyError::from_repr(value).ok_or(()) + } +} + +impl From for RIEcdsaVerifyError { + fn from(error: EcdsaVerifyError) -> Self { + match error { + EcdsaVerifyError::BadRS => RIEcdsaVerifyError::BadRS, + EcdsaVerifyError::BadV => RIEcdsaVerifyError::BadV, + EcdsaVerifyError::BadSignature => RIEcdsaVerifyError::BadSignature, + } + } +} + +impl From for EcdsaVerifyError { + fn from(error: RIEcdsaVerifyError) -> Self { + match error { + RIEcdsaVerifyError::BadRS => EcdsaVerifyError::BadRS, + RIEcdsaVerifyError::BadV => EcdsaVerifyError::BadV, + RIEcdsaVerifyError::BadSignature => EcdsaVerifyError::BadSignature, + } + } +} + +// The FFI representation of HttpError. +#[derive(EnumCount, FromRepr)] +#[repr(i16)] +#[allow(missing_docs)] +pub enum RIHttpError { + DeadlineReached = -1_i16, + IoError = -2_i16, + Invalid = -3_i16, +} + +impl From for i64 { + fn from(error: RIHttpError) -> Self { + error as i64 + } +} + +impl TryFrom for RIHttpError { + type Error = (); + + fn try_from(value: i64) -> Result { + let value: i16 = value.try_into().map_err(|_| ())?; + RIHttpError::from_repr(value).ok_or(()) + } +} + +impl From for RIHttpError { + fn from(error: HttpError) -> Self { + match error { + HttpError::DeadlineReached => RIHttpError::DeadlineReached, + HttpError::IoError => RIHttpError::IoError, + HttpError::Invalid => RIHttpError::Invalid, + } + } +} + +impl From for HttpError { + fn from(error: RIHttpError) -> Self { + match error { + RIHttpError::DeadlineReached => HttpError::DeadlineReached, + RIHttpError::IoError => HttpError::IoError, + RIHttpError::Invalid => HttpError::Invalid, + } + } +} + /// The outcome of calling `storage_kill`. Returned value is the number of storage items /// removed from the backend from making the `storage_kill` call. #[derive(Encode, Decode)] @@ -176,10 +272,350 @@ impl From for KillStorageResult { } } +/// Storage iteration counters +#[repr(C)] +#[derive(Default)] +pub struct StorageIterations { + /// The number of backend iterations. + pub backend: u32, + /// The number of unique iterations. + pub unique: u32, + /// The number of loops. + pub loops: u32, +} + +impl AsRef<[u8]> for StorageIterations { + fn as_ref(&self) -> &[u8] { + #[cfg(target_endian = "big")] + compile_error!("StorageIterations only supports little-endian architectures"); + + // SAFETY: The layout of this type is the same as for [u32; 3] and all the possible byte + // sequences are valid for this type so casting it from and to a byte slice is safe. + // However, the data may become corrupted when copied if host and runtime have different + // endianness, so that is checked statically. + unsafe { + core::slice::from_raw_parts( + (&raw const *self).cast::(), + core::mem::size_of::(), + ) + } + } +} + +impl AsMut<[u8]> for StorageIterations { + fn as_mut(&mut self) -> &mut [u8] { + #[cfg(target_endian = "big")] + compile_error!("StorageIterations only supports little-endian architectures"); + + // SAFETY: The layout of this type is the same as for [u32; 3] and all the possible byte + // sequences are valid for this type so casting it from and to a byte slice is safe. + // However, the data may become corrupted when copied if host and runtime have different + // endianness, so that is checked statically. + unsafe { + core::slice::from_raw_parts_mut( + self as *mut Self as *mut u8, + core::mem::size_of::(), + ) + } + } +} + +/// Defines a `#[repr(transparent)]` newtype over a fixed-size byte array with `Default`, +/// `AsRef<[u8]>`, and `AsMut<[u8]>` implementations. +macro_rules! define_byte_array_type { + ($(#[$meta:meta])* $vis:vis struct $name:ident(pub [u8; $size:expr])) => { + $(#[$meta])* + #[repr(transparent)] + $vis struct $name(pub [u8; $size]); + + impl Default for $name { + fn default() -> Self { + Self([0; $size]) + } + } + + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for $name { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + }; +} + +define_byte_array_type! { + /// Wrapper type for 512-bit hashes. + pub struct Hash512(pub [u8; 64]) +} + +define_byte_array_type! { + /// Wrapper type for 512-bit pubkeys. + pub struct Pubkey512(pub [u8; 64]) +} + +define_byte_array_type! { + /// A workaround wrapper type for 264-bit values (`[u8; 33]`) not implementing `Default`. + pub struct Pubkey264(pub [u8; 33]) +} + +define_byte_array_type! { + /// Represents an opaque network peer ID. + pub struct NetworkPeerId(pub [u8; 38]) +} + +trait IntoI64: Into { + const MAX: i64; +} + +impl IntoI64 for u8 { + const MAX: i64 = u8::MAX as i64; +} +impl IntoI64 for u16 { + const MAX: i64 = u16::MAX as i64; +} +impl IntoI64 for u32 { + const MAX: i64 = u32::MAX as i64; +} + +/// A wrapper around `Option` for the FFI marshalling. +/// +/// Used to return less-than-64-bit passed as `i64` through the FFI boundary. `-1_i64` is used to +/// represent `None`. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct RIIntOption(Option); + +impl From> for Option { + fn from(r: RIIntOption) -> Self { + r.0 + } +} + +impl From> for RIIntOption { + fn from(r: Option) -> Self { + Self(r) + } +} + +impl From> for i64 { + fn from(r: RIIntOption) -> Self { + match r.0 { + Some(value) => value.into(), + None => -1, + } + } +} + +impl + IntoI64> TryFrom for RIIntOption { + type Error = (); + + fn try_from(value: i64) -> Result { + if value == -1 { + Ok(RIIntOption(None)) + } else if value >= 0 && value <= T::MAX.into() { + Ok(RIIntOption(Some(value.try_into().map_err(|_| ())?))) + } else { + // Invalid FFI value (e.g., -2, or too large for T). + // `ConvertAndReturnAs` will panic when `TryFrom` returns an `Err`, which is the correct + // behavior here. + Err(()) + } + } +} + +/// Used to return less-than-64-bit value passed as `i64` through the FFI boundary. +/// Negative values are used to represent error variants. +pub enum RIIntResult { + /// Successful result + Ok(R), + /// Error result + Err(E), +} + +impl From> for RIIntResult +where + R: From, + E: From, +{ + fn from(result: Result) -> Self { + match result { + Ok(value) => Self::Ok(value.into()), + Err(error) => Self::Err(error.into()), + } + } +} + +impl From> for Result +where + OR: From, + OE: From, +{ + fn from(result: RIIntResult) -> Self { + match result { + RIIntResult::Ok(value) => Ok(value.into()), + RIIntResult::Err(error) => Err(error.into()), + } + } +} + +/// Represents a void successful result (always 0 in FFI) +pub struct VoidResult; + +impl IntoI64 for VoidResult { + const MAX: i64 = 0; +} + +impl From for u32 { + fn from(_: VoidResult) -> Self { + 0 + } +} + +impl From for VoidResult { + fn from(_: u32) -> Self { + VoidResult + } +} + +impl From<()> for VoidResult { + fn from(_: ()) -> Self { + VoidResult + } +} + +impl From for () { + fn from(_: VoidResult) -> Self { + () + } +} + +impl From for i64 { + fn from(_: VoidResult) -> Self { + 0 + } +} + +impl TryFrom for VoidResult { + type Error = (); + + fn try_from(value: i64) -> Result { + if value == 0 { + Ok(VoidResult) + } else { + Err(()) + } + } +} + +/// Represents a void error (always -1 in FFI) +pub struct VoidError; + +impl strum::EnumCount for VoidError { + const COUNT: usize = 1; +} + +impl From for i64 { + fn from(_: VoidError) -> Self { + -1 + } +} + +impl From for () { + fn from(_: VoidError) -> Self { + () + } +} + +impl From<()> for VoidError { + fn from(_: ()) -> Self { + VoidError + } +} + +impl TryFrom for VoidError { + type Error = (); + + fn try_from(value: i64) -> Result { + if value == -1 { + Ok(VoidError) + } else { + Err(()) + } + } +} + +impl + IntoI64, E: Into + strum::EnumCount> TryFrom> for i64 { + type Error = (); + + fn try_from(result: RIIntResult) -> Result { + match result { + RIIntResult::Ok(value) => Ok(value.into()), + RIIntResult::Err(e) => { + let error_code: i64 = e.into(); + if error_code < 0 && error_code >= -(E::COUNT as i64) { + Ok(error_code) + } else { + Err(()) + } + }, + } + } +} + +impl + IntoI64, E: TryFrom + strum::EnumCount> TryFrom + for RIIntResult +{ + type Error = (); + + fn try_from(value: i64) -> Result { + if value >= 0 && value <= R::MAX.into() { + Ok(RIIntResult::Ok(value.try_into().map_err(|_| ())?)) + } else if value < 0 && value >= -(E::COUNT as i64) { + Ok(RIIntResult::Err(value.try_into().map_err(|_| ())?)) + } else { + Err(()) + } + } +} + +impl + IntoI64, E: TryFrom + strum::EnumCount> TryFrom + for RIIntResult +{ + type Error = (); + + fn try_from(value: i32) -> Result { + (value as i64).try_into() + } +} + +impl + strum::EnumCount> TryFrom> for i32 { + type Error = (); + + fn try_from(value: RIIntResult) -> Result { + match value { + RIIntResult::Ok(_) => Ok(0), + RIIntResult::Err(e) => { + let error_code: i64 = e.into(); + if error_code < 0 && error_code >= -(E::COUNT as i64) { + Ok(error_code as i32) + } else { + Err(()) + } + }, + } + } +} + /// Interface for accessing the storage from within the runtime. #[runtime_interface] pub trait Storage { /// Returns the data for `key` in the storage or `None` if the key can not be found. + #[version(1, register_only)] fn get( &mut self, key: PassFatPointerAndRead<&[u8]>, @@ -207,6 +643,59 @@ pub trait Storage { }) } + /// Get `key` from storage, placing the value into `value_out` and return the number of + /// bytes that the entry in storage has beyond the offset or `None` if the storage entry + /// doesn't exist at all. + /// If `value_out` length is smaller than the returned length, only `value_out` length bytes + /// are copied into `value_out`. + /// If `allow_partial` is non-zero, the function will copy as many bytes as possible into + /// `value_out`, even if the value is longer than `value_out`. + #[version(2)] + #[raw_api] + fn read( + &mut self, + key: PassFatPointerAndRead<&[u8]>, + value_out: PassFatPointerAndWrite<&mut [u8]>, + value_offset: u32, + allow_partial: u32, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + self.storage(key).map(|value| { + let value_offset = value_offset as usize; + let data = &value[value_offset.min(value.len())..]; + let out_len = core::cmp::min(data.len(), value_out.len()); + if value_out.len() >= data.len() || allow_partial != 0 { + value_out[..out_len].copy_from_slice(&data[..out_len]); + } + data.len() as u32 + }) + } + + /// A convenience wrapper providing exact-read interface to the `read` host function. + #[wrapper] + fn read_exact(key: impl AsRef<[u8]>, value_out: &mut [u8], value_offset: u32) -> Option { + read__raw(key.as_ref(), &mut value_out[..], value_offset, 0) + } + + /// A convenience wrapper providing interface for partial storage reads (e.g. for `decode_len`). + #[wrapper] + fn read_partial(key: impl AsRef<[u8]>, value_out: &mut [u8], value_offset: u32) -> Option { + read__raw(key.as_ref(), &mut value_out[..], value_offset, 1) + } + + /// A convenience wrapper implementing the deprecated `get` host function + /// functionality through the new interface. + #[wrapper] + fn get(key: impl AsRef<[u8]>) -> Option> { + let mut value_out = vec![0u8; 256]; + let len = read_exact(key.as_ref(), &mut value_out[..], 0)?; + if len as usize > value_out.len() { + value_out.resize(len as usize, 0); + read_exact(key.as_ref(), &mut value_out[..], 0)?; + } + value_out.truncate(len as usize); + Some(value_out) + } + /// Set `key` to `value` in the storage. fn set(&mut self, key: PassFatPointerAndRead<&[u8]>, value: PassFatPointerAndRead<&[u8]>) { self.set_storage(key.to_vec(), value.to_vec()); @@ -281,24 +770,26 @@ pub trait Storage { /// operating on the same prefix should always pass `Some`, and this should be equal to the /// previous call result's `maybe_cursor` field. /// - /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once - /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// Stores the output cursor and three counters (backend deletions, unique key deletions, number + /// of iterations performed) into the provided output buffers. See + /// [`MultiRemovalResults`](sp_io::MultiRemovalResults) for more details. /// - /// NOTE: After the initial call for any given prefix, it is important that no keys further + /// Returns the number of bytes in the output cursor. If the output buffer is not large enough, + /// the cursor will be truncated to the length of the buffer, but the full length of the cursor + /// is still returned. + /// + /// NOTE: After the initial call for any given prefix, it is important that no further /// keys under the same prefix are inserted. If so, then they may or may not be deleted by /// subsequent calls. /// - /// # Note - /// - /// Please note that keys which are residing in the overlay for that prefix when + /// NOTE: Please note that keys which are residing in the overlay for that prefix when /// issuing this call are deleted without counting towards the `limit`. #[version(3, register_only)] fn clear_prefix( &mut self, maybe_prefix: PassFatPointerAndRead<&[u8]>, maybe_limit: PassFatPointerAndDecode>, - maybe_cursor: PassFatPointerAndDecode>>, /* TODO Make work or just - * Option>? */ + maybe_cursor: PassFatPointerAndDecode>>, ) -> AllocateAndReturnByCodec { Externalities::clear_prefix( *self, @@ -309,6 +800,73 @@ pub trait Storage { .into() } + /// Same as version 3 but avoids host-side allocation. + // ERRATA: The RFC specifies this as `ext_storage_clear_prefix_version_3`, but since + // version 3 was already registered with a different signature prior to the RFC + // implementation, this is registered as version 4 instead. + #[version(4)] + #[raw_api] + fn clear_prefix( + &mut self, + maybe_prefix: PassFatPointerAndRead<&[u8]>, + maybe_limit: ConvertAndPassAs, RIIntOption, i64>, + maybe_cursor_in: PassOptionalFatPointerAndRead>, + maybe_cursor_out: PassFatPointerAndWrite<&mut [u8]>, + counters_out: PassPointerAndWrite<&mut StorageIterations, 12>, + ) -> u32 { + let removal_results = Externalities::clear_prefix( + *self, + maybe_prefix, + maybe_limit, + maybe_cursor_in.as_ref().map(|x| &x[..]), + ); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + self.store_last_cursor(&cursor_out[..]); + if maybe_cursor_out.len() >= cursor_out_len { + maybe_cursor_out[..cursor_out_len].copy_from_slice(&cursor_out[..]); + } + } + counters_out.backend = removal_results.backend; + counters_out.unique = removal_results.unique; + counters_out.loops = removal_results.loops; + cursor_out_len as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the `clear_prefix` host + /// function. + #[wrapper] + fn clear_prefix( + maybe_prefix: impl AsRef<[u8]>, + maybe_limit: Option, + maybe_cursor_in: Option<&[u8]>, + ) -> MultiRemovalResults { + let mut result = MultiRemovalResults::default(); + let mut maybe_cursor_out = vec![0u8; 1024]; + let mut counters = StorageIterations::default(); + let cursor_len = clear_prefix__raw( + maybe_prefix.as_ref(), + maybe_limit, + maybe_cursor_in, + &mut maybe_cursor_out, + &mut counters, + ) as usize; + result.backend = counters.backend; + result.unique = counters.unique; + result.loops = counters.loops; + if cursor_len > 0 { + if maybe_cursor_out.len() < cursor_len { + maybe_cursor_out.resize(cursor_len, 0); + let cached_cursor_len = misc::last_cursor(maybe_cursor_out.as_mut_slice()); + debug_assert!(cached_cursor_len.is_some()); + debug_assert_eq!(cached_cursor_len.unwrap_or(0) as usize, cursor_len); + } + maybe_cursor_out.truncate(cursor_len); + result.maybe_cursor = Some(maybe_cursor_out); + } + result + } + /// Append the encoded `value` to the storage item at `key`. /// /// The storage item needs to implement [`EncodeAppend`](codec::EncodeAppend). @@ -327,7 +885,7 @@ pub trait Storage { /// /// Returns a `Vec` that holds the SCALE encoded hash. fn root(&mut self) -> AllocateAndReturnFatPointer> { - self.storage_root(StateVersion::V0) + self.storage_root() } /// "Commit" all existing operations and compute the resulting storage root. @@ -336,11 +894,46 @@ pub trait Storage { /// /// Returns a `Vec` that holds the SCALE encoded hash. #[version(2)] - fn root(&mut self, version: PassAs) -> AllocateAndReturnFatPointer> { - self.storage_root(version) + fn root(&mut self, _version: PassAs) -> AllocateAndReturnFatPointer> { + self.storage_root() + } + + /// "Commit" all existing operations and compute the resulting storage root. + /// + /// The hashing algorithm is defined by the `Block`. + /// + /// Fills provided output buffer with the SCALE encoded hash. Since the size of the resulting + /// value is known to the caller, this function requires the provided buffer to be large enough + /// to store the entire value; otherwise, it will panic. + #[version(3)] + #[raw_api] + fn root(&mut self, out: PassFatPointerAndWrite<&mut [u8]>) { + let root = self.storage_root(); + let encoded = codec::Encode::encode(&root); + let out_len = out.len(); + let encoded_len = encoded.len(); + assert!( + out_len >= encoded_len, + "Output buffer ({out_len} bytes) provided to store the storage root hash is not large enough ({encoded_len} bytes needed)" + ); + out[..encoded_len].copy_from_slice(&encoded[..]); + } + + /// A convenience wrapper providing a developer-friendly interface for the `root` host + /// function. + #[wrapper] + fn root() -> Vec { + // By this point, all the information about the length of the hash representing the storage + // root has been erased. We're using a generous buffer here. Making host functions generic + // over the hasher type is a big refactoring and is not worth it. + let mut root_out = vec![0u8; 256]; + root__raw(&mut root_out[..]); + codec::Decode::decode(&mut &root_out[..]) + .expect("storage root is always a valid SCALE-encoded Vec; qed") } /// Always returns `None`. This function exists for compatibility reasons. + #[version(1, register_only)] fn changes_root( &mut self, _parent_hash: PassFatPointerAndRead<&[u8]>, @@ -356,6 +949,47 @@ pub trait Storage { self.next_storage_key(key) } + /// Get the next key in storage after the given one in lexicographic order. + #[raw_api] + #[version(2)] + fn next_key( + &mut self, + key_in: PassFatPointerAndRead<&[u8]>, + key_out: PassFatPointerAndWrite<&mut [u8]>, + ) -> u32 { + let next_key = self.next_storage_key(key_in); + let next_key_len = next_key.as_ref().map(|k| k.len()).unwrap_or(0); + if let Some(next_key) = next_key { + if key_out.len() >= next_key_len { + key_out[..next_key_len].copy_from_slice(&next_key[..]); + } + } + next_key_len as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the `next_key` host + /// function. + /// + /// On success, `key_out` is populated with the next storage key and `true` is returned. + /// If there is no next key, `key_out` is cleared and `false` is returned. The caller can reuse + /// the buffer across calls to avoid repeated allocations. + #[wrapper] + fn next_key(key_in: impl AsRef<[u8]>, key_out: &mut Vec) -> bool { + let key_in = key_in.as_ref(); + let len = next_key__raw(key_in, key_out.as_mut_slice()) as usize; + if len == 0 { + key_out.clear(); + return false; + } + if len <= key_out.len() { + key_out.truncate(len); + return true; + } + key_out.resize(len, 0); + next_key__raw(key_in, &mut key_out[..]); + true + } + /// Start a new nested transaction. /// /// This allows to either commit or roll back all changes that are made after this call. @@ -405,6 +1039,7 @@ pub trait DefaultChildStorage { /// /// Parameter `storage_key` is the unprefixed location of the root of the child trie in the /// parent trie. Result is `None` if the value for `key` in the child storage can not be found. + #[version(1, register_only)] fn get( &mut self, storage_key: PassFatPointerAndRead<&[u8]>, @@ -432,12 +1067,85 @@ pub trait DefaultChildStorage { self.child_storage(&child_info, key).map(|value| { let value_offset = value_offset as usize; let data = &value[value_offset.min(value.len())..]; - let written = core::cmp::min(data.len(), value_out.len()); - value_out[..written].copy_from_slice(&data[..written]); + let out_len = core::cmp::min(data.len(), value_out.len()); + // if value_out.len() >= data.len() { + // value_out[..data.len()].copy_from_slice(data); + // } + value_out[..out_len].copy_from_slice(&data[..out_len]); data.len() as u32 }) } + /// Allocation efficient variant of `get`. + /// + /// Get `key` from child storage, placing the value into `value_out` and return the number + /// of bytes that the entry in storage has beyond the offset or `None` if the storage entry + /// doesn't exist at all. + /// If `value_out` length is smaller than the returned length, only `value_out` length bytes + /// are copied into `value_out`. + /// + /// If `allow_partial` is non-zero, the function will copy as many bytes as possible into + /// `value_out`, even if the value is longer than `value_out`. + #[version(2)] + #[raw_api] + fn read( + &mut self, + storage_key: PassFatPointerAndRead<&[u8]>, + key: PassFatPointerAndRead<&[u8]>, + value_out: PassFatPointerAndWrite<&mut [u8]>, + value_offset: u32, + allow_partial: u32, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + let child_info = ChildInfo::new_default(storage_key); + self.child_storage(&child_info, key) + .map(|value| { + let value_offset = value_offset as usize; + let data = &value[value_offset.min(value.len())..]; + let out_len = core::cmp::min(data.len(), value_out.len()); + if value_out.len() >= data.len() || allow_partial != 0 { + value_out[..out_len].copy_from_slice(&data[..out_len]); + } + data.len() as u32 + }) + .into() + } + + /// A convenience wrapper providing exact-read interface to the `read` host function. + #[wrapper] + fn read_exact( + storage_key: impl AsRef<[u8]>, + key: impl AsRef<[u8]>, + value_out: &mut [u8], + value_offset: u32, + ) -> Option { + read__raw(storage_key.as_ref(), key.as_ref(), &mut value_out[..], value_offset, 0) + } + + /// A convenience wrapper providing interface for partial storage reads (e.g. for `decode_len`). + #[wrapper] + fn read_partial( + storage_key: impl AsRef<[u8]>, + key: impl AsRef<[u8]>, + value_out: &mut [u8], + value_offset: u32, + ) -> Option { + read__raw(storage_key.as_ref(), key.as_ref(), &mut value_out[..], value_offset, 1) + } + + /// A convenience wrapper implementing the deprecated `get` host function + /// functionality through the new interface. + #[wrapper] + fn get(storage_key: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> Option> { + let mut value_out = vec![0u8; 256]; + let len = read_exact(storage_key.as_ref(), key.as_ref(), &mut value_out[..], 0)?; + if len as usize > value_out.len() { + value_out.resize(len as usize, 0); + read_exact(storage_key.as_ref(), key.as_ref(), &mut value_out[..], 0)?; + } + value_out.truncate(len as usize); + Some(value_out) + } + /// Set a child storage value. /// /// Set `key` to `value` in the child storage denoted by `storage_key`. @@ -501,7 +1209,7 @@ pub trait DefaultChildStorage { /// Clear a child storage key. /// - /// See `Storage` module `clear_prefix` documentation for `limit` usage. + /// See `Storage` module `clear_prefix` documentation. #[version(4, register_only)] fn storage_kill( &mut self, @@ -514,6 +1222,74 @@ pub trait DefaultChildStorage { .into() } + /// Same as version 4 but avoids host-side allocation. + // ERRATA: The RFC specifies this as `ext_default_child_storage_storage_kill_version_4`, + // but since version 4 was already registered with a different signature prior to the RFC + // implementation, this is registered as version 5 instead. + #[version(5)] + #[raw_api] + fn storage_kill( + &mut self, + storage_key: PassFatPointerAndRead<&[u8]>, + maybe_limit: ConvertAndPassAs, RIIntOption, i64>, + maybe_cursor_in: PassOptionalFatPointerAndRead>, + maybe_cursor_out: PassFatPointerAndWrite<&mut [u8]>, + counters_out: PassPointerAndWrite<&mut StorageIterations, 12>, + ) -> u32 { + let child_info = ChildInfo::new_default(storage_key); + let removal_results = self.kill_child_storage( + &child_info, + maybe_limit, + maybe_cursor_in.as_ref().map(|x| &x[..]), + ); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + self.store_last_cursor(&cursor_out[..]); + if maybe_cursor_out.len() >= cursor_out_len { + maybe_cursor_out[..cursor_out_len].copy_from_slice(&cursor_out[..]); + } + } + counters_out.backend = removal_results.backend; + counters_out.unique = removal_results.unique; + counters_out.loops = removal_results.loops; + cursor_out_len as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the `storage_kill` host + /// function. + #[wrapper] + fn storage_kill( + storage_key: impl AsRef<[u8]>, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { + let mut result = MultiRemovalResults::default(); + let mut maybe_cursor_out = vec![0u8; 1024]; + let mut counters = StorageIterations::default(); + let cursor_len = storage_kill__raw( + storage_key.as_ref(), + maybe_limit, + maybe_cursor, + &mut maybe_cursor_out[..], + &mut counters, + ) as usize; + result.backend = counters.backend; + result.unique = counters.unique; + result.loops = counters.loops; + if cursor_len > 0 { + if maybe_cursor_out.len() < cursor_len { + maybe_cursor_out.resize(cursor_len, 0); + let cached_cursor_len = misc::last_cursor(maybe_cursor_out.as_mut_slice()); + debug_assert!(cached_cursor_len.is_some()); + debug_assert_eq!(cached_cursor_len.unwrap_or(0) as usize, cursor_len); + } + maybe_cursor_out.truncate(cursor_len); + result.maybe_cursor = Some(maybe_cursor_out); + } + + result + } + /// Check a child storage key. /// /// Check whether the given `key` exists in default child defined at `storage_key`. @@ -554,7 +1330,7 @@ pub trait DefaultChildStorage { /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. /// - /// See `Storage` module `clear_prefix` documentation for `limit` usage. + /// See `Storage` module `clear_prefix` documentation. #[version(3, register_only)] fn clear_prefix( &mut self, @@ -573,6 +1349,77 @@ pub trait DefaultChildStorage { .into() } + /// Same as version 3 but avoids host-side allocation. + // ERRATA: The RFC specifies this as `ext_default_child_storage_clear_prefix_version_3`, + // but since version 3 was already registered with a different signature prior to the RFC + // implementation, this is registered as version 4 instead. + #[version(4)] + #[raw_api] + fn clear_prefix( + &mut self, + storage_key: PassFatPointerAndRead<&[u8]>, + prefix: PassFatPointerAndRead<&[u8]>, + maybe_limit: ConvertAndPassAs, RIIntOption, i64>, + maybe_cursor_in: PassOptionalFatPointerAndRead>, + maybe_cursor_out: PassFatPointerAndWrite<&mut [u8]>, + counters_out: PassPointerAndWrite<&mut StorageIterations, 12>, + ) -> u32 { + let child_info = ChildInfo::new_default(storage_key); + let removal_results = self.clear_child_prefix( + &child_info, + prefix, + maybe_limit, + maybe_cursor_in.as_ref().map(|x| &x[..]), + ); + let cursor_out_len = removal_results.maybe_cursor.as_ref().map(|c| c.len()).unwrap_or(0); + if let Some(cursor_out) = removal_results.maybe_cursor { + self.store_last_cursor(&cursor_out[..]); + if maybe_cursor_out.len() >= cursor_out_len { + maybe_cursor_out[..cursor_out_len].copy_from_slice(&cursor_out[..]); + } + } + counters_out.backend = removal_results.backend; + counters_out.unique = removal_results.unique; + counters_out.loops = removal_results.loops; + cursor_out_len as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the `clear_prefix` host + /// function. + #[wrapper] + fn clear_prefix( + storage_key: impl AsRef<[u8]>, + maybe_prefix: impl AsRef<[u8]>, + maybe_limit: Option, + maybe_cursor_in: Option<&[u8]>, + ) -> MultiRemovalResults { + let mut result = MultiRemovalResults::default(); + let mut maybe_cursor_out = vec![0u8; 1024]; + let mut counters = StorageIterations::default(); + let cursor_len = clear_prefix__raw( + storage_key.as_ref(), + maybe_prefix.as_ref(), + maybe_limit, + maybe_cursor_in, + &mut maybe_cursor_out, + &mut counters, + ) as usize; + result.backend = counters.backend; + result.unique = counters.unique; + result.loops = counters.loops; + if cursor_len > 0 { + if maybe_cursor_out.len() < cursor_len { + maybe_cursor_out.resize(cursor_len, 0); + let cached_cursor_len = misc::last_cursor(maybe_cursor_out.as_mut_slice()); + debug_assert!(cached_cursor_len.is_some()); + debug_assert_eq!(cached_cursor_len.unwrap_or(0) as usize, cursor_len); + } + maybe_cursor_out.truncate(cursor_len); + result.maybe_cursor = Some(maybe_cursor_out); + } + result + } + /// Default child root calculation. /// /// "Commit" all existing operations and compute the resulting child storage root. @@ -584,7 +1431,7 @@ pub trait DefaultChildStorage { storage_key: PassFatPointerAndRead<&[u8]>, ) -> AllocateAndReturnFatPointer> { let child_info = ChildInfo::new_default(storage_key); - self.child_storage_root(&child_info, StateVersion::V0) + self.child_storage_root(&child_info) } /// Default child root calculation. @@ -597,10 +1444,50 @@ pub trait DefaultChildStorage { fn root( &mut self, storage_key: PassFatPointerAndRead<&[u8]>, - version: PassAs, + _version: PassAs, ) -> AllocateAndReturnFatPointer> { let child_info = ChildInfo::new_default(storage_key); - self.child_storage_root(&child_info, version) + self.child_storage_root(&child_info) + } + + /// Default child root calculation. + /// + /// "Commit" all existing operations and compute the resulting child storage root. + /// The hashing algorithm is defined by the `Block`. + /// + /// Fills provided output buffer with the SCALE encoded hash. Since the size of the resulting + /// value is known to the caller, this function requires the provided buffer to be large enough + /// to store the entire value; otherwise, it will panic. + #[version(3)] + #[raw_api] + fn root( + &mut self, + storage_key: PassFatPointerAndRead<&[u8]>, + out: PassFatPointerAndWrite<&mut [u8]>, + ) { + let child_info = ChildInfo::new_default(storage_key); + let root = self.child_storage_root(&child_info); + let encoded = codec::Encode::encode(&root); + let out_len = out.len(); + let encoded_len = encoded.len(); + assert!( + out_len >= encoded_len, + "Output buffer ({out_len} bytes) provided to store the child storage root hash is not large enough ({encoded_len} bytes needed)" + ); + out[..encoded.len()].copy_from_slice(&encoded[..]); + } + + /// A convenience wrapper providing a developer-friendly interface for the `root` host + /// function. + #[wrapper] + fn root(storage_key: impl AsRef<[u8]>) -> Vec { + // By this point, all the information about the length of the hash representing the storage + // root has been erased. We're using a generous buffer here. Making host functions generic + // over the hasher type is a big refactoring and is not worth it. + let mut root_out = vec![0u8; 256]; + root__raw(storage_key.as_ref(), &mut root_out[..]); + codec::Decode::decode(&mut &root_out[..]) + .expect("child storage root is always a valid SCALE-encoded Vec; qed") } /// Child storage key iteration. @@ -614,6 +1501,56 @@ pub trait DefaultChildStorage { let child_info = ChildInfo::new_default(storage_key); self.next_child_storage_key(&child_info, key) } + + /// Child storage key iteration. + /// + /// Get the next key in storage after the given one in lexicographic order in child storage. + #[version(2)] + #[raw_api] + fn next_key( + &mut self, + storage_key: PassFatPointerAndRead<&[u8]>, + key_in: PassFatPointerAndRead<&[u8]>, + key_out: PassFatPointerAndWrite<&mut [u8]>, + ) -> u32 { + let child_info = ChildInfo::new_default(storage_key); + let next_key = self.next_child_storage_key(&child_info, key_in); + let next_key_len = next_key.as_ref().map(|k| k.len()).unwrap_or(0); + if let Some(next_key) = next_key { + if key_out.len() >= next_key_len { + key_out[..next_key_len].copy_from_slice(&next_key[..]); + } + } + next_key_len as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the `next_key` host + /// function. + /// + /// On success, `key_out` is populated with the next storage key and `true` is returned. + /// If there is no next key, `key_out` is cleared and `false` is returned. The caller can reuse + /// the buffer across calls to avoid repeated allocations. + #[wrapper] + fn next_key( + storage_key: impl AsRef<[u8]>, + key_in: impl AsRef<[u8]>, + key_out: &mut Vec, + ) -> bool { + let storage_key = storage_key.as_ref(); + let key_in = key_in.as_ref(); + let len = next_key__raw(storage_key, key_in, key_out.as_mut_slice()) as usize; + if len == 0 { + key_out.clear(); + return false; + } + if len <= key_out.len() { + key_out.truncate(len); + return true; + } + key_out.resize(len, 0); + next_key__raw(storage_key, key_in, &mut key_out[..]); + true + } } /// Interface that provides trie related functionality. @@ -638,6 +1575,29 @@ pub trait Trie { } } + /// A trie root formed from the iterated items. + #[version(3)] + #[raw_api] + fn blake2_256_root( + input: PassFatPointerAndDecode, Vec)>>, + version: PassAs, + out: PassPointerAndWrite<&mut H256, 32>, + ) { + let root = match version { + StateVersion::V0 => LayoutV0::::trie_root(input), + StateVersion::V1 => LayoutV1::::trie_root(input), + }; + out.0.copy_from_slice(&root.0); + } + + /// A convenience wrapper providing a developer-friendly interface for the `blake2_256_root` + /// host function. + #[wrapper] + fn blake2_256_root(data: Vec<(Vec, Vec)>, state_version: StateVersion) -> H256 { + let mut root = H256::default(); + blake2_256_root__raw(data, state_version, &mut root); + root + } /// A trie root formed from the enumerated items. fn blake2_256_ordered_root( input: PassFatPointerAndDecode>>, @@ -650,11 +1610,35 @@ pub trait Trie { fn blake2_256_ordered_root( input: PassFatPointerAndDecode>>, version: PassAs, - ) -> AllocateAndReturnPointer { - match version { + ) -> AllocateAndReturnPointer { + match version { + StateVersion::V0 => LayoutV0::::ordered_trie_root(input), + StateVersion::V1 => LayoutV1::::ordered_trie_root(input), + } + } + + /// A trie root formed from the enumerated items. + #[version(3)] + #[raw_api] + fn blake2_256_ordered_root( + input: PassFatPointerAndDecode>>, + version: PassAs, + out: PassPointerAndWrite<&mut H256, 32>, + ) { + let root = match version { StateVersion::V0 => LayoutV0::::ordered_trie_root(input), StateVersion::V1 => LayoutV1::::ordered_trie_root(input), - } + }; + out.0.copy_from_slice(&root.0); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `blake2_256_ordered_root` host function. + #[wrapper] + fn blake2_256_ordered_root(data: Vec>, state_version: StateVersion) -> H256 { + let mut root = H256::default(); + blake2_256_ordered_root__raw(data, state_version, &mut root); + root } /// A trie root formed from the iterated items. @@ -676,6 +1660,30 @@ pub trait Trie { } } + /// A trie root formed from the iterated items. + #[version(3)] + #[raw_api] + fn keccak_256_root( + input: PassFatPointerAndDecode, Vec)>>, + version: PassAs, + out: PassPointerAndWrite<&mut H256, 32>, + ) { + let root = match version { + StateVersion::V0 => LayoutV0::::trie_root(input), + StateVersion::V1 => LayoutV1::::trie_root(input), + }; + out.0.copy_from_slice(&root.0); + } + + /// A convenience wrapper providing a developer-friendly interface for the `keccak_256_root` + /// host function. + #[wrapper] + fn keccak_256_root(data: Vec<(Vec, Vec)>, state_version: StateVersion) -> H256 { + let mut root = H256::default(); + keccak_256_root__raw(data, state_version, &mut root); + root + } + /// A trie root formed from the enumerated items. fn keccak_256_ordered_root( input: PassFatPointerAndDecode>>, @@ -695,6 +1703,30 @@ pub trait Trie { } } + /// A trie root formed from the enumerated items. + #[version(3)] + #[raw_api] + fn keccak_256_ordered_root( + input: PassFatPointerAndDecode>>, + version: PassAs, + out: PassPointerAndWrite<&mut H256, 32>, + ) { + let root = match version { + StateVersion::V0 => LayoutV0::::ordered_trie_root(input), + StateVersion::V1 => LayoutV1::::ordered_trie_root(input), + }; + out.0.copy_from_slice(&root.0); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `keccak_256_ordered_root` host function. + #[wrapper] + fn keccak_256_ordered_root(data: Vec>, state_version: StateVersion) -> H256 { + let mut root = H256::default(); + keccak_256_ordered_root__raw(data, state_version, &mut root); + root + } + /// Verify trie proof fn blake2_256_verify_proof( root: PassPointerAndReadCopy, @@ -843,6 +1875,90 @@ pub trait Misc { }, } } + + /// Extract the runtime version of the given wasm blob by calling `Core_version`. + /// + /// Returns `None` if calling the function failed for any reason. Otherwise, write the + /// SCALE-encoded version information to the provided output buffer if it's large enough. + /// Returns the full length of the encoded version information regardless of whether the + /// buffer was written or not. + /// + /// # Performance + /// + /// This function may be very expensive to call depending on the wasm binary. It may be + /// relatively cheap if the wasm binary contains version information. In that case, + /// uncompression of the wasm blob is the dominating factor. + /// + /// If the wasm binary does not have the version information attached, then a legacy mechanism + /// may be involved. This means that a runtime call will be performed to query the version. + /// + /// Calling into the runtime may be incredible expensive and should be approached with care. + #[version(2)] + #[raw_api] + fn runtime_version( + &mut self, + wasm: PassFatPointerAndRead<&[u8]>, + out: PassFatPointerAndWrite<&mut [u8]>, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + use sp_core::traits::ReadRuntimeVersionExt; + + let mut ext = sp_state_machine::BasicExternalities::default(); + + match self + .extension::() + .expect("No `ReadRuntimeVersionExt` associated for the current context!") + .read_runtime_version(wasm, &mut ext) + { + Ok(v) => { + if out.len() >= v.len() { + out[..v.len()].copy_from_slice(v.as_slice()); + } + Some(v.len() as u32) + }, + Err(err) => { + log::debug!( + target: LOG_TARGET, + "cannot read version from the given runtime: {}", + err, + ); + None + }, + } + } + + /// A convenience wrapper providing a developer-friendly interface for the `runtime_version` + /// host function. + #[wrapper] + fn runtime_version(code: impl AsRef<[u8]>) -> Option> { + let mut version = vec![0u8; 1024]; + let maybe_len = runtime_version__raw(code.as_ref(), &mut version); + maybe_len.map(|len| { + version.truncate(len as usize); + version + }) + } + + /// Get the last storage cursor stored by `storage::clear_prefix`, + /// `default_child_storage::clear_prefix` and `default_child_storage::kill_prefix`. The length + /// of the cursor is known to the caller from the result of the call to aforementioned + /// functions, so the caller is required to provide the buffer of sufficient length, otherwise, + /// it will panic. + // ERRATA: The RFC requires passing a raw pointer without a length, which is not safe. + // Currently, we accept a fat pointer. + fn last_cursor( + &mut self, + out: PassFatPointerAndWrite<&mut [u8]>, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + let cursor = self.take_last_cursor()?; + + if out.len() >= cursor.len() { + out[..cursor.len()].copy_from_slice(&cursor[..]); + } else { + self.store_last_cursor(&cursor[..]); + } + + Some(cursor.len() as u32) + } } #[cfg(not(substrate_runtime))] @@ -873,6 +1989,9 @@ impl Default for UseDalekExt { } } +/// Initial buffer capacity (in number of keys) for `*_public_keys` wrappers. +const PUBLIC_KEYS_INITIAL_CAPACITY: usize = 16; + /// Interfaces for working with crypto related types from within the runtime. #[runtime_interface] pub trait Crypto { @@ -886,6 +2005,42 @@ pub trait Crypto { .ed25519_public_keys(id) } + /// Stores all `ed25519` public keys for the given key id from the keystore into the output + /// buffer, if it is large enough. Returns the number of bytes occupied by the keys, regardless + /// of whether the buffer was written or not. + #[version(2)] + #[raw_api] + fn ed25519_public_keys( + &mut self, + id: PassPointerAndReadCopy, + out: PassFatPointerAndReadWrite<&mut [ed25519::Public]>, + ) -> u32 { + let keys = self + .extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_public_keys(id); + if out.len() >= keys.len() { + out[..keys.len()].copy_from_slice(&keys[..]); + } + (keys.len() * core::mem::size_of::()) as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `ed25519_public_keys` host function + #[wrapper] + fn ed25519_public_keys(id: KeyTypeId) -> Vec { + let key_size = core::mem::size_of::(); + let mut keys = vec![ed25519::Public::default(); PUBLIC_KEYS_INITIAL_CAPACITY]; + loop { + let num_keys = ed25519_public_keys__raw(id, &mut keys) as usize / key_size; + if num_keys <= keys.len() { + keys.truncate(num_keys); + return keys; + } + keys.resize(num_keys, ed25519::Public::default()); + } + } + /// Generate an `ed22519` key for the given key type using an optional `seed` and /// store it in the keystore. /// @@ -904,6 +2059,41 @@ pub trait Crypto { .expect("`ed25519_generate` failed") } + /// Generate an `ed22519` key for the given key type using an optional `seed` and + /// store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Stores the public key in the provided output buffer. + // ERRATA: The RFC mentions the `seed` is `i32` in the prototype section, but in the description + // it calls for a pointer-size. Applies to all the *_generate functions. + #[version(2)] + #[raw_api] + fn ed25519_generate( + &mut self, + id: PassPointerAndReadCopy, + seed: PassFatPointerAndDecode>>, + out: PassPointerAndWrite<&mut ed25519::Public, 32>, + ) { + let seed = seed.as_ref().map(|s| core::str::from_utf8(s).expect("Seed is valid utf8!")); + out.0.copy_from_slice( + &self + .extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_generate_new(id, seed) + .expect("`ed25519_generate` failed"), + ); + } + + /// A convenience wrapper providing a developer-friendly interface for the `ed25519_generate` + /// host function. + #[wrapper] + fn ed25519_generate(id: KeyTypeId, seed: Option>) -> ed25519::Public { + let mut public = ed25519::Public::default(); + ed25519_generate__raw(id, seed, &mut public); + public + } + /// Sign the given `msg` with the `ed25519` key that corresponds to the given public key and /// key type in the keystore. /// @@ -921,6 +2111,44 @@ pub trait Crypto { .flatten() } + /// Sign the given `msg` with the `ed25519` key that corresponds to the given public key and + /// key type in the keystore. + /// + /// Returns the signature. + // ERRATA: The RFC erroneously declares `out` to be `i64`. Applies to all *_sign_* functions. + #[version(2)] + #[raw_api] + fn ed25519_sign( + &mut self, + id: PassPointerAndReadCopy, + pub_key: PassPointerAndRead<&ed25519::Public, 32>, + msg: PassFatPointerAndRead<&[u8]>, + out: PassPointerAndWrite<&mut ed25519::Signature, 64>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + self.extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_sign(id, pub_key, msg) + .ok() + .flatten() + .map(|sig| { + out.0.copy_from_slice(&sig); + }) + .ok_or(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the `ed25519_sign` host + /// function. + #[wrapper] + fn ed25519_sign( + id: KeyTypeId, + pub_key: &ed25519::Public, + message: &[u8], + ) -> Option { + let mut signature = ed25519::Signature::default(); + ed25519_sign__raw(id, pub_key, message, &mut signature).ok()?; + Some(signature) + } + /// Verify `ed25519` signature. /// /// Returns `true` when the verification was successful. @@ -1065,6 +2293,42 @@ pub trait Crypto { .sr25519_public_keys(id) } + /// Stores all `sr25519` public keys for the given key id from the keystore into the output + /// buffer, if it is large enough. Returns the number of bytes occupied by the keys, regardless + /// of whether the buffer was written or not. + #[version(2)] + #[raw_api] + fn sr25519_public_keys( + &mut self, + id: PassPointerAndReadCopy, + out: PassFatPointerAndReadWrite<&mut [sr25519::Public]>, + ) -> u32 { + let keys = self + .extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_public_keys(id); + if out.len() >= keys.len() { + out[..keys.len()].copy_from_slice(&keys[..]); + } + (keys.len() * core::mem::size_of::()) as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `sr25519_public_keys` host function + #[wrapper] + fn sr25519_public_keys(id: KeyTypeId) -> Vec { + let key_size = core::mem::size_of::(); + let mut keys = vec![sr25519::Public::default(); PUBLIC_KEYS_INITIAL_CAPACITY]; + loop { + let num_keys = sr25519_public_keys__raw(id, &mut keys) as usize / key_size; + if num_keys <= keys.len() { + keys.truncate(num_keys); + return keys; + } + keys.resize(num_keys, sr25519::Public::default()); + } + } + /// Generate an `sr22519` key for the given key type using an optional seed and /// store it in the keystore. /// @@ -1083,6 +2347,39 @@ pub trait Crypto { .expect("`sr25519_generate` failed") } + /// Generate an `sr22519` key for the given key type using an optional seed and + /// store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Stores the public key in the provided output buffer. + #[version(2)] + #[raw_api] + fn sr25519_generate( + &mut self, + id: PassPointerAndReadCopy, + seed: PassFatPointerAndDecode>>, + out: PassPointerAndWrite<&mut sr25519::Public, 32>, + ) { + let seed = seed.as_ref().map(|s| core::str::from_utf8(s).expect("Seed is valid utf8!")); + out.0.copy_from_slice( + &self + .extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_generate_new(id, seed) + .expect("`sr25519_generate` failed"), + ); + } + + /// A convenience wrapper providing a developer-friendly interface for the `sr25519_generate` + /// host function. + #[wrapper] + fn sr25519_generate(id: KeyTypeId, seed: Option>) -> sr25519::Public { + let mut public = sr25519::Public::default(); + sr25519_generate__raw(id, seed, &mut public); + public + } + /// Sign the given `msg` with the `sr25519` key that corresponds to the given public key and /// key type in the keystore. /// @@ -1100,6 +2397,43 @@ pub trait Crypto { .flatten() } + /// Sign the given `msg` with the `sr25519` key that corresponds to the given public key and + /// key type in the keystore. + /// + /// Returns the signature. + #[version(2)] + #[raw_api] + fn sr25519_sign( + &mut self, + id: PassPointerAndReadCopy, + pub_key: PassPointerAndRead<&sr25519::Public, 32>, + msg: PassFatPointerAndRead<&[u8]>, + out: PassPointerAndWrite<&mut sr25519::Signature, 64>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + self.extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_sign(id, pub_key, msg) + .ok() + .flatten() + .map(|sig| { + out.0.copy_from_slice(&sig); + }) + .ok_or(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the `sr25519_sign` host + /// function. + #[wrapper] + fn sr25519_sign( + id: KeyTypeId, + pub_key: &sr25519::Public, + message: &[u8], + ) -> Option { + let mut signature = sr25519::Signature::default(); + sr25519_sign__raw(id, pub_key, message, &mut signature).ok()?; + Some(signature) + } + /// Verify an `sr25519` signature. /// /// Returns `true` when the verification in successful regardless of @@ -1122,6 +2456,42 @@ pub trait Crypto { .ecdsa_public_keys(id) } + /// Stores all `ecdsa` public keys for the given key id from the keystore into the output + /// buffer, if it is large enough. Returns the number of bytes occupied by the keys, regardless + /// of whether the buffer was written or not. + #[version(2)] + #[raw_api] + fn ecdsa_public_keys( + &mut self, + id: PassPointerAndReadCopy, + out: PassFatPointerAndReadWrite<&mut [ecdsa::Public]>, + ) -> u32 { + let keys = self + .extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_public_keys(id); + if out.len() >= keys.len() { + out[..keys.len()].copy_from_slice(&keys[..]); + } + (keys.len() * core::mem::size_of::()) as u32 + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `ecdsa_public_keys` host function + #[wrapper] + fn ecdsa_public_keys(id: KeyTypeId) -> Vec { + let key_size = core::mem::size_of::(); + let mut keys = vec![ecdsa::Public::default(); PUBLIC_KEYS_INITIAL_CAPACITY]; + loop { + let num_keys = ecdsa_public_keys__raw(id, &mut keys) as usize / key_size; + if num_keys <= keys.len() { + keys.truncate(num_keys); + return keys; + } + keys.resize(num_keys, ecdsa::Public::default()); + } + } + /// Generate an `ecdsa` key for the given key type using an optional `seed` and /// store it in the keystore. /// @@ -1140,6 +2510,39 @@ pub trait Crypto { .expect("`ecdsa_generate` failed") } + /// Generate an `ecdsa` key for the given key type using an optional `seed` and + /// store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Stores the public key in the provided output buffer. + #[version(2)] + #[raw_api] + fn ecdsa_generate( + &mut self, + id: PassPointerAndReadCopy, + seed: PassFatPointerAndDecode>>, + out: PassPointerAndWrite<&mut ecdsa::Public, 33>, + ) { + let seed = seed.as_ref().map(|s| core::str::from_utf8(s).expect("Seed is valid utf8!")); + out.0.copy_from_slice( + &self + .extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_generate_new(id, seed) + .expect("`ecdsa_generate` failed"), + ); + } + + /// A convenience wrapper providing a developer-friendly interface for the `ecdsa_generate` host + /// function. + #[wrapper] + fn ecdsa_generate(id: KeyTypeId, seed: Option>) -> ecdsa::Public { + let mut public = ecdsa::Public::default(); + ecdsa_generate__raw(id, seed, &mut public); + public + } + /// Sign the given `msg` with the `ecdsa` key that corresponds to the given public key and /// key type in the keystore. /// @@ -1157,10 +2560,50 @@ pub trait Crypto { .flatten() } + /// Sign the given `msg` with the `ecdsa` key that corresponds to the given public key and + /// key type in the keystore. + /// + /// Returns the signature. + #[version(2)] + #[raw_api] + fn ecdsa_sign( + &mut self, + id: PassPointerAndReadCopy, + pub_key: PassPointerAndRead<&ecdsa::Public, 33>, + msg: PassFatPointerAndRead<&[u8]>, + out: PassPointerAndWrite<&mut ecdsa::Signature, 65>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_sign(id, pub_key, msg) + .ok() + .flatten() + .map(|sig| { + out.0.copy_from_slice(&sig); + }) + .ok_or(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the `ecdsa_sign` host + /// function. + #[wrapper] + fn ecdsa_sign( + id: KeyTypeId, + pub_key: &ecdsa::Public, + message: &[u8], + ) -> Option { + let mut signature = ecdsa::Signature::default(); + ecdsa_sign__raw(id, pub_key, message, &mut signature).ok()?; + Some(signature) + } + /// Sign the given a pre-hashed `msg` with the `ecdsa` key that corresponds to the given public /// key and key type in the keystore. /// /// Returns the signature. + // ERRATA: The RFC gathers all the *_sign_{prehashed} functions under a single definition that + // requires `msg` to be a fat pointer which obviously doesn't make sense for a prehashed + // message. fn ecdsa_sign_prehashed( &mut self, id: PassPointerAndReadCopy, @@ -1174,6 +2617,43 @@ pub trait Crypto { .flatten() } + /// Sign the given a pre-hashed `msg` with the `ecdsa` key that corresponds to the given public + /// key and key type in the keystore. + /// + /// Returns the signature. + #[version(2)] + #[raw_api] + fn ecdsa_sign_prehashed( + &mut self, + id: PassPointerAndReadCopy, + pub_key: PassPointerAndRead<&ecdsa::Public, 33>, + msg: PassPointerAndRead<&[u8; 32], 32>, + out: PassPointerAndWrite<&mut ecdsa::Signature, 65>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_sign_prehashed(id, pub_key, msg) + .ok() + .flatten() + .map(|sig| { + out.0.copy_from_slice(&sig); + }) + .ok_or(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `ecdsa_sign_prehashed` host function. + #[wrapper] + fn ecdsa_sign_prehashed( + id: KeyTypeId, + pub_key: &ecdsa::Public, + msg: &[u8; 32], + ) -> Option { + let mut signature = ecdsa::Signature::default(); + ecdsa_sign_prehashed__raw(id, pub_key, msg, &mut signature).ok()?; + Some(signature) + } + /// Verify `ecdsa` signature. /// /// Returns `true` when the verification was successful. @@ -1257,11 +2737,38 @@ pub trait Crypto { .map_err(|_| EcdsaVerifyError::BadV)?; let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[..64]) .map_err(|_| EcdsaVerifyError::BadRS)?; - let msg = libsecp256k1::Message::parse(msg); - let pubkey = - libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; + let msg = libsecp256k1::Message::parse(msg); + let pubkey = + libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; + let mut res = [0u8; 64]; + res.copy_from_slice(&pubkey.serialize()[1..65]); + Ok(res) + } + + /// Verify and recover a SECP256k1 ECDSA signature. + /// + /// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`. + /// - `msg` is the blake2-256 hash of the message. + /// + /// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey + /// (doesn't include the 0x04 prefix). + #[version(2)] + fn secp256k1_ecdsa_recover( + sig: PassPointerAndRead<&[u8; 65], 65>, + msg: PassPointerAndRead<&[u8; 32], 32>, + ) -> AllocateAndReturnByCodec> { + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = Message::from_digest_slice(msg).expect("Message is 32 bytes; qed"); + #[cfg(feature = "std")] + let ctx = secp256k1::SECP256K1; + #[cfg(not(feature = "std"))] + let ctx = secp256k1::Secp256k1::::gen_new(); + let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; - res.copy_from_slice(&pubkey.serialize()[1..65]); + res.copy_from_slice(&pubkey.serialize_uncompressed()[1..65]); Ok(res) } @@ -1272,11 +2779,17 @@ pub trait Crypto { /// /// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey /// (doesn't include the 0x04 prefix). - #[version(2)] + #[version(3)] + #[raw_api] fn secp256k1_ecdsa_recover( sig: PassPointerAndRead<&[u8; 65], 65>, msg: PassPointerAndRead<&[u8; 32], 32>, - ) -> AllocateAndReturnByCodec> { + out: PassPointerAndWrite<&mut Pubkey512, 64>, + ) -> ConvertAndReturnAs< + Result<(), EcdsaVerifyError>, + RIIntResult, + i32, + > { let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) .map_err(|_| EcdsaVerifyError::BadV)?; let sig = RecoverableSignature::from_compact(&sig[..64], rid) @@ -1287,9 +2800,20 @@ pub trait Crypto { #[cfg(not(feature = "std"))] let ctx = secp256k1::Secp256k1::::gen_new(); let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; - let mut res = [0u8; 64]; - res.copy_from_slice(&pubkey.serialize_uncompressed()[1..]); - Ok(res) + out.0.copy_from_slice(&pubkey.serialize_uncompressed()[1..]); + Ok(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `secp256k1_ecdsa_recover` host function. + #[wrapper] + fn secp256k1_ecdsa_recover( + signature: &[u8; 65], + message: &[u8; 32], + ) -> Result<[u8; 64], EcdsaVerifyError> { + let mut public = Pubkey512([0u8; 64]); + secp256k1_ecdsa_recover__raw(signature, message, &mut public)?; + Ok(public.0) } /// Verify and recover a SECP256k1 ECDSA signature. @@ -1338,6 +2862,49 @@ pub trait Crypto { Ok(pubkey.serialize()) } + /// Verify and recover a SECP256k1 ECDSA signature. + /// + /// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`. + /// - `msg` is the blake2-256 hash of the message. + /// + /// Returns `Err` if the signature is bad, otherwise the 33-byte compressed pubkey. + #[version(3)] + #[raw_api] + fn secp256k1_ecdsa_recover_compressed( + sig: PassPointerAndRead<&[u8; 65], 65>, + msg: PassPointerAndRead<&[u8; 32], 32>, + out: PassPointerAndWrite<&mut Pubkey264, 33>, + ) -> ConvertAndReturnAs< + Result<(), EcdsaVerifyError>, + RIIntResult, + i32, + > { + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = Message::from_digest_slice(msg).expect("Message is 32 bytes; qed"); + #[cfg(feature = "std")] + let ctx = secp256k1::SECP256K1; + #[cfg(not(feature = "std"))] + let ctx = secp256k1::Secp256k1::::gen_new(); + let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; + out.0.copy_from_slice(&pubkey.serialize()); + Ok(()) + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `secp256k1_ecdsa_recover_compressed` host function. + #[wrapper] + fn secp256k1_ecdsa_recover_compressed( + signature: &[u8; 65], + message: &[u8; 32], + ) -> Result<[u8; 33], EcdsaVerifyError> { + let mut public = Pubkey264([0u8; 33]); + secp256k1_ecdsa_recover_compressed__raw(signature, message, &mut public)?; + Ok(public.0) + } + /// Generate an `bls12-381` key for the given key type using an optional `seed` and /// store it in the keystore. /// @@ -1440,40 +3007,168 @@ pub trait Hashing { sp_crypto_hashing::keccak_256(data) } + /// Conduct a 256-bit Keccak hash. + #[version(2)] + #[raw_api] + fn keccak_256(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 32], 32>) { + out.copy_from_slice(&sp_crypto_hashing::keccak_256(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `keccak_256` host function. + #[wrapper] + fn keccak_256(data: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + keccak_256__raw(data, &mut out); + out + } + /// Conduct a 512-bit Keccak hash. fn keccak_512(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 64], 64> { sp_crypto_hashing::keccak_512(data) } + /// Conduct a 512-bit Keccak hash. + #[version(2)] + #[raw_api] + fn keccak_512(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut Hash512, 64>) { + out.0.copy_from_slice(&sp_crypto_hashing::keccak_512(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `keccak_512` host function. + #[wrapper] + fn keccak_512(data: &[u8]) -> [u8; 64] { + let mut out = Hash512::default(); + keccak_512__raw(data, &mut out); + out.0 + } + /// Conduct a 256-bit Sha2 hash. fn sha2_256(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 32], 32> { sp_crypto_hashing::sha2_256(data) } + /// Conduct a 256-bit Sha2 hash. + #[version(2)] + #[raw_api] + fn sha2_256(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 32], 32>) { + out.copy_from_slice(&sp_crypto_hashing::sha2_256(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `sha2_256` host function. + #[wrapper] + fn sha2_256(data: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + sha2_256__raw(data, &mut out); + out + } + /// Conduct a 128-bit Blake2 hash. fn blake2_128(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 16], 16> { sp_crypto_hashing::blake2_128(data) } + /// Conduct a 128-bit Blake2 hash. + #[version(2)] + #[raw_api] + fn blake2_128(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 16], 16>) { + out.copy_from_slice(&sp_crypto_hashing::blake2_128(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `blake2_128` host function. + #[wrapper] + fn blake2_128(data: &[u8]) -> [u8; 16] { + let mut out = [0u8; 16]; + blake2_128__raw(data, &mut out); + out + } + /// Conduct a 256-bit Blake2 hash. fn blake2_256(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 32], 32> { sp_crypto_hashing::blake2_256(data) } + /// Conduct a 256-bit Blake2 hash. + #[version(2)] + #[raw_api] + fn blake2_256(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 32], 32>) { + out.copy_from_slice(&sp_crypto_hashing::blake2_256(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `blake2_256` host function. + #[wrapper] + fn blake2_256(data: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + blake2_256__raw(data, &mut out); + out + } + /// Conduct four XX hashes to give a 256-bit result. fn twox_256(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 32], 32> { sp_crypto_hashing::twox_256(data) } + /// Conduct four XX hashes to give a 256-bit result. + #[version(2)] + #[raw_api] + fn twox_256(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 32], 32>) { + out.copy_from_slice(&sp_crypto_hashing::twox_256(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `twox_256` host function. + #[wrapper] + fn twox_256(data: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + twox_256__raw(data, &mut out); + out + } + /// Conduct two XX hashes to give a 128-bit result. fn twox_128(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 16], 16> { sp_crypto_hashing::twox_128(data) } + /// Conduct two XX hashes to give a 128-bit result. + #[version(2)] + #[raw_api] + fn twox_128(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 16], 16>) { + out.copy_from_slice(&sp_crypto_hashing::twox_128(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `twox_128` host function. + #[wrapper] + fn twox_128(data: &[u8]) -> [u8; 16] { + let mut out = [0u8; 16]; + twox_128__raw(data, &mut out); + out + } + /// Conduct two XX hashes to give a 64-bit result. fn twox_64(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 8], 8> { sp_crypto_hashing::twox_64(data) } + + /// Conduct two XX hashes to give a 64-bit result. + #[version(2)] + #[raw_api] + fn twox_64(data: PassFatPointerAndRead<&[u8]>, out: PassPointerAndWrite<&mut [u8; 8], 8>) { + out.copy_from_slice(&sp_crypto_hashing::twox_64(data)); + } + + /// A convenience wrapper providing a developer-friendly interface for the + /// `twox_64` host function. + #[wrapper] + fn twox_64(data: &[u8]) -> [u8; 8] { + let mut out = [0u8; 8]; + twox_64__raw(data, &mut out); + out + } } /// Interface that provides transaction indexing API. @@ -1548,13 +3243,46 @@ pub trait Offchain { .submit_transaction(data) } + /// Submit an encoded transaction to the pool. + /// + /// The transaction will end up in the pool. + #[version(2)] + fn submit_transaction( + &mut self, + data: PassFatPointerAndRead>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + self.extension::() + .expect( + "submit_transaction can be called only in the offchain call context with + TransactionPool capabilities enabled", + ) + .submit_transaction(data) + } + /// Returns information about the local node's network state. + #[version(1, register_only)] fn network_state(&mut self) -> AllocateAndReturnByCodec> { self.extension::() .expect("network_state can be called only in the offchain worker context") .network_state() } + /// Returns the peer ID of the local node. + fn network_peer_id( + &mut self, + out: PassPointerAndWrite<&mut NetworkPeerId, 38>, + ) -> ConvertAndReturnAs, RIIntResult, i32> { + let peer_id = self + .extension::() + .expect("network_state can be called only in the offchain worker context") + .network_state()? + .peer_id + .0; + + out.0.copy_from_slice(&peer_id); + Ok(()) + } + /// Returns current UNIX timestamp (in millis) fn timestamp(&mut self) -> ReturnAs { self.extension::() @@ -1579,6 +3307,30 @@ pub trait Offchain { .random_seed() } + /// Writes a random seed to the provided output buffer. + /// + /// This is a truly random, non-deterministic seed generated by host environment. + /// Obviously fine in the off-chain worker context. + #[version(2)] + #[raw_api] + fn random_seed(&mut self, out: PassPointerAndWrite<&mut [u8; 32], 32>) { + out.copy_from_slice( + &self + .extension::() + .expect("random_seed can be called only in the offchain worker context") + .random_seed(), + ); + } + + /// A convenience wrapper providing a developer-friendly interface for the `random_seed` host + /// function. + #[wrapper] + fn random_seed() -> [u8; 32] { + let mut seed = [0u8; 32]; + random_seed__raw(&mut seed); + seed + } + /// Sets a value in the local storage. /// /// Note this storage is not part of the consensus, it's only accessible by @@ -1643,6 +3395,7 @@ pub trait Offchain { /// If the value does not exist in the storage `None` will be returned. /// Note this storage is not part of the consensus, it's only accessible by /// offchain worker tasks running on the same machine. It IS persisted between runs. + #[version(1, register_only)] fn local_storage_get( &mut self, kind: PassAs, @@ -1656,6 +3409,48 @@ pub trait Offchain { .local_storage_get(kind, key) } + /// Reads a value from the local storage. + /// + /// If the value does not exist in the storage `None` will be returned. + /// Note this storage is not part of the consensus, it's only accessible by + /// offchain worker tasks running on the same machine. It IS persisted between runs. + fn local_storage_read( + &mut self, + kind: PassAs, + key: PassFatPointerAndRead<&[u8]>, + value_out: PassFatPointerAndWrite<&mut [u8]>, + offset: u32, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + self.extension::() + .expect( + "local_storage_get can be called only in the offchain call context with + OffchainDb extension", + ) + .local_storage_get(kind, key) + .map(|v| { + let value_offset = offset as usize; + let data = &v[value_offset.min(v.len())..]; + if data.len() <= value_out.len() { + value_out[..data.len()].copy_from_slice(data); + } + data.len() as u32 + }) + } + + /// A convenience wrapper implementing the deprecated `get` host function + /// functionality through the new interface. + #[wrapper] + fn local_storage_get(kind: StorageKind, key: impl AsRef<[u8]>) -> Option> { + let mut value_out = vec![0u8; 256]; + let len = local_storage_read(kind, key.as_ref(), &mut value_out[..], 0)?; + if len as usize > value_out.len() { + value_out.resize(len as usize, 0); + local_storage_read(kind, key.as_ref(), &mut value_out[..], 0)?; + } + value_out.truncate(len as usize); + Some(value_out) + } + /// Initiates a http request given HTTP verb and the URL. /// /// Meta is a future-reserved field containing additional, parity-scale-codec encoded @@ -1671,6 +3466,24 @@ pub trait Offchain { .http_request_start(method, uri, meta) } + /// Initiates a http request given HTTP verb and the URL. + /// + /// Meta is a future-reserved field containing additional, parity-scale-codec encoded + /// parameters. Returns the id of newly started request. + #[version(2)] + fn http_request_start( + &mut self, + method: PassFatPointerAndRead<&str>, + uri: PassFatPointerAndRead<&str>, + meta: PassFatPointerAndDecode>, + ) -> ConvertAndReturnAs, RIIntResult, i64> { + assert!(meta.is_empty(), "Meta must be empty in http_request_start"); + self.extension::() + .expect("http_request_start can be called only in the offchain worker context") + .http_request_start(method, uri, &[]) + .into() + } + /// Append header to the request. fn http_request_add_header( &mut self, @@ -1683,6 +3496,19 @@ pub trait Offchain { .http_request_add_header(request_id, name, value) } + /// Append header to the request. + #[version(2)] + fn http_request_add_header( + &mut self, + request_id: PassAs, + name: PassFatPointerAndRead<&str>, + value: PassFatPointerAndRead<&str>, + ) -> ConvertAndReturnAs, RIIntResult, i64> { + self.extension::() + .expect("http_request_add_header can be called only in the offchain worker context") + .http_request_add_header(request_id, name, value) + } + /// Write a chunk of request body. /// /// Writing an empty chunks finalizes the request. @@ -1700,6 +3526,24 @@ pub trait Offchain { .http_request_write_body(request_id, chunk, deadline) } + /// Write a chunk of request body. + /// + /// Writing an empty chunks finalizes the request. + /// Passing `None` as deadline blocks forever. + /// + /// Returns an error in case deadline is reached or the chunk couldn't be written. + #[version(2)] + fn http_request_write_body( + &mut self, + request_id: PassAs, + chunk: PassFatPointerAndRead<&[u8]>, + deadline: PassFatPointerAndDecode>, + ) -> ConvertAndReturnAs, RIIntResult, i64> { + self.extension::() + .expect("http_request_write_body can be called only in the offchain worker context") + .http_request_write_body(request_id, chunk, deadline) + } + /// Block and wait for the responses for given requests. /// /// Returns a vector of request statuses (the len is the same as ids). @@ -1717,10 +3561,60 @@ pub trait Offchain { .http_response_wait(ids, deadline) } + /// TODO: Original error codes are used as they do not contradict anything. That should be + /// either reflected in RFC-145 or changed here. + /// + /// Block and wait for the responses for given requests. + /// + /// Fills the provided output buffer with request statuses. The length of the provided buffer + /// should be no less than the length of the input ids. + /// + /// Note that if deadline is not provided the method will block indefinitely, + /// otherwise unready responses will produce `DeadlineReached` status. + /// + /// Passing `None` as deadline blocks forever. + #[version(2)] + #[raw_api] + fn http_response_wait( + &mut self, + ids: PassFatPointerAndDecodeSlice<&[HttpRequestId]>, + deadline: PassFatPointerAndDecode>, + out: PassFatPointerAndWrite<&mut [u32]>, + ) { + assert_eq!( + out.len(), + ids.len(), + "Output buffer length must match input ids length in http_response_wait" + ); + let statuses = self + .extension::() + .expect("http_response_wait can be called only in the offchain worker context") + .http_response_wait(ids, deadline); + statuses.into_iter().zip(out).for_each(|(status, out)| { + *out = status.into(); + }); + } + + /// A convenience wrapper providing a developer-friendly interface for the `http_response_wait` + /// host function. + #[wrapper] + fn http_response_wait( + ids: &[HttpRequestId], + deadline: Option, + ) -> Vec { + let mut statuses = vec![0u32; ids.len()]; + http_response_wait__raw(&ids, deadline.into(), &mut statuses[..]); + statuses + .into_iter() + .map(|s| HttpRequestStatus::try_from(s).unwrap_or(HttpRequestStatus::Invalid)) + .collect::>() + } + /// Read all response headers. /// /// Returns a vector of pairs `(HeaderKey, HeaderValue)`. /// NOTE: response headers have to be read before response body. + #[version(1, register_only)] fn http_response_headers( &mut self, request_id: PassAs, @@ -1730,6 +3624,85 @@ pub trait Offchain { .http_response_headers(request_id) } + /// Read the name of the header at the given index into the provided output buffer. + /// + /// Returns the full length of the header name. The value is actually stored only if the + /// buffer is large enough. Otherwise, the buffer is not written into, and its contents are + /// unchanged. + /// + /// Returns `None` if the index is out of bounds. + fn http_response_header_name( + &mut self, + request_id: PassAs, + header_index: u32, + out: PassFatPointerAndWrite<&mut [u8]>, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + let headers = self + .extension::() + .expect("http_response_header_name can be called only in the offchain worker context") + .http_response_headers(request_id); + let res = &headers.get(header_index as usize)?.0; + if res.len() <= out.len() { + out[..res.len()].copy_from_slice(res); + } + Some(res.len() as u32) + } + + /// Read the value of the header at the given index into the provided output buffer. + /// + /// Returns the full length of the header value. The value is actually stored only if the + /// buffer is large enough. Otherwise, the buffer is not written into, and its contents are + /// unchanged. + /// + /// Returns `None` if the index is out of bounds. + fn http_response_header_value( + &mut self, + request_id: PassAs, + header_index: u32, + out: PassFatPointerAndWrite<&mut [u8]>, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + let headers = self + .extension::() + .expect("http_response_header_value can be called only in the offchain worker context") + .http_response_headers(request_id); + let res = &headers.get(header_index as usize)?.1; + if res.len() <= out.len() { + out[..res.len()].copy_from_slice(res); + } + Some(res.len() as u32) + } + + /// A convenience wrapper providing a developer-friendly interface for the obsoleted + /// `http_response_headers` host function. + #[wrapper] + fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec, Vec)> { + let mut name_buf = vec![0u8; 256]; + let mut value_buf = vec![0u8; 256]; + let mut head_idx = 0; + let mut headers = Vec::new(); + + while let Some(name_len) = + http_response_header_name(request_id, head_idx, &mut name_buf[..]) + { + let name_len = name_len as usize; + if name_len > name_buf.len() { + name_buf.resize(name_len, 0); + http_response_header_name(request_id, head_idx, &mut name_buf[..]) + .expect("It was checked that the header exists"); + } + let value_len = http_response_header_value(request_id, head_idx, &mut value_buf[..]) + .expect("It was checked that the header exists") as usize; + if value_len > value_buf.len() { + value_buf.resize(value_len, 0); + http_response_header_value(request_id, head_idx, &mut value_buf[..]) + .expect("It was checked that the header exists"); + } + headers.push((name_buf[..name_len].to_vec(), value_buf[..value_len].to_vec())); + head_idx += 1; + } + headers + } + /// Read a chunk of body response to given buffer. /// /// Returns the number of bytes written or an error in case a deadline @@ -1750,6 +3723,27 @@ pub trait Offchain { .map(|r| r as u32) } + /// Read a chunk of body response to given buffer. + /// + /// Returns the number of bytes written or an error in case a deadline + /// is reached or server closed the connection. + /// If `0` is returned it means that the response has been fully consumed + /// and the `request_id` is now invalid. + /// NOTE: this implies that response headers must be read before draining the body. + /// Passing `None` as a deadline blocks forever. + #[version(2)] + fn http_response_read_body( + &mut self, + request_id: PassAs, + buffer_out: PassFatPointerAndWrite<&mut [u8]>, + deadline: PassFatPointerAndDecode>, + ) -> ConvertAndReturnAs, RIIntResult, i64> { + self.extension::() + .expect("http_response_read_body can be called only in the offchain worker context") + .http_response_read_body(request_id, buffer_out, deadline) + .map(|r| r as u32) + } + /// Set the authorized nodes and authorized_only flag. fn set_authorized_nodes( &mut self, @@ -1987,6 +3981,19 @@ pub fn oom(_: core::alloc::Layout) -> ! { } } +/// Input data handling functions +#[runtime_interface(wasm_only)] +pub trait Input { + /// Read input data into the provided buffer. + fn read(&mut self, buffer: PassFatPointerAndWrite<&mut [u8]>) { + let data = self + .take_input_data() + .expect("input data is not empty on code entry and is only taken once; qed"); + assert!(buffer.len() >= data.len()); + buffer.copy_from_slice(&data[..]); + } +} + /// Type alias for Externalities implementation used in tests. #[cfg(feature = "std")] // NOTE: Deliberately isn't `not(substrate_runtime)`. pub type TestExternalities = sp_state_machine::TestExternalities; @@ -2010,6 +4017,7 @@ pub type SubstrateHostFunctions = ( crate::trie::HostFunctions, offchain_index::HostFunctions, transaction_index::HostFunctions, + input::HostFunctions, ); #[cfg(test)] @@ -2059,11 +4067,23 @@ mod tests { }); t.execute_with(|| { + // `read_exact` with a buffer that is too small does NOT write data into the buffer + // (RFC-145). + let mut v = [0u8; 4]; + assert_eq!(storage::read_exact(b":test", &mut v[..], 0).unwrap(), value.len() as u32); + assert_eq!(v, [0u8, 0, 0, 0]); + + // `read_partial` with a buffer that is too small DOES write partial data. let mut v = [0u8; 4]; - assert_eq!(storage::read(b":test", &mut v[..], 0).unwrap(), value.len() as u32); + assert_eq!(storage::read_partial(b":test", &mut v[..], 0).unwrap(), value.len() as u32); assert_eq!(v, [11u8, 0, 0, 0]); + + // `read_exact` with an exact-sized buffer works. let mut w = [0u8; 11]; - assert_eq!(storage::read(b":test", &mut w[..], 4).unwrap(), value.len() as u32 - 4); + assert_eq!( + storage::read_exact(b":test", &mut w[..], 4).unwrap(), + value.len() as u32 - 4 + ); assert_eq!(&w, b"Hello world"); }); } @@ -2081,30 +4101,20 @@ mod tests { }); t.execute_with(|| { - // We can switch to this once we enable v3 of the `clear_prefix`. - // assert!(matches!( - // storage::clear_prefix(b":abc", None), - // MultiRemovalResults::NoneLeft { db: 2, total: 2 } - //)); - assert!(matches!( - storage::clear_prefix(b":abc", None), - KillStorageResult::AllRemoved(2), - )); + let res = storage::clear_prefix(b":abc", None, None); + assert_eq!(res.backend, 2); + assert_eq!(res.unique, 2); + assert_eq!(res.loops, 2); assert!(storage::get(b":a").is_some()); assert!(storage::get(b":abdd").is_some()); assert!(storage::get(b":abcd").is_none()); assert!(storage::get(b":abc").is_none()); - // We can switch to this once we enable v3 of the `clear_prefix`. - // assert!(matches!( - // storage::clear_prefix(b":abc", None), - // MultiRemovalResults::NoneLeft { db: 0, total: 0 } - //)); - assert!(matches!( - storage::clear_prefix(b":abc", None), - KillStorageResult::AllRemoved(0), - )); + let res = storage::clear_prefix(b":abc", None, None); + assert_eq!(res.backend, 0); + assert_eq!(res.unique, 0); + assert_eq!(res.loops, 0); }); } diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index ec10a6567dcaf..352c85e8bfabe 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -17,6 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +byte-slice-cast = { workspace = true } bytes = { workspace = true } codec = { features = ["bytes"], workspace = true } impl-trait-for-tuples = { workspace = true } @@ -41,6 +42,7 @@ trybuild = { workspace = true } [features] default = ["std"] std = [ + "byte-slice-cast/std", "bytes/std", "codec/std", "sp-externalities/std", diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index 062f3f6a9b290..3009718135638 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -41,7 +41,7 @@ use syn::{ use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned}; +use quote::{quote, quote_spanned, ToTokens}; use std::iter; @@ -69,6 +69,13 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Res Ok(t) }); + // wrappers + let result: Result = + runtime_interface.wrappers().try_fold(result?, |mut t, (name, wrapper)| { + t.extend(function_wrapper_impl(name, wrapper)); + Ok(t) + }); + result } @@ -96,10 +103,18 @@ fn function_no_std_impl( is_wasm_only: bool, ) -> Result { let should_trap_on_return = method.should_trap_on_return(); + let is_raw_api = method.is_raw_api(); let mut method = (*method).clone(); crate::utils::unpack_inner_types_in_signature(&mut method.sig); - let function_name = &method.sig.ident; + let (function_name, maybe_allow_non_snake) = if is_raw_api { + ( + Ident::new(&format!("{}__raw", &method.sig.ident), method.sig.ident.span()), + quote! { #[allow(non_snake_case)] }, + ) + } else { + (method.sig.ident.clone(), quote! {}) + }; let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident); let args = get_function_arguments(&method.sig); let arg_names = get_function_argument_names(&method.sig); @@ -136,6 +151,7 @@ fn function_no_std_impl( #cfg_wasm_only #[cfg(substrate_runtime)] #( #attrs )* + #maybe_allow_non_snake pub fn #function_name( #( #args, )* ) #return_value { // Call the host function #host_function_name.get()( #( #arg_names, )* ) @@ -144,11 +160,40 @@ fn function_no_std_impl( }) } +fn function_wrapper_impl(name: &Ident, wrapper: &TraitItemFn) -> Result { + let attrs = wrapper + .attrs + .iter() + .filter(|a| !a.path().is_ident("wrapper")) + .collect::>(); + let args = get_function_arguments(&wrapper.sig); + let return_value = &wrapper.sig.output; + let body = wrapper.default.as_ref().expect("wrapper must have a body").to_token_stream(); + + Ok(quote_spanned! { wrapper.span() => + #( #attrs )* + pub fn #name( #( #args, )* ) #return_value { + #body + } + }) +} + /// Generate call to latest function version for `cfg(not(substrate_runtime))` /// /// This should generate simple `fn func(..) { func_version_(..) }`. -fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result { - let function_name = &method.sig.ident; +fn function_std_latest_impl( + method: &RuntimeInterfaceFunction, + latest_version: u32, +) -> Result { + let (function_name, maybe_allow_non_snake) = if method.is_raw_api() { + ( + Ident::new(&format!("{}__raw", &method.sig.ident), method.sig.ident.span()), + quote! { #[allow(non_snake_case)] }, + ) + } else { + (method.sig.ident.clone(), quote! {}) + }; + let method = (*method).clone(); let args = get_function_arguments(&method.sig).map(pat_ty_to_host_inner).map(FnArg::Typed); let arg_names = get_function_argument_names(&method.sig).collect::>(); let return_value = host_inner_return_ty(&method.sig.output); @@ -159,6 +204,7 @@ fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result Ok(quote_spanned! { method.span() => #[cfg(not(substrate_runtime))] #( #attrs )* + #maybe_allow_non_snake pub fn #function_name( #( #args, )* ) #return_value { #latest_function_name( #( #arg_names, )* diff --git a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs index b0db2ad8a6eb3..3944662c015d9 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs @@ -43,6 +43,7 @@ mod attributes { pub struct RuntimeInterfaceFunction { item: TraitItemFn, should_trap_on_return: bool, + is_raw_api: bool, } impl std::ops::Deref for RuntimeInterfaceFunction { @@ -56,10 +57,15 @@ impl RuntimeInterfaceFunction { fn new(item: &TraitItemFn) -> Result { let mut item = item.clone(); let mut should_trap_on_return = false; + let mut is_raw_api = false; + item.attrs.retain(|attr| { if attr.path().is_ident("trap_on_return") { should_trap_on_return = true; false + } else if attr.path().is_ident("raw_api") { + is_raw_api = true; + false } else { true } @@ -72,12 +78,16 @@ impl RuntimeInterfaceFunction { )); } - Ok(Self { item, should_trap_on_return }) + Ok(Self { item, should_trap_on_return, is_raw_api }) } pub fn should_trap_on_return(&self) -> bool { self.should_trap_on_return } + + pub fn is_raw_api(&self) -> bool { + self.is_raw_api + } } /// Runtime interface function with all associated versions of this function. @@ -141,6 +151,7 @@ impl RuntimeInterfaceFunctionSet { /// All functions of a runtime interface grouped by the function names. pub struct RuntimeInterface { items: BTreeMap, + wrappers: BTreeMap, } impl RuntimeInterface { @@ -158,6 +169,10 @@ impl RuntimeInterface { .flat_map(|(_, item)| item.versions.iter()) .map(|(v, i)| (*v, i)) } + + pub fn wrappers(&self) -> impl Iterator { + self.wrappers.iter() + } } /// Generates the include for the runtime-interface crate. @@ -307,9 +322,16 @@ fn get_item_version(item: &TraitItemFn) -> Result> { /// Returns all runtime interface members, with versions. pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result { let mut functions: BTreeMap = BTreeMap::new(); + let mut wrappers: BTreeMap = BTreeMap::new(); for item in get_trait_methods(trait_def) { let name = item.sig.ident.clone(); + let is_wrapper = item.attrs.iter().any(|attr| attr.path().is_ident("wrapper")); + if is_wrapper { + wrappers.insert(name.clone(), item.clone()); + continue; + } + let version = get_item_version(item)?.unwrap_or_default(); if version.version < 1 { @@ -342,7 +364,7 @@ pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result } } - Ok(RuntimeInterface { items: functions }) + Ok(RuntimeInterface { items: functions, wrappers }) } pub fn host_inner_arg_ty(ty: &syn::Type) -> syn::Type { diff --git a/substrate/primitives/runtime-interface/src/pass_by.rs b/substrate/primitives/runtime-interface/src/pass_by.rs index c9d335ce455be..30c566dd708dc 100644 --- a/substrate/primitives/runtime-interface/src/pass_by.rs +++ b/substrate/primitives/runtime-interface/src/pass_by.rs @@ -29,11 +29,13 @@ use crate::host::*; #[cfg(substrate_runtime)] use crate::wasm::*; +#[cfg(not(substrate_runtime))] +use byte_slice_cast::FromByteSlice; #[cfg(not(substrate_runtime))] use sp_wasm_interface::{FunctionContext, Pointer, Result}; #[cfg(not(substrate_runtime))] -use alloc::{format, string::String}; +use alloc::{format, string::String, vec}; use alloc::vec::Vec; use core::{any::type_name, marker::PhantomData}; @@ -150,7 +152,7 @@ where /// to the host. Then the host reads that blob and converts it into an owned type and passes it /// (either as an owned type or as a reference) to the host function. /// -/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) +/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer). pub struct PassFatPointerAndRead(PhantomData); impl RIType for PassFatPointerAndRead { @@ -222,6 +224,60 @@ where } } +/// Pass an optional value into the host by a fat pointer. +/// +/// This casts the value into a `&[u8]` and passes a pointer to that byte blob and its length +/// to the host. Then the host reads that blob and converts it into an owned type and passes it +/// (either as an owned type or as a reference) to the host function. +/// +/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer). +/// `u64::MAX` is used to represent `None`. +pub struct PassOptionalFatPointerAndRead(PhantomData); + +impl RIType for PassOptionalFatPointerAndRead { + type FFIType = u64; + type Inner = T; +} + +#[cfg(not(substrate_runtime))] +impl<'a> FromFFIValue<'a> for PassOptionalFatPointerAndRead> { + type Owned = Option>; + + fn from_ffi_value( + context: &mut dyn FunctionContext, + arg: Self::FFIType, + ) -> Result { + if arg == Self::FFIType::MAX { + Ok(None) + } else { + let (ptr, len) = unpack_ptr_and_len(arg); + context.read_memory(Pointer::new(ptr), len).map(Some) + } + } + + fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { + owned.as_deref() + } +} + +#[cfg(substrate_runtime)] +impl IntoFFIValue for PassOptionalFatPointerAndRead> +where + T: AsRef<[u8]>, +{ + type Destructor = (); + + fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { + match value { + None => (Self::FFIType::MAX, ()), + Some(value) => { + let value = value.as_ref(); + (pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ()) + }, + } + } +} + /// Pass an `Option` of a value into the host by a fat pointer. /// /// Behaves like [`PassFatPointerAndRead`] for the `Some` case, with `None` encoded as the @@ -292,7 +348,7 @@ impl RIType for PassFatPointerAndReadWrite { } #[cfg(not(substrate_runtime))] -impl<'a> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [u8]> { +impl<'a, T: FromByteSlice> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [T]> { type Owned = Vec; fn from_ffi_value( @@ -304,7 +360,8 @@ impl<'a> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [u8]> { } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { - &mut *owned + FromByteSlice::from_mut_byte_slice(owned) + .expect("byte slice has wrong alignment or size for target type") } fn write_back_into_runtime( @@ -319,20 +376,20 @@ impl<'a> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [u8]> { } #[cfg(substrate_runtime)] -impl<'a> IntoFFIValue for PassFatPointerAndReadWrite<&'a mut [u8]> { +impl<'a, T> IntoFFIValue for PassFatPointerAndReadWrite<&'a mut [T]> { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { - (pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ()) + (pack_ptr_and_len(value.as_ptr() as u32, core::mem::size_of_val(*value) as u32), ()) } } -/// Pass a pointer into the host by a fat pointer, writing it back after the host call ends. +/// Pass a buffer into the host by a fat pointer. The host will write data into it. /// /// This casts the value into a `&mut [u8]` and passes a pointer to that byte blob and its length -/// to the host. The host *doesn't* read from this and instead creates a zero-initialized buffer -/// as a mutable reference to the host function. After the host function finishes the byte blob -/// is written back into the guest memory. +/// to the host. Then the host allocates a temporary buffer of the same size and passes it +/// as a mutable reference to the host function. After the host function finishes the data is +/// written back into the guest memory. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct PassFatPointerAndWrite(PhantomData); @@ -343,19 +400,20 @@ impl RIType for PassFatPointerAndWrite { } #[cfg(not(substrate_runtime))] -impl<'a> FromFFIValue<'a> for PassFatPointerAndWrite<&'a mut [u8]> { +impl<'a, T: FromByteSlice> FromFFIValue<'a> for PassFatPointerAndWrite<&'a mut [T]> { type Owned = Vec; fn from_ffi_value( _context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { - let (_ptr, len) = unpack_ptr_and_len(arg); - Ok(alloc::vec![0u8; len as usize]) + let (_, len) = unpack_ptr_and_len(arg); + Ok(vec![0u8; len as usize]) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { - &mut *owned + FromByteSlice::from_mut_byte_slice(owned) + .expect("byte slice has wrong alignment or size for target type") } fn write_back_into_runtime( @@ -370,11 +428,11 @@ impl<'a> FromFFIValue<'a> for PassFatPointerAndWrite<&'a mut [u8]> { } #[cfg(substrate_runtime)] -impl<'a> IntoFFIValue for PassFatPointerAndWrite<&'a mut [u8]> { +impl<'a, T> IntoFFIValue for PassFatPointerAndWrite<&'a mut [T]> { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { - (pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ()) + (pack_ptr_and_len(value.as_ptr() as u32, core::mem::size_of_val(*value) as u32), ()) } } @@ -529,13 +587,12 @@ impl<'a, T: codec::Encode> IntoFFIValue for PassFatPointerAndDecodeSlice<&'a [T] } /// A trait signifying a primitive type. -trait Primitive: Copy {} +trait Primitive: Copy + Default {} impl Primitive for u8 {} impl Primitive for u16 {} impl Primitive for u32 {} impl Primitive for u64 {} - impl Primitive for i8 {} impl Primitive for i16 {} impl Primitive for i32 {} @@ -596,6 +653,65 @@ where } } +/// Pass `T` through the FFI boundary by first converting it to `U` and then to `V` in the runtime, +/// and then converting it back to `U` and then to `T` on the host's side. +/// +/// Raw FFI type: same as `V`'s FFI type +pub struct ConvertAndPassAs(PhantomData<(T, U, V)>); + +impl RIType for ConvertAndPassAs +where + V: RIType, +{ + type FFIType = ::FFIType; + type Inner = T; +} + +#[cfg(not(substrate_runtime))] +impl<'a, T, U, V> FromFFIValue<'a> for ConvertAndPassAs +where + V: RIType + FromFFIValue<'a> + Primitive, + U: TryFrom<>::Owned> + Copy, + T: From + Copy, +{ + type Owned = T; + + fn from_ffi_value( + context: &mut dyn FunctionContext, + arg: Self::FFIType, + ) -> Result { + let value = ::from_ffi_value(context, arg).and_then(|value| value.try_into() + .map_err(|_| format!( + "failed to convert '{}' (passed as '{}') into intermediate type '{}' when marshalling hostcall's arguments through the FFI boundary", + type_name::(), + type_name::(), + type_name::() + )))?; + Ok(T::from(value)) + } + + fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { + *owned + } +} + +#[cfg(substrate_runtime)] +impl IntoFFIValue for ConvertAndPassAs +where + V: RIType + IntoFFIValue + Primitive, + V::Inner: From, + U: From, + T: Copy, +{ + type Destructor = ::Destructor; + + fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { + let conv_value = U::from(*value); + let mut value = V::Inner::from(conv_value); + ::into_ffi_value(&mut value) + } +} + /// Return `T` through the FFI boundary by first converting it to `U` on the host's side, and then /// converting it back to `T` in the runtime. /// @@ -668,7 +784,7 @@ where impl IntoFFIValue for ConvertAndReturnAs where V: RIType + IntoFFIValue + Primitive, - ::Inner: From, + ::Inner: TryFrom, U: From, { fn into_ffi_value( @@ -676,7 +792,13 @@ where context: &mut dyn FunctionContext, ) -> Result { let value: U = value.into(); - let value: ::Inner = value.into(); + let value: ::Inner = value.try_into().map_err(|_| { + format!( + "failed to convert intermediate type '{}' into '{}' when marshalling a hostcall's return value through the FFI boundary", + type_name::(), + type_name::<::Inner>() + ) + })?; ::into_ffi_value(value, context) } } diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 9e456e9bcfa0b..1a0264de5cb7e 100644 --- a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -16,8 +16,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +bytes = { workspace = true } sp-core = { workspace = true } -sp-io = { workspace = true } +sp-io = { workspace = true, features = ["disable_allocator"] } sp-runtime-interface = { workspace = true } [build-dependencies] @@ -26,6 +27,7 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = [features] default = ["std"] std = [ + "bytes/std", "sp-core/std", "sp-io/std", "sp-runtime-interface/std", diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs b/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs index 871a4922ce3ab..c853356c5c806 100644 --- a/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs @@ -16,11 +16,33 @@ // limitations under the License. //! Tests for the runtime interface traits and proc macros. +//! +//! This crate uses V1 entry points (host-side allocation) to test backward compatibility +//! and AllocateAndReturn* marshalling strategies. sp-io is built with `disable_allocator` +//! so that picoalloc is not registered; instead, the host-side allocator +//! (`ext_allocator_malloc`/`ext_allocator_free`) is used via a custom `#[global_allocator]`. #![cfg_attr(not(feature = "std"), no_std)] -use sp_core::wasm_export_functions; -use sp_runtime_interface::runtime_interface; +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +#[cfg(not(feature = "std"))] +use core::mem; + +use sp_core::wasm_export_functions_v1; +use sp_runtime_interface::{ + pass_by::{ + AllocateAndReturnByCodec, AllocateAndReturnFatPointer, AllocateAndReturnPointer, PassAs, + PassFatPointerAndDecode, PassFatPointerAndDecodeSlice, PassFatPointerAndRead, + PassFatPointerAndReadWrite, PassPointerAndRead, PassPointerAndReadCopy, + PassPointerAndWrite, ReturnAs, + }, + runtime_interface, +}; // Include the WASM binary #[cfg(feature = "std")] @@ -35,6 +57,29 @@ pub fn wasm_binary_unwrap() -> &'static [u8] { ) } +// Use the host-side allocator (ext_allocator_malloc/free) instead of picoalloc. +// This is necessary because V1 entry points cause the host to create a +// FreeingBumpHeapAllocator from __heap_base, which would conflict with picoalloc. +#[cfg(not(feature = "std"))] +mod host_allocator { + use core::alloc::{GlobalAlloc, Layout}; + + struct HostAllocator; + + #[global_allocator] + static ALLOCATOR: HostAllocator = HostAllocator; + + unsafe impl GlobalAlloc for HostAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + sp_io::allocator::malloc(layout.size() as u32) + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + sp_io::allocator::free(ptr); + } + } +} + /// This function is not used, but we require it for the compiler to include `sp-io`. /// `sp-io` is required for its panic and oom handler. #[cfg(not(feature = "std"))] @@ -43,21 +88,258 @@ pub fn import_sp_io() { sp_io::misc::print_utf8(&[]); } +/// Used in marshalling strategy tests. +const TEST_ARRAY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + #[runtime_interface] pub trait TestApi { - fn test_versioning(&self, _data: u32) -> bool { - // should not be called - unimplemented!() + /// Old version that accepts both 42 and 50. + fn test_versioning(&self, data: u32) -> bool { + data == 42 || data == 50 + } + + /// Returns the input data as result. + fn return_input(data: PassFatPointerAndRead>) -> AllocateAndReturnFatPointer> { + data + } + + /// Returns 16kb data. + fn return_16kb() -> AllocateAndReturnByCodec> { + vec![0; 4 * 1024] + } + + fn return_option_vec() -> AllocateAndReturnByCodec>> { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec) + } + + fn return_option_bytes() -> AllocateAndReturnByCodec> { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec.into()) + } + + fn return_option_input( + data: PassFatPointerAndRead>, + ) -> AllocateAndReturnByCodec>> { + Some(data) + } + + fn get_and_return_array( + data: PassPointerAndReadCopy<[u8; 34], 34>, + ) -> AllocateAndReturnPointer<[u8; 16], 16> { + let mut res = [0u8; 16]; + res.copy_from_slice(&data[..16]); + res + } + + fn return_input_public_key( + key: PassPointerAndReadCopy, + ) -> AllocateAndReturnPointer { + key + } + + fn return_input_as_tuple( + a: PassFatPointerAndRead>, + b: u32, + c: PassFatPointerAndDecode>>, + d: u8, + ) -> AllocateAndReturnByCodec<(Vec, u32, Option>, u8)> { + (a, b, c, d) + } + + fn set_storage( + &mut self, + key: PassFatPointerAndRead<&[u8]>, + data: PassFatPointerAndRead<&[u8]>, + ) { + self.place_storage(key.to_vec(), Some(data.to_vec())); + } + + fn return_value_into_mutable_reference(&self, data: PassFatPointerAndReadWrite<&mut [u8]>) { + let res = "hello"; + data[..res.len()].copy_from_slice(res.as_bytes()); + } + + fn array_as_mutable_reference(data: PassPointerAndWrite<&mut [u8; 16], 16>) { + data.copy_from_slice(&TEST_ARRAY); + } + + fn pass_pointer_and_read_copy(value: PassPointerAndReadCopy<[u8; 3], 3>) { + assert_eq!(value, [1, 2, 3]); + } + + fn pass_pointer_and_read(value: PassPointerAndRead<&[u8; 3], 3>) { + assert_eq!(value, &[1, 2, 3]); + } + + fn pass_fat_pointer_and_read(value: PassFatPointerAndRead<&[u8]>) { + assert_eq!(value, [1, 2, 3]); + } + + fn pass_fat_pointer_and_read_write(value: PassFatPointerAndReadWrite<&mut [u8]>) { + assert_eq!(value, [1, 2, 3]); + value.copy_from_slice(&[4, 5, 6]); + } + + fn pass_pointer_and_write(value: PassPointerAndWrite<&mut [u8; 3], 3>) { + assert_eq!(*value, [0, 0, 0]); + *value = [1, 2, 3]; + } + + fn pass_by_codec(value: PassFatPointerAndDecode>) { + assert_eq!(value, [1, 2, 3]); + } + + fn pass_slice_ref_by_codec(value: PassFatPointerAndDecodeSlice<&[u16]>) { + assert_eq!(value, [1, 2, 3]); + } + + fn pass_as(value: PassAs) { + assert_eq!(value.0, 123); + } + + fn return_as() -> ReturnAs { + Opaque(123) + } + + fn allocate_and_return_pointer() -> AllocateAndReturnPointer<[u8; 3], 3> { + [1, 2, 3] + } + + fn allocate_and_return_fat_pointer() -> AllocateAndReturnFatPointer> { + vec![1, 2, 3] + } + + fn allocate_and_return_by_codec() -> AllocateAndReturnByCodec> { + vec![1, 2, 3] } } -wasm_export_functions! { +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Opaque(u32); + +impl From for u32 { + fn from(value: Opaque) -> Self { + value.0 + } +} + +impl TryFrom for Opaque { + type Error = (); + fn try_from(value: u32) -> Result { + Ok(Opaque(value)) + } +} + +wasm_export_functions_v1! { fn test_versioning_works() { - // old api allows only 42 and 50 assert!(test_api::test_versioning(42)); assert!(test_api::test_versioning(50)); - assert!(!test_api::test_versioning(142)); assert!(!test_api::test_versioning(0)); } + + fn test_return_data() { + let input = vec![1, 2, 3, 4, 5, 6]; + let res = test_api::return_input(input.clone()); + assert_eq!(input, res); + } + + fn test_return_option_data() { + let input = vec![1, 2, 3, 4, 5, 6]; + let res = test_api::return_option_input(input.clone()); + assert_eq!(Some(input), res); + } + + fn test_get_and_return_array() { + let mut input = unsafe { mem::MaybeUninit::<[u8; 34]>::zeroed().assume_init() }; + input.copy_from_slice(&[ + 24, 3, 23, 20, 2, 16, 32, 1, 12, 26, 27, 8, 29, 31, 6, 5, 4, 19, 10, 28, 34, 21, 18, 33, 9, + 13, 22, 25, 15, 11, 30, 7, 14, 17, + ]); + let res = test_api::get_and_return_array(input); + assert_eq!(&res, &input[..16]); + } + + fn test_return_input_public_key() { + let key = sp_core::sr25519::Public::try_from( + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ][..], + ).unwrap(); + let ret_key = test_api::return_input_public_key(key.clone()); + let key_data: &[u8] = key.as_ref(); + let ret_key_data: &[u8] = ret_key.as_ref(); + assert_eq!(key_data, ret_key_data); + } + + fn test_return_input_as_tuple() { + let a = vec![1, 3, 4, 5]; + let b = 10000; + let c = Some(vec![2, 3]); + let d = 5; + let res = test_api::return_input_as_tuple(a.clone(), b, c.clone(), d); + assert_eq!(a, res.0); + assert_eq!(b, res.1); + assert_eq!(c, res.2); + assert_eq!(d, res.3); + } + + fn test_vec_return_value_memory_is_freed() { + let mut len = 0; + for _ in 0..1024 { + len += test_api::return_16kb().len(); + } + assert_eq!(1024 * 1024 * 4, len); + } + + fn test_encoded_return_value_memory_is_freed() { + let mut len = 0; + for _ in 0..1024 { + len += test_api::return_option_input(vec![0; 16 * 1024]).map(|v| v.len()).unwrap(); + } + assert_eq!(1024 * 1024 * 16, len); + } + + fn test_array_return_value_memory_is_freed() { + let mut len = 0; + for _ in 0..1024 * 1024 { + len += test_api::get_and_return_array([0; 34])[1]; + } + assert_eq!(0, len); + } + + fn test_return_option_vec() { + test_api::return_option_vec(); + } + + fn test_return_option_bytes() { + test_api::return_option_bytes(); + } + + fn test_v1_marshalling_strategies() { + test_api::pass_pointer_and_read_copy([1_u8, 2, 3]); + test_api::pass_pointer_and_read(&[1_u8, 2, 3]); + test_api::pass_fat_pointer_and_read(&[1_u8, 2, 3][..]); + { + let mut slice = [1_u8, 2, 3]; + test_api::pass_fat_pointer_and_read_write(&mut slice); + assert_eq!(slice, [4_u8, 5, 6]); + } + { + let mut slice = [9_u8, 9, 9]; + test_api::pass_pointer_and_write(&mut slice); + assert_eq!(slice, [1_u8, 2, 3]); + } + test_api::pass_by_codec(vec![1_u16, 2, 3]); + test_api::pass_slice_ref_by_codec(&[1_u16, 2, 3][..]); + test_api::pass_as(Opaque(123)); + assert_eq!(test_api::return_as(), Opaque(123)); + assert_eq!(test_api::allocate_and_return_pointer(), [1_u8, 2, 3]); + assert_eq!(test_api::allocate_and_return_fat_pointer(), vec![1_u8, 2, 3]); + assert_eq!(test_api::allocate_and_return_by_codec(), vec![1_u16, 2, 3]); + } } diff --git a/substrate/primitives/runtime-interface/test-wasm/src/lib.rs b/substrate/primitives/runtime-interface/test-wasm/src/lib.rs index 74e96674f7043..aeb8070f56385 100644 --- a/substrate/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/substrate/primitives/runtime-interface/test-wasm/src/lib.rs @@ -16,6 +16,9 @@ // limitations under the License. //! Tests for the runtime interface traits and proc macros. +//! +//! This crate uses V2 entry points (runtime-side allocation). Tests for V1 host-side +//! allocation strategies (AllocateAndReturn*) live in `test-wasm-deprecated`. #![cfg_attr(not(feature = "std"), no_std)] @@ -23,8 +26,7 @@ extern crate alloc; use sp_runtime_interface::{ pass_by::{ - AllocateAndReturnByCodec, AllocateAndReturnFatPointer, AllocateAndReturnPointer, PassAs, - PassFatPointerAndDecode, PassFatPointerAndDecodeSlice, PassFatPointerAndRead, + ConvertAndReturnAs, PassAs, PassFatPointerAndDecodeSlice, PassFatPointerAndRead, PassFatPointerAndReadWrite, PassPointerAndRead, PassPointerAndReadCopy, PassPointerAndWrite, ReturnAs, }, @@ -35,7 +37,8 @@ use sp_runtime_interface::{ use core::mem; use alloc::{vec, vec::Vec}; -use sp_core::{sr25519::Public, wasm_export_functions}; +use sp_core::wasm_export_functions; +use sp_io::RIIntOption; // Include the WASM binary #[cfg(feature = "std")] @@ -55,33 +58,7 @@ const TEST_ARRAY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, #[runtime_interface] pub trait TestApi { - /// Returns the input data as result. - fn return_input(data: PassFatPointerAndRead>) -> AllocateAndReturnFatPointer> { - data - } - - /// Returns 16kb data. - /// - /// # Note - /// - /// We return a `Vec` because this will use the code path that uses SCALE - /// to pass the data between native/wasm. (`Vec` is passed without encoding the - /// data) - fn return_16kb() -> AllocateAndReturnByCodec> { - vec![0; 4 * 1024] - } - - fn return_option_vec() -> AllocateAndReturnByCodec>> { - let mut vec = Vec::new(); - vec.resize(16 * 1024, 0xAA); - Some(vec) - } - - fn return_option_bytes() -> AllocateAndReturnByCodec> { - let mut vec = Vec::new(); - vec.resize(16 * 1024, 0xAA); - Some(vec.into()) - } + // ---- Host functions that don't allocate on the host side ---- /// Set the storage at key with value. fn set_storage( @@ -98,34 +75,11 @@ pub trait TestApi { data[..res.len()].copy_from_slice(res.as_bytes()); } - /// Returns the input data wrapped in an `Option` as result. - fn return_option_input( - data: PassFatPointerAndRead>, - ) -> AllocateAndReturnByCodec>> { - Some(data) - } - - /// Get an array as input and returns a subset of this array. - fn get_and_return_array( - data: PassPointerAndReadCopy<[u8; 34], 34>, - ) -> AllocateAndReturnPointer<[u8; 16], 16> { - let mut res = [0u8; 16]; - res.copy_from_slice(&data[..16]); - res - } - /// Take and fill mutable array. fn array_as_mutable_reference(data: PassPointerAndWrite<&mut [u8; 16], 16>) { data.copy_from_slice(&TEST_ARRAY); } - /// Returns the given public key as result. - fn return_input_public_key( - key: PassPointerAndReadCopy, - ) -> AllocateAndReturnPointer { - key - } - /// A function that is called with invalid utf8 data from the runtime. /// /// This also checks that we accept `_` (wild card) argument names. @@ -155,17 +109,7 @@ pub trait TestApi { data == 42 } - /// Returns the input values as tuple. - fn return_input_as_tuple( - a: PassFatPointerAndRead>, - b: u32, - c: PassFatPointerAndDecode>>, - d: u8, - ) -> AllocateAndReturnByCodec<(Vec, u32, Option>, u8)> { - (a, b, c, d) - } - - // Host functions for testing every marshaling strategy: + // ---- V1 marshalling strategies (no host alloc needed) ---- fn pass_pointer_and_read_copy(value: PassPointerAndReadCopy<[u8; 3], 3>) { assert_eq!(value, [1, 2, 3]); @@ -189,7 +133,7 @@ pub trait TestApi { *value = [1, 2, 3]; } - fn pass_by_codec(value: PassFatPointerAndDecode>) { + fn pass_by_codec(value: sp_runtime_interface::pass_by::PassFatPointerAndDecode>) { assert_eq!(value, [1, 2, 3]); } @@ -205,16 +149,55 @@ pub trait TestApi { Opaque(123) } - fn allocate_and_return_pointer() -> AllocateAndReturnPointer<[u8; 3], 3> { - [1, 2, 3] + // ---- V2 marshalling strategies (runtime-side allocation) ---- + + /// Test PassFatPointerAndWrite: host writes into a runtime-provided buffer. + #[raw_api] + fn return_input( + data: PassFatPointerAndRead<&[u8]>, + out: sp_runtime_interface::pass_by::PassFatPointerAndWrite<&mut [u8]>, + ) -> u32 { + let copy_len = data.len().min(out.len()); + out[..copy_len].copy_from_slice(&data[..copy_len]); + data.len() as u32 + } + + /// Wrapper: developer-friendly interface for return_input. + #[wrapper] + fn return_input(data: Vec) -> Vec { + let mut out = vec![0u8; data.len()]; + let len = return_input__raw(&data, &mut out) as usize; + out.truncate(len); + out + } + + /// Test ConvertAndReturnAs: return an `Option` as `i64`. + fn return_option_value( + &self, + data: u32, + ) -> ConvertAndReturnAs, RIIntOption, i64> { + if data == 0 { + None + } else { + Some(data * 2) + } } - fn allocate_and_return_fat_pointer() -> AllocateAndReturnFatPointer> { - vec![1, 2, 3] + /// Test PassPointerAndWrite with `#[raw_api]`/`#[wrapper]`. + #[raw_api] + fn get_and_return_array( + data: PassPointerAndReadCopy<[u8; 34], 34>, + out: PassPointerAndWrite<&mut [u8; 16], 16>, + ) { + out.copy_from_slice(&data[..16]); } - fn allocate_and_return_by_codec() -> AllocateAndReturnByCodec> { - vec![1, 2, 3] + /// Wrapper for get_and_return_array. + #[wrapper] + fn get_and_return_array(data: [u8; 34]) -> [u8; 16] { + let mut out = [0u8; 16]; + get_and_return_array__raw(data, &mut out); + out } } @@ -249,13 +232,6 @@ wasm_export_functions! { assert_eq!(input, res); } - fn test_return_option_data() { - let input = vec![1, 2, 3, 4, 5, 6]; - let res = test_api::return_option_input(input.clone()); - - assert_eq!(Some(input), res); - } - fn test_set_storage() { let key = "hello"; let value = "world"; @@ -291,20 +267,6 @@ wasm_export_functions! { assert_eq!(array, TEST_ARRAY); } - fn test_return_input_public_key() { - let key = Public::try_from( - &[ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, - ][..], - ).unwrap(); - let ret_key = test_api::return_input_public_key(key.clone()); - - let key_data: &[u8] = key.as_ref(); - let ret_key_data: &[u8] = ret_key.as_ref(); - assert_eq!(key_data, ret_key_data); - } - fn test_invalid_utf8_data_should_return_an_error() { let data = vec![0, 159, 146, 150]; // I'm an evil hacker, trying to hack! @@ -327,30 +289,6 @@ wasm_export_functions! { assert!(test_api::overwrite_native_function_implementation()); } - fn test_vec_return_value_memory_is_freed() { - let mut len = 0; - for _ in 0..1024 { - len += test_api::return_16kb().len(); - } - assert_eq!(1024 * 1024 * 4, len); - } - - fn test_encoded_return_value_memory_is_freed() { - let mut len = 0; - for _ in 0..1024 { - len += test_api::return_option_input(vec![0; 16 * 1024]).map(|v| v.len()).unwrap(); - } - assert_eq!(1024 * 1024 * 16, len); - } - - fn test_array_return_value_memory_is_freed() { - let mut len = 0; - for _ in 0..1024 * 1024 { - len += test_api::get_and_return_array([0; 34])[1]; - } - assert_eq!(0, len); - } - fn test_versioning_works() { // we fix new api to accept only 42 as a proper input // as opposed to sp-runtime-interface-test-wasm-deprecated::test_api::verify_input @@ -368,29 +306,8 @@ wasm_export_functions! { assert!(test_api::test_versioning_register_only(80)); } - fn test_return_input_as_tuple() { - let a = vec![1, 3, 4, 5]; - let b = 10000; - let c = Some(vec![2, 3]); - let d = 5; - - let res = test_api::return_input_as_tuple(a.clone(), b, c.clone(), d); - - assert_eq!(a, res.0); - assert_eq!(b, res.1); - assert_eq!(c, res.2); - assert_eq!(d, res.3); - } - - fn test_return_option_vec() { - test_api::return_option_vec(); - } - - fn test_return_option_bytes() { - test_api::return_option_bytes(); - } - - fn test_marshalling_strategies() { + fn test_v2_marshalling_strategies() { + // Strategies that don't allocate on the host side: test_api::pass_pointer_and_read_copy([1_u8, 2, 3]); test_api::pass_pointer_and_read(&[1_u8, 2, 3]); test_api::pass_fat_pointer_and_read(&[1_u8, 2, 3][..]); @@ -408,8 +325,18 @@ wasm_export_functions! { test_api::pass_slice_ref_by_codec(&[1_u16, 2, 3][..]); test_api::pass_as(Opaque(123)); assert_eq!(test_api::return_as(), Opaque(123)); - assert_eq!(test_api::allocate_and_return_pointer(), [1_u8, 2, 3]); - assert_eq!(test_api::allocate_and_return_fat_pointer(), vec![1_u8, 2, 3]); - assert_eq!(test_api::allocate_and_return_by_codec(), vec![1_u16, 2, 3]); + + // V2-specific strategies: + assert_eq!(test_api::return_option_value(5), Some(10)); + assert_eq!(test_api::return_option_value(0), None); + + let input = vec![10, 20, 30, 40, 50]; + let res = test_api::return_input(input.clone()); + assert_eq!(input, res); + + let mut arr = [0u8; 34]; + arr[0..4].copy_from_slice(&[99, 88, 77, 66]); + let res = test_api::get_and_return_array(arr); + assert_eq!(&res, &arr[..16]); } } diff --git a/substrate/primitives/runtime-interface/test/src/lib.rs b/substrate/primitives/runtime-interface/test/src/lib.rs index cccc7f4af67d6..de9ad6a57d02d 100644 --- a/substrate/primitives/runtime-interface/test/src/lib.rs +++ b/substrate/primitives/runtime-interface/test/src/lib.rs @@ -21,7 +21,10 @@ use sp_runtime_interface::*; use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap}; -use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap; +use sp_runtime_interface_test_wasm_deprecated::{ + test_api::HostFunctions as DeprecatedHostFunctions, + wasm_binary_unwrap as wasm_binary_deprecated_unwrap, +}; use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::AllocationStats}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT}; @@ -33,6 +36,12 @@ use std::{ type TestExternalities = sp_state_machine::TestExternalities; +fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { + call_wasm_method_with_result::(binary, method) + .0 + .expect(&format!("Failed to execute `{}`", method)) +} + fn call_wasm_method_with_result( binary: &[u8], method: &str, @@ -58,20 +67,15 @@ fn call_wasm_method_with_result( (result, allocation_stats) } -fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { - call_wasm_method_with_result::(binary, method).0.unwrap() -} +// ========================================================================= +// V2 entry point tests (test-wasm, runtime-side allocation) +// ========================================================================= #[test] fn test_return_data() { call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); } -#[test] -fn test_return_option_data() { - call_wasm_method::(wasm_binary_unwrap(), "test_return_option_data"); -} - #[test] fn test_set_storage() { let mut ext = call_wasm_method::(wasm_binary_unwrap(), "test_set_storage"); @@ -98,11 +102,6 @@ fn test_array_as_mutable_reference() { call_wasm_method::(wasm_binary_unwrap(), "test_array_as_mutable_reference"); } -#[test] -fn test_return_input_public_key() { - call_wasm_method::(wasm_binary_unwrap(), "test_return_input_public_key"); -} - #[test] fn host_function_not_found() { let err = call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data") @@ -131,38 +130,19 @@ fn test_overwrite_native_function_implementation() { ); } -#[test] -fn test_vec_return_value_memory_is_freed() { - call_wasm_method::( - wasm_binary_unwrap(), - "test_vec_return_value_memory_is_freed", - ); -} - -#[test] -fn test_encoded_return_value_memory_is_freed() { - call_wasm_method::( - wasm_binary_unwrap(), - "test_encoded_return_value_memory_is_freed", - ); -} - -#[test] -fn test_array_return_value_memory_is_freed() { - call_wasm_method::( - wasm_binary_unwrap(), - "test_array_return_value_memory_is_freed", - ); -} - #[test] fn test_versioning_with_new_host_works() { // We call to the new wasm binary with new host function. call_wasm_method::(wasm_binary_unwrap(), "test_versioning_works"); - // we call to the old wasm binary with a new host functions - // old versions of host functions should be called and test should be ok! - call_wasm_method::(wasm_binary_deprecated_unwrap(), "test_versioning_works"); + // We call to the old wasm binary with the deprecated host functions. + // The deprecated wasm uses V1 marshalling strategies (AllocateAndReturn*) which have + // incompatible signatures with the new V2 host functions, so we use the matching + // DeprecatedHostFunctions that provide the correct V1 function signatures. + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_versioning_works", + ); } #[test] @@ -170,6 +150,11 @@ fn test_versioning_register_only() { call_wasm_method::(wasm_binary_unwrap(), "test_versioning_register_only_works"); } +#[test] +fn test_v2_marshalling_strategies() { + call_wasm_method::(wasm_binary_unwrap(), "test_v2_marshalling_strategies"); +} + fn run_test_in_another_process( test_name: &str, test_body: impl FnOnce(), @@ -243,7 +228,6 @@ fn test_tracing() { let subscriber = TracingSubscriber(Default::default()); let _guard = tracing::subscriber::set_default(subscriber.clone()); - // Call some method to generate a trace call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); let inner = subscriber.0.lock().unwrap(); @@ -251,20 +235,99 @@ fn test_tracing() { }); } +// ========================================================================= +// V1 entry point tests (test-wasm-deprecated, host-side allocation) +// ========================================================================= + +#[test] +fn test_versioning_with_deprecated_wasm() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_versioning_works", + ); +} + +#[test] +fn test_return_data_v1() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_return_data", + ); +} + +#[test] +fn test_return_option_data_v1() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_return_option_data", + ); +} + #[test] -fn test_return_input_as_tuple() { - call_wasm_method::(wasm_binary_unwrap(), "test_return_input_as_tuple"); +fn test_get_and_return_array_v1() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_get_and_return_array", + ); +} + +#[test] +fn test_return_input_public_key_v1() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_return_input_public_key", + ); +} + +#[test] +fn test_return_input_as_tuple_v1() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_return_input_as_tuple", + ); +} + +#[test] +fn test_vec_return_value_memory_is_freed() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_vec_return_value_memory_is_freed", + ); +} + +#[test] +fn test_encoded_return_value_memory_is_freed() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_encoded_return_value_memory_is_freed", + ); +} + +#[test] +fn test_array_return_value_memory_is_freed() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_array_return_value_memory_is_freed", + ); +} + +#[test] +fn test_v1_marshalling_strategies() { + call_wasm_method::( + wasm_binary_deprecated_unwrap(), + "test_v1_marshalling_strategies", + ); } #[test] fn test_returning_option_bytes_from_a_host_function_is_efficient() { - let (result, stats_vec) = call_wasm_method_with_result::( - wasm_binary_unwrap(), + let (result, stats_vec) = call_wasm_method_with_result::( + wasm_binary_deprecated_unwrap(), "test_return_option_vec", ); result.unwrap(); - let (result, stats_bytes) = call_wasm_method_with_result::( - wasm_binary_unwrap(), + let (result, stats_bytes) = call_wasm_method_with_result::( + wasm_binary_deprecated_unwrap(), "test_return_option_bytes", ); result.unwrap(); @@ -272,31 +335,7 @@ fn test_returning_option_bytes_from_a_host_function_is_efficient() { let stats_vec = stats_vec.unwrap(); let stats_bytes = stats_bytes.unwrap(); - // The way we currently implement marshaling of `Option>` through - // the WASM FFI boundary from the host to the runtime requires that it is - // marshaled through SCALE. This is quite inefficient as it requires two - // memory allocations inside of the runtime: - // - // 1) the first allocation to copy the SCALE-encoded blob into the runtime; - // 2) and another allocation for the resulting `Vec` when decoding that blob. - // - // Both of these allocations are are as big as the `Vec` which is being - // passed to the runtime. This is especially bad when fetching big values - // from storage, as it can lead to an out-of-memory situation. - // - // Our `Option` marshaling is better; it still must go through SCALE, - // and it still requires two allocations, however since `Bytes` is zero-copy - // only the first allocation is `Vec`-sized, and the second allocation - // which creates the deserialized `Bytes` is tiny, and is only necessary because - // the underlying `Bytes` buffer from which we're deserializing gets automatically - // turned into an `Arc`. - // - // So this assertion tests that deserializing `Option` allocates less than - // deserializing `Option>`. + // With V1 entry points the host allocator is available. Option should be more + // efficient than Option> due to zero-copy deserialization. assert_eq!(stats_bytes.bytes_allocated_sum + 16 * 1024 + 8, stats_vec.bytes_allocated_sum); } - -#[test] -fn test_marshalling_strategies() { - call_wasm_method::(wasm_binary_unwrap(), "test_marshalling_strategies"); -} diff --git a/substrate/primitives/runtime-interface/tests/ui/improperly_wrapped_ri_type.stderr b/substrate/primitives/runtime-interface/tests/ui/improperly_wrapped_ri_type.stderr index 814f108f229e9..1cb8d2f1a78ec 100644 --- a/substrate/primitives/runtime-interface/tests/ui/improperly_wrapped_ri_type.stderr +++ b/substrate/primitives/runtime-interface/tests/ui/improperly_wrapped_ri_type.stderr @@ -8,11 +8,11 @@ error[E0277]: the trait bound `Option: RIType` is not satisfied AllocateAndReturnByCodec AllocateAndReturnFatPointer AllocateAndReturnPointer + ConvertAndPassAs ConvertAndReturnAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice - PassFatPointerAndRead and $N others = note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -26,11 +26,11 @@ error[E0277]: the trait bound `Option: RIType` is not satisfied AllocateAndReturnByCodec AllocateAndReturnFatPointer AllocateAndReturnPointer + ConvertAndPassAs ConvertAndReturnAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice - PassFatPointerAndRead and $N others error[E0277]: the trait bound `Option: RIType` is not satisfied @@ -43,11 +43,11 @@ error[E0277]: the trait bound `Option: RIType` is not satisfied AllocateAndReturnByCodec AllocateAndReturnFatPointer AllocateAndReturnPointer + ConvertAndPassAs ConvertAndReturnAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice - PassFatPointerAndRead and $N others error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied @@ -57,6 +57,7 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied | ^^^^^^^^^^ the trait `FromFFIValue<'_>` is not implemented for `Option` | = help: the following other types implement trait `FromFFIValue<'a>`: + ConvertAndPassAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice<&'a [T]> @@ -64,7 +65,6 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied PassFatPointerAndRead<&'a str> PassFatPointerAndRead> PassFatPointerAndReadOption<&'a [u8]> - PassFatPointerAndReadWrite<&'a mut [u8]> and $N others error[E0277]: the trait bound `Option: IntoFFIValue` is not satisfied @@ -91,6 +91,7 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied | ^ the trait `FromFFIValue<'_>` is not implemented for `Option` | = help: the following other types implement trait `FromFFIValue<'a>`: + ConvertAndPassAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice<&'a [T]> @@ -98,7 +99,6 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied PassFatPointerAndRead<&'a str> PassFatPointerAndRead> PassFatPointerAndReadOption<&'a [u8]> - PassFatPointerAndReadWrite<&'a mut [u8]> and $N others error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied @@ -108,6 +108,7 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied | ^^^^^^^^^^^^^^^^^^^^ the trait `FromFFIValue<'_>` is not implemented for `Option` | = help: the following other types implement trait `FromFFIValue<'a>`: + ConvertAndPassAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice<&'a [T]> @@ -115,7 +116,6 @@ error[E0277]: the trait bound `Option: FromFFIValue<'_>` is not satisfied PassFatPointerAndRead<&'a str> PassFatPointerAndRead> PassFatPointerAndReadOption<&'a [u8]> - PassFatPointerAndReadWrite<&'a mut [u8]> and $N others = note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -129,11 +129,11 @@ error[E0277]: the trait bound `Option: RIType` is not satisfied AllocateAndReturnByCodec AllocateAndReturnFatPointer AllocateAndReturnPointer + ConvertAndPassAs ConvertAndReturnAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice - PassFatPointerAndRead and $N others error[E0277]: the trait bound `Option: RIType` is not satisfied @@ -146,9 +146,9 @@ error[E0277]: the trait bound `Option: RIType` is not satisfied AllocateAndReturnByCodec AllocateAndReturnFatPointer AllocateAndReturnPointer + ConvertAndPassAs ConvertAndReturnAs PassAs PassFatPointerAndDecode PassFatPointerAndDecodeSlice - PassFatPointerAndRead and $N others diff --git a/substrate/primitives/runtime/src/offchain/http.rs b/substrate/primitives/runtime/src/offchain/http.rs index 99e7937ae9894..9c304e07f2672 100644 --- a/substrate/primitives/runtime/src/offchain/http.rs +++ b/substrate/primitives/runtime/src/offchain/http.rs @@ -203,10 +203,8 @@ impl<'a, I: AsRef<[u8]>, T: IntoIterator> Request<'a, T> { /// Err is returned in case the deadline is reached /// or the request timeouts. pub fn send(self) -> Result { - let meta = &[]; - // start an http request. - let id = sp_io::offchain::http_request_start(self.method.as_ref(), self.url, meta) + let id = sp_io::offchain::http_request_start(self.method.as_ref(), self.url, vec![]) .map_err(|_| HttpError::IoError)?; // add custom headers diff --git a/substrate/primitives/state-machine/src/basic.rs b/substrate/primitives/state-machine/src/basic.rs index 087c723e04f61..4069f910423fe 100644 --- a/substrate/primitives/state-machine/src/basic.rs +++ b/substrate/primitives/state-machine/src/basic.rs @@ -41,12 +41,19 @@ use sp_trie::{empty_child_trie_root, LayoutV0, LayoutV1, TrieConfiguration}; pub struct BasicExternalities { overlay: OverlayedChanges, extensions: Extensions, + state_version: StateVersion, + last_cursor: Option>, } impl BasicExternalities { /// Create a new instance of `BasicExternalities` pub fn new(inner: Storage) -> Self { - BasicExternalities { overlay: inner.into(), extensions: Default::default() } + BasicExternalities { + overlay: inner.into(), + extensions: Default::default(), + state_version: StateVersion::default(), + last_cursor: None, + } } /// New basic externalities with empty storage. @@ -268,7 +275,11 @@ impl Externalities for BasicExternalities { self.overlay.append_storage(key, element, Default::default); } - fn storage_root(&mut self, state_version: StateVersion) -> Vec { + fn set_runtime_state_version(&mut self, state_version: StateVersion) { + self.state_version = state_version; + } + + fn storage_root(&mut self) -> Vec { let mut top = self .overlay .changes_mut() @@ -279,7 +290,7 @@ impl Externalities for BasicExternalities { // type of child trie support. let empty_hash = empty_child_trie_root::>(); for child_info in self.overlay.children().map(|d| d.1.clone()).collect::>() { - let child_root = self.child_storage_root(&child_info, state_version); + let child_root = self.child_storage_root(&child_info); if empty_hash[..] == child_root[..] { top.remove(child_info.prefixed_storage_key().as_slice()); } else { @@ -287,22 +298,18 @@ impl Externalities for BasicExternalities { } } - match state_version { + match self.state_version { StateVersion::V0 => LayoutV0::::trie_root(top).as_ref().into(), StateVersion::V1 => LayoutV1::::trie_root(top).as_ref().into(), } } - fn child_storage_root( - &mut self, - child_info: &ChildInfo, - state_version: StateVersion, - ) -> Vec { + fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec { if let Some((data, child_info)) = self.overlay.child_changes_mut(child_info.storage_key()) { let delta = data.into_iter().map(|(k, v)| (k.as_ref(), v.value().map(|v| v.as_slice()))); crate::in_memory_backend::new_in_mem::() - .child_storage_root(&child_info, delta, state_version) + .child_storage_root(&child_info, delta, self.state_version) .0 } else { empty_child_trie_root::>() @@ -345,6 +352,14 @@ impl Externalities for BasicExternalities { fn get_read_and_written_keys(&self) -> Vec<(Vec, u32, u32, bool)> { unimplemented!("get_read_and_written_keys is not supported in Basic") } + + fn store_last_cursor(&mut self, cursor: &[u8]) { + self.last_cursor = Some(cursor.to_vec()); + } + + fn take_last_cursor(&mut self) -> Option> { + self.last_cursor.take() + } } impl sp_externalities::ExtensionStore for BasicExternalities { @@ -390,7 +405,7 @@ mod tests { "39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa", ); - assert_eq!(&ext.storage_root(StateVersion::default())[..], &root); + assert_eq!(&ext.storage_root()[..], &root); } #[test] diff --git a/substrate/primitives/state-machine/src/ext.rs b/substrate/primitives/state-machine/src/ext.rs index 29c48348889c0..e5c8994011a11 100644 --- a/substrate/primitives/state-machine/src/ext.rs +++ b/substrate/primitives/state-machine/src/ext.rs @@ -71,6 +71,11 @@ where /// Extensions registered with this instance. #[cfg(feature = "std")] extensions: Option>, + /// Fallback state version used by `storage_root` when no `RuntimeStateVersionExt` is + /// registered. In no-std builds (where extensions are not supported) this is the only source. + state_version: StateVersion, + /// Last cursor of a storage operation. + last_cursor: Option>, } impl<'a, H, B> Ext<'a, H, B> @@ -81,7 +86,7 @@ where /// Create a new `Ext`. #[cfg(not(feature = "std"))] pub fn new(overlay: &'a mut OverlayedChanges, backend: &'a B) -> Self { - Ext { overlay, backend, id: 0 } + Ext { overlay, backend, id: 0, state_version: StateVersion::default(), last_cursor: None } } /// Create a new `Ext` from overlayed changes and read-only backend @@ -96,8 +101,17 @@ where backend, id: rand::random(), extensions: extensions.map(OverlayedExtensions::new), + state_version: StateVersion::default(), + last_cursor: None, } } + + /// Override the state version used by `storage_root`. Chained-call equivalent of + /// [`Externalities::set_runtime_state_version`]. + pub fn with_state_version(mut self, state_version: StateVersion) -> Self { + self.state_version = state_version; + self + } } #[cfg(test)] @@ -475,10 +489,14 @@ where }); } - fn storage_root(&mut self, state_version: StateVersion) -> Vec { + fn set_runtime_state_version(&mut self, state_version: StateVersion) { + self.state_version = state_version; + } + + fn storage_root(&mut self) -> Vec { let _guard = guard(); - let (root, _cached) = self.overlay.storage_root(self.backend, state_version); + let (root, _cached) = self.overlay.storage_root(self.backend, self.state_version); trace!( target: "state", @@ -491,16 +509,12 @@ where root.encode() } - fn child_storage_root( - &mut self, - child_info: &ChildInfo, - state_version: StateVersion, - ) -> Vec { + fn child_storage_root(&mut self, child_info: &ChildInfo) -> Vec { let _guard = guard(); let (root, _cached) = self .overlay - .child_storage_root(child_info, self.backend, state_version) + .child_storage_root(child_info, self.backend, self.state_version) .expect(EXT_NOT_ALLOWED_TO_FAIL); trace!( @@ -577,6 +591,14 @@ where Ok(()) } + fn store_last_cursor(&mut self, cursor: &[u8]) { + self.last_cursor = Some(cursor.to_vec()); + } + + fn take_last_cursor(&mut self) -> Option> { + self.last_cursor.take() + } + fn wipe(&mut self) { for _ in 0..self.overlay.transaction_depth() { self.overlay.rollback_transaction().expect(BENCHMARKING_FN); diff --git a/substrate/primitives/state-machine/src/lib.rs b/substrate/primitives/state-machine/src/lib.rs index c552c6f639218..81222a7294fa8 100644 --- a/substrate/primitives/state-machine/src/lib.rs +++ b/substrate/primitives/state-machine/src/lib.rs @@ -1991,7 +1991,7 @@ mod tests { let mut ext = Ext::new(&mut overlay, &backend, None); ext.set_child_storage(&child_info_1, b"abc".to_vec(), b"def".to_vec()); ext.set_child_storage(&child_info_2, b"abc".to_vec(), b"def".to_vec()); - ext.storage_root(state_version); + ext.storage_root(); overlay.drain_storage_changes(&backend, state_version).unwrap().transaction }; let mut duplicate = false; diff --git a/substrate/primitives/state-machine/src/overlayed_changes/mod.rs b/substrate/primitives/state-machine/src/overlayed_changes/mod.rs index dff75bf277931..288094b8d5291 100644 --- a/substrate/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/substrate/primitives/state-machine/src/overlayed_changes/mod.rs @@ -1050,9 +1050,9 @@ mod tests { let mut ext = Ext::new(&mut overlay, &backend, None); let root = "39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa"; - assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root); + assert_eq!(bytes2hex("", &ext.storage_root()), root); // Calling a second time should use it from the cache - assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root); + assert_eq!(bytes2hex("", &ext.storage_root()), root); } // Check that the storage root is recalculated @@ -1060,7 +1060,7 @@ mod tests { let mut ext = Ext::new(&mut overlay, &backend, None); let root = "5c0a4e35cb967de785e1cb8743e6f24b6ff6d45155317f2078f6eb3fc4ff3e3d"; - assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root); + assert_eq!(bytes2hex("", &ext.storage_root()), root); } #[test] @@ -1095,7 +1095,6 @@ mod tests { #[test] fn overlayed_child_storage_root_works() { - let state_version = StateVersion::default(); let child_info = ChildInfo::new_default(b"Child1"); let child_info = &child_info; let backend = new_in_mem::(); @@ -1113,18 +1112,12 @@ mod tests { let child_root = "c02965e1df4dc5baf6977390ce67dab1d7a9b27a87c1afe27b50d29cc990e0f5"; let root = "eafb765909c3ed5afd92a0c564acf4620d0234b31702e8e8e9b48da72a748838"; - assert_eq!( - bytes2hex("", &ext.child_storage_root(child_info, state_version)), - child_root, - ); + assert_eq!(bytes2hex("", &ext.child_storage_root(child_info)), child_root,); - assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root); + assert_eq!(bytes2hex("", &ext.storage_root()), root); // Calling a second time should use it from the cache - assert_eq!( - bytes2hex("", &ext.child_storage_root(child_info, state_version)), - child_root, - ); + assert_eq!(bytes2hex("", &ext.child_storage_root(child_info)), child_root,); } } diff --git a/substrate/primitives/state-machine/src/read_only.rs b/substrate/primitives/state-machine/src/read_only.rs index e5da2fde1fd55..1b80612a650d1 100644 --- a/substrate/primitives/state-machine/src/read_only.rs +++ b/substrate/primitives/state-machine/src/read_only.rs @@ -26,7 +26,7 @@ use core::{ }; use hash_db::Hasher; use sp_core::{ - storage::{ChildInfo, StateVersion, TrackedStorageKey}, + storage::{ChildInfo, TrackedStorageKey}, traits::Externalities, }; use sp_externalities::MultiRemovalResults; @@ -60,12 +60,13 @@ where #[derive(Debug)] pub struct ReadOnlyExternalities<'a, H: Hasher, B: 'a + Backend> { backend: &'a B, + last_cursor: Option>, _phantom: PhantomData, } impl<'a, H: Hasher, B: 'a + Backend> From<&'a B> for ReadOnlyExternalities<'a, H, B> { fn from(backend: &'a B) -> Self { - ReadOnlyExternalities { backend, _phantom: PhantomData } + ReadOnlyExternalities { backend, last_cursor: None, _phantom: PhantomData } } } @@ -172,15 +173,11 @@ where unimplemented!("storage_append is not supported in ReadOnlyExternalities") } - fn storage_root(&mut self, _state_version: StateVersion) -> Vec { + fn storage_root(&mut self) -> Vec { unimplemented!("storage_root is not supported in ReadOnlyExternalities") } - fn child_storage_root( - &mut self, - _child_info: &ChildInfo, - _state_version: StateVersion, - ) -> Vec { + fn child_storage_root(&mut self, _child_info: &ChildInfo) -> Vec { unimplemented!("child_storage_root is not supported in ReadOnlyExternalities") } @@ -196,6 +193,14 @@ where unimplemented!("Transactions are not supported by ReadOnlyExternalities"); } + fn store_last_cursor(&mut self, cursor: &[u8]) { + self.last_cursor = Some(cursor.to_vec()); + } + + fn take_last_cursor(&mut self) -> Option> { + self.last_cursor.take() + } + fn wipe(&mut self) {} fn commit(&mut self) {} diff --git a/substrate/primitives/state-machine/src/testing.rs b/substrate/primitives/state-machine/src/testing.rs index 4d044e6933437..d35e3b475ee9d 100644 --- a/substrate/primitives/state-machine/src/testing.rs +++ b/substrate/primitives/state-machine/src/testing.rs @@ -420,7 +420,7 @@ mod tests { let root = array_bytes::hex_n_into_unchecked::<_, H256, 32>( "ed4d8c799d996add422395a6abd7545491d40bd838d738afafa1b8a4de625489", ); - assert_eq!(H256::from_slice(ext.storage_root(Default::default()).as_slice()), root); + assert_eq!(H256::from_slice(ext.storage_root().as_slice()), root); } #[test] diff --git a/substrate/primitives/virtualization/src/host_functions.rs b/substrate/primitives/virtualization/src/host_functions.rs index 6dd8d7ce1b760..8eaa78d7905d2 100644 --- a/substrate/primitives/virtualization/src/host_functions.rs +++ b/substrate/primitives/virtualization/src/host_functions.rs @@ -246,17 +246,19 @@ impl IntoI64 for u32 { const MAX: i64 = u32::MAX as i64; } -impl + IntoI64, E: Into + strum::EnumCount> From> for i64 { - fn from(result: RIIntResult) -> Self { +impl + IntoI64, E: Into + strum::EnumCount> TryFrom> for i64 { + type Error = (); + + fn try_from(result: RIIntResult) -> Result { match result { - RIIntResult::Ok(value) => value.into(), + RIIntResult::Ok(value) => Ok(value.into()), RIIntResult::Err(e) => { let error_code: i64 = e.into(); - assert!( - error_code < 0 && error_code >= -(E::COUNT as i64), - "Error variant index out of bounds" - ); - error_code + if error_code < 0 && error_code >= -(E::COUNT as i64) { + Ok(error_code) + } else { + Err(()) + } }, } } diff --git a/substrate/primitives/wasm-interface/src/lib.rs b/substrate/primitives/wasm-interface/src/lib.rs index 0b01b41e3c615..cfc863e8b5ed7 100644 --- a/substrate/primitives/wasm-interface/src/lib.rs +++ b/substrate/primitives/wasm-interface/src/lib.rs @@ -310,6 +310,8 @@ pub trait FunctionContext { fn allocate_memory(&mut self, size: WordSize) -> Result>; /// Deallocate a given memory instance. fn deallocate_memory(&mut self, ptr: Pointer) -> Result<()>; + /// Take the input data from the host state. + fn take_input_data(&mut self) -> Result>; /// Registers a panic error message within the executor. /// /// This is meant to be used in situations where the runtime diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index a432322aed8f1..2693997a848cd 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -919,12 +919,12 @@ fn test_read_storage() { sp_io::storage::set(KEY, b"test"); let mut v = [0u8; 4]; - let r = sp_io::storage::read(KEY, &mut v, 0); + let r = sp_io::storage::read_exact(KEY, &mut v, 0); assert_eq!(r, Some(4)); assert_eq!(&v, b"test"); let mut v = [0u8; 4]; - let r = sp_io::storage::read(KEY, &mut v, 4); + let r = sp_io::storage::read_exact(KEY, &mut v, 4); assert_eq!(r, Some(0)); assert_eq!(&v, &[0, 0, 0, 0]); } @@ -935,12 +935,12 @@ fn test_read_child_storage() { sp_io::default_child_storage::set(STORAGE_KEY, KEY, b"test"); let mut v = [0u8; 4]; - let r = sp_io::default_child_storage::read(STORAGE_KEY, KEY, &mut v, 0); + let r = sp_io::default_child_storage::read_exact(STORAGE_KEY, KEY, &mut v, 0); assert_eq!(r, Some(4)); assert_eq!(&v, b"test"); let mut v = [0u8; 4]; - let r = sp_io::default_child_storage::read(STORAGE_KEY, KEY, &mut v, 8); + let r = sp_io::default_child_storage::read_exact(STORAGE_KEY, KEY, &mut v, 8); assert_eq!(r, Some(0)); assert_eq!(&v, &[0, 0, 0, 0]); } @@ -957,9 +957,9 @@ fn test_witness(proof: StorageProof, root: crate::Hash) { None, ); assert!(ext.storage(b"value3").is_some()); - assert!(ext.storage_root(Default::default()).as_slice() == &root[..]); + assert!(ext.storage_root().as_slice() == &root[..]); ext.place_storage(vec![0], Some(vec![1])); - assert!(ext.storage_root(Default::default()).as_slice() != &root[..]); + assert!(ext.storage_root().as_slice() != &root[..]); } /// Some tests require the hashed keys of the storage. As the values of hashed keys are not trivial diff --git a/substrate/test-utils/runtime/src/substrate_test_pallet.rs b/substrate/test-utils/runtime/src/substrate_test_pallet.rs index 7cf931f0e744d..4018ec2f3b2e3 100644 --- a/substrate/test-utils/runtime/src/substrate_test_pallet.rs +++ b/substrate/test-utils/runtime/src/substrate_test_pallet.rs @@ -194,12 +194,13 @@ pub mod pallet { impl Pallet { fn execute_read(read: u32, panic_at_end: bool) -> DispatchResult { let mut next_key = vec![]; + let mut next = Vec::new(); for _ in 0..(read as usize) { - if let Some(next) = sp_io::storage::next_key(&next_key) { + if sp_io::storage::next_key(&next_key, &mut next) { // Read the value sp_io::storage::get(&next); - next_key = next; + core::mem::swap(&mut next_key, &mut next); } else { if panic_at_end { return Ok(()); diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 372c7b5a8b38f..b0ffa2253d0c1 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -1152,8 +1152,11 @@ mod tests { .await .expect("Can't read state snapshot file") .execute_with(|| { - let key = - sp_io::storage::next_key(&[]).expect("some key must exist in the snapshot"); + let mut key = Vec::new(); + assert!( + sp_io::storage::next_key(&[], &mut key), + "some key must exist in the snapshot" + ); assert!(sp_io::storage::get(&key).is_some()); key }); diff --git a/substrate/utils/frame/storage-access-test-runtime/Cargo.toml b/substrate/utils/frame/storage-access-test-runtime/Cargo.toml index 5093653d2dc83..0014edbdbb5a4 100644 --- a/substrate/utils/frame/storage-access-test-runtime/Cargo.toml +++ b/substrate/utils/frame/storage-access-test-runtime/Cargo.toml @@ -17,6 +17,7 @@ workspace = true codec = { features = ["derive"], workspace = true } cumulus-pallet-parachain-system = { workspace = true, optional = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true } sp-trie = { workspace = true } @@ -31,6 +32,7 @@ std = [ "codec/std", "cumulus-pallet-parachain-system/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-state-machine/std", "sp-trie/std", diff --git a/substrate/utils/frame/storage-access-test-runtime/src/lib.rs b/substrate/utils/frame/storage-access-test-runtime/src/lib.rs index 6b22e37aec111..d11f657faf684 100644 --- a/substrate/utils/frame/storage-access-test-runtime/src/lib.rs +++ b/substrate/utils/frame/storage-access-test-runtime/src/lib.rs @@ -174,9 +174,10 @@ pub fn oom(_: core::alloc::Layout) -> ! { #[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))] #[no_mangle] -pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 { +pub extern "C" fn validate_block(arguments_len: usize) -> u64 { type Block = generic::Block, OpaqueExtrinsic>; - let params = unsafe { alloc::slice::from_raw_parts(params, len) }; - proceed_storage_access::(params); + let mut buf = alloc::vec![0u8; arguments_len]; + sp_io::input::read(&mut buf[..]); + proceed_storage_access::(&buf); 1 } diff --git a/substrate/utils/wasm-builder/src/metadata_hash.rs b/substrate/utils/wasm-builder/src/metadata_hash.rs index 1003f2d18eafd..394f394159f71 100644 --- a/substrate/utils/wasm-builder/src/metadata_hash.rs +++ b/substrate/utils/wasm-builder/src/metadata_hash.rs @@ -37,6 +37,8 @@ type HostFunctions = ( sp_io::storage::HostFunctions, // The hashing functions. sp_io::hashing::HostFunctions, + // Input reading. + sp_io::input::HostFunctions, ); /// Generate the metadata hash.