From 8b546d764b98b7eaa1430da99136cfef59ab1d7c Mon Sep 17 00:00:00 2001 From: ndk Date: Fri, 20 Mar 2026 14:08:58 +0100 Subject: [PATCH 1/3] Add support for ext_trie_blake2_256_verify_proof host functions Implement ext_trie_blake2_256_verify_proof_version_1 and _version_2 which were previously stubbed with host_fn_not_implemented!(). These host functions are needed by runtimes that perform Merkle proof verification, such as Polkadot Bulletin Chain's pallet-transaction-storage. The implementation uses smoldot's existing proof_decode module to decode and verify the proof, then looks up the key and compares against the expected value. For state version V1 (version_2 only), hashed storage values (>= 33 bytes) are handled by comparing blake2b hashes. --- lib/src/executor/host.rs | 55 ++++++++++++++++++++++++++++++++++++++-- wasm-node/CHANGELOG.md | 4 +++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/src/executor/host.rs b/lib/src/executor/host.rs index 5f7aa88626..57c1dafb4b 100644 --- a/lib/src/executor/host.rs +++ b/lib/src/executor/host.rs @@ -2108,8 +2108,59 @@ impl ReadyToRun { }, } } - HostFunction::ext_trie_blake2_256_verify_proof_version_1 => host_fn_not_implemented!(), - HostFunction::ext_trie_blake2_256_verify_proof_version_2 => host_fn_not_implemented!(), + HostFunction::ext_trie_blake2_256_verify_proof_version_1 + | HostFunction::ext_trie_blake2_256_verify_proof_version_2 => { + let state_version = if matches!( + host_fn, + HostFunction::ext_trie_blake2_256_verify_proof_version_2 + ) { + expect_state_version!(4) + } else { + TrieEntryVersion::V0 + }; + + let root = expect_pointer_constant_size!(0, 32); + let proof = expect_pointer_size!(1).as_ref().to_vec(); + let key = expect_pointer_size!(2).as_ref().to_vec(); + let value = expect_pointer_size!(3).as_ref().to_vec(); + + let outcome = match trie::proof_decode::decode_and_verify_proof( + trie::proof_decode::Config { proof: &proof }, + ) { + Ok(decoded) => match decoded.trie_node_info( + &root, + trie::bytes_to_nibbles(key.iter().copied()), + ) { + Ok(info) => match info.storage_value { + trie::proof_decode::StorageValue::Known { + value: found_value, + .. + } => found_value == &value[..], + trie::proof_decode::StorageValue::HashKnownValueMissing(hash) => { + // The proof contains only the hash of the value. + // This happens with state version V1 where values + // >= 33 bytes are stored as their blake2b hash. + // Verify by hashing the expected value and comparing. + matches!(state_version, TrieEntryVersion::V1) + && *hash + == <[u8; 32]>::try_from( + blake2_rfc::blake2b::blake2b(32, &[], &value) + .as_bytes(), + ) + .unwrap_or_else(|_| unreachable!()) + } + trie::proof_decode::StorageValue::None => false, + }, + Err(_) => false, + }, + Err(_) => false, + }; + + HostVm::ReadyToRun(ReadyToRun { + resume_value: Some(vm::WasmValue::I32(if outcome { 1 } else { 0 })), + inner: self.inner, + }) + } HostFunction::ext_trie_keccak_256_verify_proof_version_1 => host_fn_not_implemented!(), HostFunction::ext_trie_keccak_256_verify_proof_version_2 => host_fn_not_implemented!(), HostFunction::ext_misc_print_num_version_1 => { diff --git a/wasm-node/CHANGELOG.md b/wasm-node/CHANGELOG.md index f17a987ad9..dc5898c9b7 100644 --- a/wasm-node/CHANGELOG.md +++ b/wasm-node/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- Add support for the `ext_trie_blake2_256_verify_proof_version_1` and `ext_trie_blake2_256_verify_proof_version_2` host functions. These are used by runtimes that perform Merkle proof verification (e.g. Polkadot Bulletin Chain's `pallet-transaction-storage`). + ### Fixed - Fix execution proofs with inputs bigger than 1MiB failing due to protocol limits. Smoldot now uses storage-on-demand for large inputs. ([#2196](https://github.com/smol-dot/smoldot/pull/2196)) From 39c585b337c884894cf8e06648acf3f44e306c06 Mon Sep 17 00:00:00 2001 From: Andrii Date: Tue, 31 Mar 2026 10:59:10 +0200 Subject: [PATCH 2/3] Update wasm-node/CHANGELOG.md Co-authored-by: Pierre Krieger --- wasm-node/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm-node/CHANGELOG.md b/wasm-node/CHANGELOG.md index dc5898c9b7..4ff91b4c65 100644 --- a/wasm-node/CHANGELOG.md +++ b/wasm-node/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- Add support for the `ext_trie_blake2_256_verify_proof_version_1` and `ext_trie_blake2_256_verify_proof_version_2` host functions. These are used by runtimes that perform Merkle proof verification (e.g. Polkadot Bulletin Chain's `pallet-transaction-storage`). +- Add support for the `ext_trie_blake2_256_verify_proof_version_1` and `ext_trie_blake2_256_verify_proof_version_2` host functions. ([#2221](https://github.com/smol-dot/smoldot/pull/2221)) ### Fixed From ffc9d9185ceeb7a0fb6738fa119b4ed63d27556a Mon Sep 17 00:00:00 2001 From: ndk Date: Tue, 31 Mar 2026 11:10:58 +0200 Subject: [PATCH 3/3] Fix formatting in ext_trie_blake2_256_verify_proof implementation Co-Authored-By: Claude Opus 4.6 --- lib/src/executor/host.rs | 58 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/src/executor/host.rs b/lib/src/executor/host.rs index 57c1dafb4b..a80ab52a2c 100644 --- a/lib/src/executor/host.rs +++ b/lib/src/executor/host.rs @@ -2124,37 +2124,37 @@ impl ReadyToRun { let key = expect_pointer_size!(2).as_ref().to_vec(); let value = expect_pointer_size!(3).as_ref().to_vec(); - let outcome = match trie::proof_decode::decode_and_verify_proof( - trie::proof_decode::Config { proof: &proof }, - ) { - Ok(decoded) => match decoded.trie_node_info( - &root, - trie::bytes_to_nibbles(key.iter().copied()), - ) { - Ok(info) => match info.storage_value { - trie::proof_decode::StorageValue::Known { - value: found_value, - .. - } => found_value == &value[..], - trie::proof_decode::StorageValue::HashKnownValueMissing(hash) => { - // The proof contains only the hash of the value. - // This happens with state version V1 where values - // >= 33 bytes are stored as their blake2b hash. - // Verify by hashing the expected value and comparing. - matches!(state_version, TrieEntryVersion::V1) - && *hash - == <[u8; 32]>::try_from( - blake2_rfc::blake2b::blake2b(32, &[], &value) - .as_bytes(), - ) - .unwrap_or_else(|_| unreachable!()) - } - trie::proof_decode::StorageValue::None => false, + let outcome = + match trie::proof_decode::decode_and_verify_proof(trie::proof_decode::Config { + proof: &proof, + }) { + Ok(decoded) => match decoded + .trie_node_info(&root, trie::bytes_to_nibbles(key.iter().copied())) + { + Ok(info) => match info.storage_value { + trie::proof_decode::StorageValue::Known { + value: found_value, + .. + } => found_value == &value[..], + trie::proof_decode::StorageValue::HashKnownValueMissing(hash) => { + // The proof contains only the hash of the value. + // This happens with state version V1 where values + // >= 33 bytes are stored as their blake2b hash. + // Verify by hashing the expected value and comparing. + matches!(state_version, TrieEntryVersion::V1) + && *hash + == <[u8; 32]>::try_from( + blake2_rfc::blake2b::blake2b(32, &[], &value) + .as_bytes(), + ) + .unwrap_or_else(|_| unreachable!()) + } + trie::proof_decode::StorageValue::None => false, + }, + Err(_) => false, }, Err(_) => false, - }, - Err(_) => false, - }; + }; HostVm::ReadyToRun(ReadyToRun { resume_value: Some(vm::WasmValue::I32(if outcome { 1 } else { 0 })),