Skip to content

Commit f617709

Browse files
volivatomaka
andauthored
feat: implement state_getReadProof (#2136)
* feat: implement state_getReadProof * cleanup code, remove unwraps * expose merkle proofs on sync_service storage * wire up get merkle proofs with rpc method * merge proofs without decoding them * Update light-base/src/sync_service.rs Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com> * Update light-base/src/json_rpc_service/background.rs Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com> * minimize proofs and merge them at the end * fix build * fix wasm build by using alloc::vec * Fix the straight-forward concerns * Add CHANGELOG entry * Revert changes to proof_decode.rs * Minor doc tweak * Extract `build_proof_entry` from proof decoder * Simplify `closest_ancestor_in_proof` * Add `proof_entry` * Update `minimize_proof` * Finish the changes * Fix sync service * Fix `minimize_proof` * Fix minimize_proof --------- Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
1 parent 400aabf commit f617709

File tree

7 files changed

+459
-128
lines changed

7 files changed

+459
-128
lines changed

lib/src/json_rpc/methods.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ define_methods! {
432432
state_getKeysPaged(prefix: Option<HexString>, count: u32, start_key: Option<HexString>, hash: Option<HashHexString>) -> Vec<HexString> [state_getKeysPagedAt],
433433
state_getMetadata(hash: Option<HashHexString>) -> HexString,
434434
state_getPairs() -> (), // TODO:
435-
state_getReadProof() -> (), // TODO:
435+
state_getReadProof(keys: Vec<HexString>, at: Option<HashHexString>) -> ReadProof,
436436
state_getRuntimeVersion(at: Option<HashHexString>) -> RuntimeVersion<'a> [chain_getRuntimeVersion],
437437
state_getStorage(key: HexString, hash: Option<HashHexString>) -> HexString [state_getStorageAt],
438438
state_getStorageHash() -> () [state_getStorageHashAt], // TODO:
@@ -1049,6 +1049,12 @@ pub struct SystemHealth {
10491049
pub should_have_peers: bool,
10501050
}
10511051

1052+
#[derive(Debug, Clone, serde::Serialize)]
1053+
pub struct ReadProof {
1054+
pub at: HashHexString,
1055+
pub proof: Vec<HexString>,
1056+
}
1057+
10521058
#[derive(Debug, Clone, serde::Serialize)]
10531059
pub struct SystemPeer {
10541060
#[serde(rename = "peerId")]

lib/src/trie.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ mod nibble;
109109

110110
pub mod branch_search;
111111
pub mod calculate_root;
112+
pub mod minimize_proof;
112113
pub mod prefix_proof;
113114
pub mod proof_decode;
114115
pub mod proof_encode;

lib/src/trie/minimize_proof.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Smoldot
2+
// Copyright (C) 2025 Pierre Krieger
3+
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4+
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
use crate::trie::{
19+
bytes_to_nibbles,
20+
proof_decode::{self, DecodedTrieProof, StorageValue},
21+
proof_encode::ProofBuilder,
22+
};
23+
use alloc::vec::Vec;
24+
use hashbrown::HashSet;
25+
26+
pub use proof_decode::ParseError;
27+
28+
/// Error potentially returned by [`minimize_proof`].
29+
#[derive(Debug, derive_more::Display, derive_more::Error)]
30+
pub enum MinimizeProofError {
31+
/// Desired key can't be found in the proof.
32+
KeyNotFound,
33+
/// Proof doesn't contain enough information and isn't valid.
34+
IncompleteProof,
35+
}
36+
37+
/// Minimizes a single-key proof, removing all entries that are not related to the proof for
38+
/// that key.
39+
///
40+
/// Returns the resulting proof, encoded.
41+
pub fn minimize_proof<T: AsRef<[u8]>>(
42+
decoded_proof: &DecodedTrieProof<T>,
43+
trie_root_merkle_value: &[u8; 32],
44+
key: &[u8],
45+
) -> Result<Vec<u8>, MinimizeProofError> {
46+
let mut builder = ProofBuilder::new();
47+
48+
let mut key_nibbles_if_first_iter =
49+
Some(bytes_to_nibbles(key.iter().copied()).collect::<Vec<_>>());
50+
51+
// Query a missing node and provide its value. Stop when the proof is complete.
52+
loop {
53+
let Some(missing) = builder
54+
.missing_node_values()
55+
.next()
56+
.map(|v| Vec::from(v))
57+
.or_else(|| key_nibbles_if_first_iter.take())
58+
else {
59+
break;
60+
};
61+
62+
if let Some(ancestor_key) = decoded_proof
63+
.closest_ancestor_in_proof(trie_root_merkle_value, missing.iter().copied())
64+
.map_err(|_| MinimizeProofError::KeyNotFound)?
65+
{
66+
let ancestor = decoded_proof
67+
.proof_entry(trie_root_merkle_value, ancestor_key)
68+
.unwrap();
69+
builder.set_node_value(
70+
&missing,
71+
ancestor.node_value,
72+
match ancestor.trie_node_info.storage_value {
73+
StorageValue::Known { value, .. } => Some(value),
74+
_ => None,
75+
},
76+
);
77+
} else {
78+
// Add the root node in the output.
79+
// This should only ever happen if the input key is completely outside of the trie.
80+
debug_assert!(itertools::equal(
81+
missing.iter().copied(),
82+
bytes_to_nibbles(key.iter().copied())
83+
));
84+
let root = decoded_proof
85+
.trie_root_proof_entry(trie_root_merkle_value)
86+
.ok_or(MinimizeProofError::KeyNotFound)?;
87+
builder.set_node_value(&missing, root.node_value, None);
88+
};
89+
}
90+
91+
Ok(builder.build_to_vec())
92+
}
93+
94+
/// Merges multiple proofs into a single one, removing common entries.
95+
pub fn merge_proofs<'a>(mut proofs: impl Iterator<Item = &'a [u8]>) -> Result<Vec<u8>, ParseError> {
96+
// Decode each element of `proofs` and collect the entries in a `HashSet`.
97+
let entries = proofs.try_fold(
98+
HashSet::with_hasher(fnv::FnvBuildHasher::default()),
99+
|mut acc, proof| {
100+
let proof_entries = proof_decode::decode_proof(&proof)?;
101+
acc.extend(proof_entries);
102+
Ok(acc)
103+
},
104+
)?;
105+
106+
// Encode the output proof.
107+
let mut ret = Vec::with_capacity(8 + entries.iter().map(|e| e.len() + 8).sum::<usize>());
108+
ret.extend_from_slice(crate::util::encode_scale_compact_usize(entries.len()).as_ref());
109+
for entry in entries {
110+
ret.extend_from_slice(crate::util::encode_scale_compact_usize(entry.len()).as_ref());
111+
ret.extend_from_slice(entry);
112+
}
113+
Ok(ret)
114+
}

0 commit comments

Comments
 (0)