diff --git a/lib/benches/proof-decode.rs b/lib/benches/proof-decode.rs
index f827c4e4c3..1fe33f988f 100644
--- a/lib/benches/proof-decode.rs
+++ b/lib/benches/proof-decode.rs
@@ -15,6 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use std::iter;
+
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use rand::Rng as _;
@@ -95,7 +97,15 @@ fn benchmark_proof_decode(c: &mut Criterion) {
.map(|_| rand::thread_rng().gen_range(0..16).try_into().unwrap())
.collect::>()
},
- |key| i.next_key(&proof.trie_root, &key, false, &[], true),
+ |key| {
+ i.next_key(
+ &proof.trie_root,
+ key.iter().copied(),
+ false,
+ iter::empty(),
+ true,
+ )
+ },
BatchSize::SmallInput,
)
},
@@ -114,7 +124,7 @@ fn benchmark_proof_decode(c: &mut Criterion) {
.map(|_| rand::thread_rng().gen_range(0..16).try_into().unwrap())
.collect::>()
},
- |key| i.closest_descendant_merkle_value(&proof.trie_root, &key),
+ |key| i.closest_descendant_merkle_value(&proof.trie_root, key.iter().copied()),
BatchSize::SmallInput,
)
},
@@ -133,7 +143,7 @@ fn benchmark_proof_decode(c: &mut Criterion) {
.map(|_| rand::thread_rng().gen_range(0..16).try_into().unwrap())
.collect::>()
},
- |key| i.closest_ancestor_in_proof(&proof.trie_root, &key),
+ |key| i.closest_ancestor_in_proof(&proof.trie_root, key.iter().copied()),
BatchSize::SmallInput,
)
},
diff --git a/lib/src/sync/warp_sync.rs b/lib/src/sync/warp_sync.rs
index ab9fc31d40..96d240e01f 100644
--- a/lib/src/sync/warp_sync.rs
+++ b/lib/src/sync/warp_sync.rs
@@ -1715,19 +1715,23 @@ impl BuildRuntime {
let code_nibbles = trie::bytes_to_nibbles(b":code".iter().copied()).collect::>();
match decoded_downloaded_runtime.closest_ancestor_in_proof(
&self.inner.warped_header_state_root,
- &code_nibbles[..code_nibbles.len() - 1],
+ code_nibbles.iter().take(code_nibbles.len() - 1).copied(),
) {
Ok(Some(closest_ancestor_key)) => {
+ let closest_ancestor_key = closest_ancestor_key.collect::>();
let next_nibble = code_nibbles[closest_ancestor_key.len()];
let merkle_value = decoded_downloaded_runtime
- .trie_node_info(&self.inner.warped_header_state_root, closest_ancestor_key)
+ .trie_node_info(
+ &self.inner.warped_header_state_root,
+ closest_ancestor_key.iter().copied(),
+ )
.unwrap()
.children
.child(next_nibble)
.merkle_value();
match merkle_value {
- Some(mv) => (mv.to_owned(), closest_ancestor_key.to_vec()),
+ Some(mv) => (mv.to_owned(), closest_ancestor_key),
None => {
self.inner.warped_block_ty = WarpedBlockTy::KnownBad;
self.inner.runtime_download = RuntimeDownload::NotStarted {
@@ -2034,9 +2038,9 @@ impl BuildChainInformation {
let (proof, downloaded_source) = calls.get(&nk.call_in_progress()).unwrap();
let value = match proof.next_key(
&self.inner.warped_header_state_root,
- &nk.key().collect::>(), // TODO: overhead
+ nk.key(),
nk.or_equal(),
- &nk.prefix().collect::>(), // TODO: overhead
+ nk.prefix(),
nk.branch_nodes(),
) {
Ok(v) => v,
@@ -2052,14 +2056,14 @@ impl BuildChainInformation {
);
}
};
- nk.inject_key(value.map(|v| v.iter().copied()))
+ nk.inject_key(value)
}
chain_information::build::InProgress::ClosestDescendantMerkleValue(mv) => {
// TODO: child tries not supported
let (proof, downloaded_source) = calls.get(&mv.call_in_progress()).unwrap();
let value = match proof.closest_descendant_merkle_value(
&self.inner.warped_header_state_root,
- &mv.key().collect::>(), // TODO: overhead
+ mv.key(),
) {
Ok(v) => v,
Err(proof_decode::IncompleteProofError { .. }) => {
diff --git a/lib/src/transactions/validate/tests.rs b/lib/src/transactions/validate/tests.rs
index 9abcffc869..abdb9b6e2c 100644
--- a/lib/src/transactions/validate/tests.rs
+++ b/lib/src/transactions/validate/tests.rs
@@ -67,17 +67,17 @@ fn validate_from_proof() {
let next_key = call_proof
.next_key(
main_trie_root,
- &nk.key().collect::>(),
+ nk.key(),
nk.or_equal(),
- &nk.prefix().collect::>(),
+ nk.prefix(),
nk.branch_nodes(),
)
.unwrap();
- validation_in_progress = nk.inject_key(next_key.map(|k| k.iter().copied()));
+ validation_in_progress = nk.inject_key(next_key);
}
super::Query::ClosestDescendantMerkleValue(mv) => {
let value = call_proof
- .closest_descendant_merkle_value(main_trie_root, &mv.key().collect::>())
+ .closest_descendant_merkle_value(main_trie_root, mv.key())
.unwrap();
validation_in_progress = mv.inject_merkle_value(value);
}
diff --git a/lib/src/trie/nibble.rs b/lib/src/trie/nibble.rs
index a7cdfa4583..4ac84806cd 100644
--- a/lib/src/trie/nibble.rs
+++ b/lib/src/trie/nibble.rs
@@ -76,6 +76,12 @@ impl From for u8 {
}
}
+impl From for usize {
+ fn from(nibble: Nibble) -> usize {
+ usize::from(nibble.0)
+ }
+}
+
impl fmt::LowerHex for Nibble {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
diff --git a/lib/src/trie/prefix_proof.rs b/lib/src/trie/prefix_proof.rs
index 89a94e12f9..e0a6968329 100644
--- a/lib/src/trie/prefix_proof.rs
+++ b/lib/src/trie/prefix_proof.rs
@@ -29,7 +29,7 @@
use super::{nibble, proof_decode};
-use alloc::{borrow::ToOwned as _, vec, vec::Vec};
+use alloc::{vec, vec::Vec};
use core::{fmt, iter, mem};
mod tests;
@@ -169,7 +169,8 @@ impl PrefixScan {
};
match (
- decoded_proof.trie_node_info(&self.trie_root_hash, info_of_node),
+ decoded_proof
+ .trie_node_info(&self.trie_root_hash, info_of_node.iter().copied()),
query_ty,
) {
(Ok(info), QueryTy::Exact) => info,
@@ -179,7 +180,7 @@ impl PrefixScan {
// Rather than complicate this code, we just add the child to
// `next` (this time an `Exact` query) and process it during
// the next iteration.
- next.push((child_key.to_owned(), QueryTy::Exact));
+ next.push((child_key.collect::>(), QueryTy::Exact));
continue;
}
proof_decode::Child::AbsentFromProof { .. } => {
@@ -258,7 +259,7 @@ impl PrefixScan {
next.push((direction, QueryTy::Direction));
}
proof_decode::Child::InProof { child_key, .. } => {
- next.push((child_key.to_owned(), QueryTy::Exact))
+ next.push((child_key.collect::>(), QueryTy::Exact))
}
}
}
diff --git a/lib/src/trie/proof_decode.rs b/lib/src/trie/proof_decode.rs
index d47cdfd57e..03b34b1879 100644
--- a/lib/src/trie/proof_decode.rs
+++ b/lib/src/trie/proof_decode.rs
@@ -40,8 +40,8 @@
use super::{nibble, trie_node, TrieEntryVersion};
-use alloc::{collections::BTreeMap, vec, vec::Vec};
-use core::{fmt, iter, mem, ops};
+use alloc::vec::Vec;
+use core::{fmt, iter, ops};
/// Configuration to pass to [`decode_and_verify_proof`].
pub struct Config {
@@ -128,14 +128,6 @@ where
merkle_values
};
- // Dummy empty proofs are always valid.
- if merkle_values.is_empty() {
- return Ok(DecodedTrieProof {
- proof: config.proof,
- entries: BTreeMap::new(),
- });
- }
-
// Start by iterating over each element of the proof, and keep track of elements that are
// decodable but aren't mentioned in any other element. This gives us the tries roots.
let trie_roots = {
@@ -161,7 +153,10 @@ where
// note of the traversed elements.
// Keep track of all the entries found in the proof.
- let mut entries = BTreeMap::new();
+ let mut entries: Vec = Vec::with_capacity(merkle_values.len());
+
+ let mut trie_roots_with_entries =
+ hashbrown::HashMap::with_capacity_and_hasher(trie_roots.len(), Default::default());
// Keep track of the proof entries that haven't been visited when traversing.
let mut unvisited_proof_entries =
@@ -169,148 +164,212 @@ where
// We repeat this operation for every trie root.
for trie_root_hash in trie_roots {
- // Find the expected trie root in the proof. This is the starting point of the verification.
- let mut remain_iterate = {
- let (root_position, root_range) =
- merkle_values.get(&trie_root_hash[..]).unwrap().clone();
- let _ = unvisited_proof_entries.remove(&root_position);
- vec![(root_range, Vec::new())]
- };
+ struct StackEntry<'a> {
+ range_in_proof: ops::Range,
+ index_in_entries: usize,
+ num_visited_children: u8,
+ children_node_values: [Option<&'a [u8]>; 16],
+ }
- while !remain_iterate.is_empty() {
- // Iterate through each entry in `remain_iterate`.
- // This clears `remain_iterate` so that we can add new entries to it during the iteration.
- for (proof_entry_range, storage_key_before_partial) in
- mem::replace(&mut remain_iterate, Vec::with_capacity(merkle_values.len()))
- {
- // Decodes the proof entry.
- let proof_entry = &proof_as_ref[proof_entry_range.clone()];
- let decoded_node_value =
- trie_node::decode(proof_entry).map_err(Error::InvalidNodeValue)?;
- let decoded_node_value_children_bitmap = decoded_node_value.children_bitmap();
-
- // Build the storage key of the node.
- let storage_key = {
- let mut storage_key_after_partial = Vec::with_capacity(
- storage_key_before_partial.len() + decoded_node_value.partial_key.len(),
- );
- storage_key_after_partial.extend_from_slice(&storage_key_before_partial);
- storage_key_after_partial.extend(decoded_node_value.partial_key);
- storage_key_after_partial
- };
+ // TODO: configurable capacity?
+ let mut visited_entries_stack: Vec = Vec::with_capacity(24);
- // Add the children to `remain_iterate`.
- for (child_num, child_node_value) in
- decoded_node_value.children.into_iter().enumerate()
- {
- // Ignore missing children slots.
- let child_node_value = match child_node_value {
- None => continue,
- Some(v) => v,
+ loop {
+ // Find which node to visit next.
+ // This is the next child of the node at the top of the stack, or if the node at
+ // the top of the stack doesn't have any child, we pop it and continue iterating.
+ // If the stack is empty, we are necessarily at the first iteration.
+ let visited_node_entry_range = match visited_entries_stack.last_mut() {
+ None => {
+ // Stack is empty.
+ // Because we immediately `break` after popping the last element, the stack
+ // can only ever be empty at the very start.
+ let (root_position, root_range) =
+ merkle_values.get(&trie_root_hash[..]).unwrap().clone();
+ let _ = unvisited_proof_entries.remove(&root_position);
+ trie_roots_with_entries.insert(*trie_root_hash, entries.len());
+ root_range
+ }
+ Some(StackEntry {
+ num_visited_children: stack_top_visited_children,
+ ..
+ }) if *stack_top_visited_children == 16 => {
+ // We have visited all the children of the top of the stack. Pop the node from
+ // the stack.
+ let Some(StackEntry {
+ index_in_entries: stack_top_index_in_entries,
+ ..
+ }) = visited_entries_stack.pop()
+ else {
+ unreachable!()
};
- debug_assert!(child_num < 16);
- let child_nibble =
- nibble::Nibble::try_from(u8::try_from(child_num).unwrap()).unwrap();
+ // Update the value of `child_entries_follow_up`
+ // and `children_present_in_proof_bitmap` of the parent.
+ if let Some(&StackEntry {
+ index_in_entries: parent_index_in_entries,
+ num_visited_children: parent_children_visited,
+ ..
+ }) = visited_entries_stack.last()
+ {
+ entries[parent_index_in_entries].child_entries_follow_up +=
+ entries[stack_top_index_in_entries].child_entries_follow_up + 1;
+ entries[parent_index_in_entries].children_present_in_proof_bitmap |=
+ 1 << (parent_children_visited - 1);
+ }
- // Key of the child node before its partial key.
- let mut child_storage_key_before_partial =
- Vec::with_capacity(storage_key.len() + 1);
- child_storage_key_before_partial.extend_from_slice(&storage_key);
- child_storage_key_before_partial.push(child_nibble);
+ // If we popped the last node of the stack, we have finished the iteration.
+ if visited_entries_stack.is_empty() {
+ break;
+ } else {
+ continue;
+ }
+ }
+ Some(StackEntry {
+ range_in_proof: stack_top_proof_range,
+ num_visited_children: stack_top_visited_children,
+ children_node_values: stack_top_children,
+ ..
+ }) => {
+ // Find the next child of the top of the stack.
+ let stack_top_entry = &proof_as_ref[stack_top_proof_range.clone()];
+
+ // Find the index of the next child (that we are about to visit).
+ let next_child_to_visit = stack_top_children
+ .iter()
+ .skip(usize::from(*stack_top_visited_children))
+ .position(|c| c.is_some())
+ .map(|idx| u8::try_from(idx).unwrap() + *stack_top_visited_children)
+ .unwrap_or(16);
+
+ // `continue` if all children have been visited. The next iteration will
+ // pop the stack entry.
+ if next_child_to_visit == 16 {
+ *stack_top_visited_children = 16;
+ continue;
+ }
+ *stack_top_visited_children = next_child_to_visit + 1;
- // The value of the child node is either directly inlined (if less than 32 bytes)
- // or is a hash.
+ // The value of the child node is either directly inlined (if less
+ // than 32 bytes) or is a hash.
+ let child_node_value =
+ stack_top_children[usize::from(next_child_to_visit)].unwrap();
+ debug_assert!(child_node_value.len() <= 32); // Guaranteed by decoding API.
if child_node_value.len() < 32 {
- let offset = proof_entry_range.start
+ let offset = stack_top_proof_range.start
+ if !child_node_value.is_empty() {
- child_node_value.as_ptr() as usize - proof_entry.as_ptr() as usize
+ child_node_value.as_ptr() as usize
+ - stack_top_entry.as_ptr() as usize
} else {
0
};
- debug_assert!(offset == 0 || offset >= proof_entry_range.start);
- debug_assert!(offset <= (proof_entry_range.start + proof_entry.len()));
- remain_iterate.push((
- offset..(offset + child_node_value.len()),
- child_storage_key_before_partial,
- ));
- } else {
- // The decoding API guarantees that the child value is never larger than
- // 32 bytes.
- debug_assert_eq!(child_node_value.len(), 32);
- if let Some((child_position, child_entry_range)) =
- merkle_values.get(child_node_value)
- {
- // If the node value of the child is less than 32 bytes long, it should
- // have been inlined instead of given separately.
- if child_entry_range.end - child_entry_range.start < 32 {
- return Err(Error::UnexpectedHashedNode);
- }
-
- // Remove the entry from `unvisited_proof_entries`.
- // Note that it is questionable what to do if the same entry is visited
- // multiple times. In case where multiple storage branches are identical,
- // the sender of the proof should de-duplicate the identical nodes. For
- // this reason, it could be legitimate for the same proof entry to be
- // visited multiple times.
- let _ = unvisited_proof_entries.remove(child_position);
- remain_iterate.push((
- child_entry_range.clone(),
- child_storage_key_before_partial,
- ));
+ debug_assert!(offset == 0 || offset >= stack_top_proof_range.start);
+ debug_assert!(
+ offset <= (stack_top_proof_range.start + stack_top_entry.len())
+ );
+ offset..(offset + child_node_value.len())
+ } else if let Some(&(child_position, ref child_entry_range)) =
+ merkle_values.get(child_node_value)
+ {
+ // If the node value of the child is less than 32 bytes long, it should
+ // have been inlined instead of given separately.
+ if child_entry_range.end - child_entry_range.start < 32 {
+ return Err(Error::UnexpectedHashedNode);
}
+
+ // Remove the entry from `unvisited_proof_entries`.
+ // Note that it is questionable what to do if the same entry is visited
+ // multiple times. In case where multiple storage branches are identical,
+ // the sender of the proof should de-duplicate the identical nodes. For
+ // this reason, it could be legitimate for the same proof entry to be
+ // visited multiple times.
+ let _ = unvisited_proof_entries.remove(&child_position);
+ child_entry_range.clone()
+ } else {
+ // Child is a hash that was not found in the proof. Simply continue
+ // iterating, in order to try to find the follow-up child.
+ continue;
}
}
+ };
- // Insert the node into `entries`.
- // This is done at the end so that `storage_key` doesn't need to be cloned.
- let _prev_value = entries.insert((*trie_root_hash, storage_key), {
- let storage_value = match decoded_node_value.storage_value {
- trie_node::StorageValue::None => StorageValueInner::None,
- trie_node::StorageValue::Hashed(value_hash) => {
- if let Some((value_position, value_entry_range)) =
- merkle_values.get(&value_hash[..])
- {
- let _ = unvisited_proof_entries.remove(value_position);
- StorageValueInner::Known {
- is_inline: false,
- offset: value_entry_range.start,
- len: value_entry_range.end - value_entry_range.start,
- }
- } else {
- let offset =
- value_hash.as_ptr() as usize - proof_as_ref.as_ptr() as usize;
- debug_assert!(offset >= proof_entry_range.start);
- debug_assert!(
- offset <= (proof_entry_range.start + proof_entry.len())
- );
- StorageValueInner::HashKnownValueMissing { offset }
- }
- }
- trie_node::StorageValue::Unhashed(v) => {
- let offset = if !v.is_empty() {
- v.as_ptr() as usize - proof_as_ref.as_ptr() as usize
- } else {
- 0
- };
- debug_assert!(offset == 0 || offset >= proof_entry_range.start);
- debug_assert!(offset <= (proof_entry_range.start + proof_entry.len()));
- StorageValueInner::Known {
- is_inline: true,
- offset,
- len: v.len(),
- }
- }
- };
+ // Decodes the proof entry.
+ let visited_node_entry = &proof_as_ref[visited_node_entry_range.clone()];
+ let visited_node_decoded =
+ trie_node::decode(visited_node_entry).map_err(Error::InvalidNodeValue)?;
+ // All nodes must either have a child or a storage value or be the root.
+ if visited_node_decoded.children_bitmap() == 0
+ && matches!(
+ visited_node_decoded.storage_value,
+ trie_node::StorageValue::None
+ )
+ && !visited_entries_stack.is_empty()
+ {
+ return Err(Error::NonRootBranchNodeWithNoValue);
+ }
+
+ // Nodes with no storage value and one children are forbidden.
+ if visited_node_decoded
+ .children
+ .iter()
+ .filter(|c| c.is_some())
+ .count()
+ == 1
+ && matches!(
+ visited_node_decoded.storage_value,
+ trie_node::StorageValue::None
+ )
+ {
+ return Err(Error::NodeWithNoValueAndOneChild);
+ }
+
+ // Add an entry for this node in the final list of entries.
+ entries.push(Entry {
+ parent_entry_index: visited_entries_stack.last().map(|entry| {
(
- storage_value,
- proof_entry_range.clone(),
- decoded_node_value_children_bitmap,
+ entry.index_in_entries,
+ nibble::Nibble::try_from(entry.num_visited_children - 1).unwrap(),
)
- });
- debug_assert!(_prev_value.is_none());
- }
+ }),
+ range_in_proof: visited_node_entry_range.clone(),
+ storage_value_in_proof: match visited_node_decoded.storage_value {
+ trie_node::StorageValue::None => None,
+ trie_node::StorageValue::Hashed(value_hash) => {
+ if let Some(&(value_position, ref value_entry_range)) =
+ merkle_values.get(&value_hash[..])
+ {
+ let _ = unvisited_proof_entries.remove(&value_position);
+ Some(value_entry_range.clone())
+ } else {
+ None
+ }
+ }
+ trie_node::StorageValue::Unhashed(v) => {
+ let offset = if !v.is_empty() {
+ v.as_ptr() as usize - proof_as_ref.as_ptr() as usize
+ } else {
+ 0
+ };
+ debug_assert!(offset == 0 || offset >= visited_node_entry_range.start);
+ debug_assert!(
+ offset <= (visited_node_entry_range.start + visited_node_entry.len())
+ );
+ Some(offset..offset + v.len())
+ }
+ },
+ child_entries_follow_up: 0, // Filled later.
+ children_present_in_proof_bitmap: 0, // Filled later.
+ });
+
+ // Add the visited node to the stack. The next iteration will either go to its first
+ // child, or pop the node from the stack.
+ visited_entries_stack.push(StackEntry {
+ range_in_proof: visited_node_entry_range,
+ index_in_entries: entries.len() - 1,
+ num_visited_children: 0,
+ children_node_values: visited_node_decoded.children,
+ });
}
}
@@ -323,33 +382,47 @@ where
Ok(DecodedTrieProof {
proof: config.proof,
entries,
+ trie_roots: trie_roots_with_entries,
})
}
-/// Equivalent to [`StorageValue`] but contains offsets indexing [`DecodedTrieProof::proof`].
-#[derive(Debug, Copy, Clone)]
-enum StorageValueInner {
- /// Equivalent to [`StorageValue::Known`].
- Known {
- is_inline: bool,
- offset: usize,
- len: usize,
- },
- /// Equivalent to [`StorageValue::HashKnownValueMissing`].
- HashKnownValueMissing { offset: usize },
- /// Equivalent to [`StorageValue::None`].
- None,
-}
-
/// Decoded Merkle proof. The proof is guaranteed valid.
pub struct DecodedTrieProof {
/// The proof itself.
proof: T,
- /// For each trie-root-hash + storage-key tuple, contains the entry found in the proof, the
- /// range at which to find its node value, and the children bitmap.
- // TODO: a BTreeMap is actually kind of stupid since `proof` is itself in a tree format
- entries: BTreeMap<([u8; 32], Vec), (StorageValueInner, ops::Range, u16)>,
+ /// All entries in the proof, in lexicographic order. Ordering between trie roots is
+ /// unspecified.
+ entries: Vec,
+
+ ///
+ /// Given that hashes are verified to actually match their values, there is no risk of
+ /// HashDoS attack.
+ // TODO: is that true? ^ depends on whether there are a lot of storage values
+ trie_roots: hashbrown::HashMap<[u8; 32], usize, fnv::FnvBuildHasher>,
+}
+
+struct Entry {
+ /// Index within [`DecodedTrieProof::entries`] of the parent of this entry and child direction
+ /// nibble, or `None` if it is the root.
+ parent_entry_index: Option<(usize, nibble::Nibble)>,
+
+ /// Range within [`DecodedTrieProof::proof`] of the node value of this entry.
+ range_in_proof: ops::Range,
+
+ /// Range within [`DecodedTrieProof::proof`] of the unhashed storage value of this entry.
+ /// `None` if the entry doesn't have any storage entry or if it's missing from the proof.
+ storage_value_in_proof: Option>,
+
+ // TODO: doc
+ children_present_in_proof_bitmap: u16,
+
+ /// Given an entry of index `N`, it is always followed with `k` entries that correspond to the
+ /// sub-tree of that entry, where `k` is equal to [`Entry::child_entries_follow_up`]. In order
+ /// to jump to the next sibling of that entry, jump to `N + 1 + k`. If `k` is non-zero, then
+ /// entry `N + 1` corresponds to the first child of the entry of index `N`.
+ child_entries_follow_up: usize,
+ // TODO: by adding the partial key, we should be able to avoid decoding the entry while iterating down the trie
}
impl> fmt::Debug for DecodedTrieProof {
@@ -376,14 +449,16 @@ impl> fmt::Debug for DecodedTrieProof {
}
}
- struct DummyNibbles<'a>(&'a [nibble::Nibble]);
- impl<'a> fmt::Debug for DummyNibbles<'a> {
+ struct DummyNibbles<'a, T: AsRef<[u8]>>(EntryKeyIter<'a, T>);
+ impl<'a, T: AsRef<[u8]>> fmt::Debug for DummyNibbles<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- if self.0.is_empty() {
- write!(f, "∅")?
+ let mut any_written = false;
+ for nibble in self.0.clone() {
+ any_written = true;
+ write!(f, "{:x}", nibble)?
}
- for nibble in self.0 {
- write!(f, "{:x}", *nibble)?
+ if !any_written {
+ write!(f, "∅")?
}
Ok(())
}
@@ -447,6 +522,7 @@ impl> DecodedTrieProof {
/// containing a value at a key that consists in an uneven number of nibbles is considered as
/// valid according to [`decode_and_verify_proof`].
///
+ // TODO: paragraph below not true anymore
/// However, given that [`decode_and_verify_proof`] verifies the trie proof against the state
/// trie root hash, we are also guaranteed that this proof reflects the actual trie. If the
/// actual trie can't contain any storage value at a key that consists in an uneven number of
@@ -459,6 +535,8 @@ impl> DecodedTrieProof {
/// context of a runtime, and that the block using this trie is guaranteed to be valid, then
/// this function will work as intended and return the entire content of the proof.
///
+ // TODO: ordering between trie roots unspecified
+ // TODO: consider not returning a Vec
pub fn iter_runtime_context_ordered(
&'_ self,
) -> impl Iterator>, StorageValue<'_>)> + '_ {
@@ -472,11 +550,11 @@ impl> DecodedTrieProof {
)| {
let value = entry.trie_node_info.storage_value;
- if key.len() % 2 != 0 {
+ if key.clone().count() % 2 != 0 {
return None;
}
- let key = nibble::nibbles_to_bytes_suffix_extend(key.iter().copied()).collect();
+ let key = nibble::nibbles_to_bytes_suffix_extend(key).collect();
Some((
EntryKey {
trie_root_hash,
@@ -491,194 +569,172 @@ impl> DecodedTrieProof {
/// Returns a list of all elements of the proof, ordered by key in lexicographic order.
///
/// The iterator includes branch nodes.
+ // TODO: ordering between trie roots unspecified
pub fn iter_ordered(
&'_ self,
- ) -> impl Iterator, ProofEntry<'_>)> + '_ {
- self.entries.iter().map(
- |((trie_root_hash, key), (storage_value_inner, node_value_range, children_bitmap))| {
- let storage_value = match storage_value_inner {
- StorageValueInner::Known {
- offset,
- len,
- is_inline,
- ..
- } => StorageValue::Known {
- value: &self.proof.as_ref()[*offset..][..*len],
- inline: *is_inline,
- },
- StorageValueInner::None => StorageValue::None,
- StorageValueInner::HashKnownValueMissing { offset } => {
- StorageValue::HashKnownValueMissing(
- <&[u8; 32]>::try_from(&self.proof.as_ref()[*offset..][..32]).unwrap(),
- )
- }
- };
+ ) -> impl Iterator>, ProofEntry<'_, T>)> + '_ {
+ let proof = self.proof.as_ref();
- (
- EntryKey {
- trie_root_hash,
- key: &key[..],
- },
- ProofEntry {
- node_value: &self.proof.as_ref()[node_value_range.clone()],
- unhashed_storage_value: match storage_value_inner {
- StorageValueInner::Known {
- is_inline: false,
- offset,
- len,
- } => Some(&self.proof.as_ref()[*offset..][..*len]),
- _ => None,
- },
- trie_node_info: TrieNodeInfo {
- children: self.children_from_key(
- trie_root_hash,
- key,
- &self.proof.as_ref()[node_value_range.clone()],
- *children_bitmap,
- ),
- storage_value,
- },
- },
- )
- },
- )
+ self.trie_roots
+ .iter()
+ .flat_map(|(trie_root_hash, &trie_root_entry_index)| {
+ self.entries
+ .iter()
+ .enumerate()
+ .skip(trie_root_entry_index)
+ .take(self.entries[trie_root_entry_index].child_entries_follow_up + 1)
+ .map(|(entry_index, entry)| {
+ let key = EntryKey {
+ trie_root_hash,
+ key: EntryKeyIter::new(self, entry_index),
+ };
+
+ let Ok(entry_index_decoded) =
+ trie_node::decode(&proof[entry.range_in_proof.clone()])
+ else {
+ // Proof has been checked to be entirely decodable.
+ unreachable!()
+ };
+
+ let entry = ProofEntry {
+ node_value: &self.proof.as_ref()[entry.range_in_proof.clone()],
+ unhashed_storage_value: entry
+ .storage_value_in_proof
+ .as_ref()
+ .map(|range| &proof[range.clone()]),
+ trie_node_info: TrieNodeInfo {
+ children: Children {
+ children: {
+ let mut children = core::array::from_fn(|_| Child::NoChild);
+ let mut i = entry_index + 1;
+ for child_num in 0..16 {
+ let Some(child_merkle_value) =
+ entry_index_decoded.children[child_num]
+ else {
+ continue;
+ };
+ if entry.children_present_in_proof_bitmap
+ & (1 << child_num)
+ != 0
+ {
+ children[child_num] = Child::InProof {
+ child_key: EntryKeyIter::new(self, i),
+ merkle_value: child_merkle_value,
+ };
+ i += self.entries[i].child_entries_follow_up;
+ i += 1;
+ } else {
+ children[child_num] = Child::AbsentFromProof {
+ merkle_value: child_merkle_value,
+ };
+ }
+ }
+ children
+ },
+ },
+ storage_value: match (
+ entry_index_decoded.storage_value,
+ &entry.storage_value_in_proof,
+ ) {
+ (trie_node::StorageValue::Unhashed(value), _) => {
+ StorageValue::Known {
+ value,
+ inline: true,
+ }
+ }
+ (trie_node::StorageValue::Hashed(_), Some(value_range)) => {
+ StorageValue::Known {
+ value: &proof[value_range.clone()],
+ inline: false,
+ }
+ }
+ (trie_node::StorageValue::Hashed(hash), None) => {
+ StorageValue::HashKnownValueMissing(hash)
+ }
+ (trie_node::StorageValue::None, _v) => {
+ debug_assert!(_v.is_none());
+ StorageValue::None
+ }
+ },
+ },
+ };
+
+ (key, entry)
+ })
+ })
}
- fn children_from_key<'a>(
+ /// Returns the key of the closest ancestor to the given key that can be found in the proof.
+ /// If `key` is in the proof, returns `key`.
+ pub fn closest_ancestor_in_proof<'a>(
&'a self,
trie_root_merkle_value: &[u8; 32],
- key: &[nibble::Nibble],
- parent_node_value: &'a [u8],
- children_bitmap: u16,
- ) -> Children<'a> {
- debug_assert_eq!(
- self.entries
- .get(&(*trie_root_merkle_value, key.to_vec()))
- .unwrap()
- .2,
- children_bitmap
- );
-
- let mut children = [Child::NoChild; 16];
- let mut child_search = key.to_vec();
-
- let parent_node_value = trie_node::decode(parent_node_value).unwrap();
-
- for nibble in nibble::all_nibbles().filter(|n| (children_bitmap & (1 << u8::from(*n))) != 0)
- {
- child_search.push(nibble);
-
- let merkle_value = &parent_node_value.children[usize::from(u8::from(nibble))].unwrap();
-
- children[usize::from(u8::from(nibble))] = if let Some(((_, child), _)) = self
- .entries
- .range((
- ops::Bound::Included((*trie_root_merkle_value, child_search.clone())), // TODO: stupid allocation
- ops::Bound::Unbounded,
- ))
- .next()
- .filter(|((trie_root, maybe_child), _)| {
- trie_root == trie_root_merkle_value && maybe_child.starts_with(&child_search)
- }) {
- Child::InProof {
- child_key: child,
- merkle_value,
- }
- } else {
- Child::AbsentFromProof { merkle_value }
- };
+ mut key: impl Iterator,
+ ) -> Result