From 08a23052e158c0750a95c56c1db74ffa753488ba Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:28:03 +0200 Subject: [PATCH] trie-db: Fetch the closest merkle value (#199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * trie-db: Add `get_closest_merkle_value` to Trie trait Signed-off-by: Alexandru Vasile * trie-db: Extract the merkle value Signed-off-by: Alexandru Vasile * trie-db/test: Check merkle value on update key Signed-off-by: Alexandru Vasile * Update trie-db/src/lookup.rs Co-authored-by: Arkadiy Paronyan * Update trie-db/src/lookup.rs Co-authored-by: Arkadiy Paronyan * trie-db: Rename look_up_merkle_without_cache function Signed-off-by: Alexandru Vasile * trie-db/tests: Check closest descendant of partial keys Signed-off-by: Alexandru Vasile * trie-db: Adjust lookups for partial keys Signed-off-by: Alexandru Vasile * trie-db/tests: Check non-existent key and branch nodes Signed-off-by: Alexandru Vasile * trie-db: Ensure recording of `NonExisting` for leaves Signed-off-by: Alexandru Vasile * trie-db: Ensure the merkle descedent hash is returned Signed-off-by: Alexandru Vasile * trie-db/tests: Extend tests with branch nodes and single key db Signed-off-by: Alexandru Vasile * trie-db/tests: Check trie modification and merkle propagation Signed-off-by: Alexandru Vasile * trie-db/tests: Use `PrefixedKey` instead of `HashKey` Signed-off-by: Alexandru Vasile * trie-db/tests: Test extra keys for `test_merkle_value` Signed-off-by: Alexandru Vasile * trie-db: Return the extension node hash Signed-off-by: Alexandru Vasile * trie-db: Use `starts_with` method instead of common prefix Signed-off-by: Alexandru Vasile * trie-db: Return no merkle value on empty node Signed-off-by: Alexandru Vasile * trie-db/tests: Ensure inline nodes Signed-off-by: Alexandru Vasile * trie-db/tests: Check empty trie with empty keys Signed-off-by: Alexandru Vasile * trie-db/tests: Add extra keys to check Signed-off-by: Alexandru Vasile * trie-db: Use `starts_with` for extension nodes Signed-off-by: Alexandru Vasile * trie-db: Rename merkle lookups to lookup_first_descendant Signed-off-by: Alexandru Vasile * trie-db: Return inline hashes properly Signed-off-by: Alexandru Vasile * trie-db: Implement starts_with_slice for NibbleVec Signed-off-by: Alexandru Vasile * trie-db: Use cache for finding first descendent hash Signed-off-by: Alexandru Vasile * trie-db: Introduce caching for descedent node access Signed-off-by: Alexandru Vasile * trie-db: Use rstd::vec::Vec Signed-off-by: Alexandru Vasile * trie-db: Forward merkle value for fatdb and sectriedb Signed-off-by: Alexandru Vasile * trie-db: Rename `get_closest_merkle_value` to `lookup_first_descendant` Signed-off-by: Alexandru Vasile * trie-db: Introduce MerkleValue to return inline nodes and hashes Signed-off-by: Alexandru Vasile * trie-db: Remove inner function for merkle value lookups Signed-off-by: Alexandru Vasile * Update trie-db/src/lib.rs Co-authored-by: Bastian Köcher * Update trie-db/src/lib.rs Co-authored-by: Bastian Köcher * Apply fmt Signed-off-by: Alexandru Vasile --------- Signed-off-by: Alexandru Vasile Co-authored-by: Arkadiy Paronyan Co-authored-by: Bastian Köcher --- trie-db/src/fatdb.rs | 9 +- trie-db/src/lib.rs | 33 +++++ trie-db/src/lookup.rs | 227 +++++++++++++++++++++++++++++++- trie-db/src/nibble/nibblevec.rs | 19 +++ trie-db/src/sectriedb.rs | 11 +- trie-db/src/triedb.rs | 21 ++- trie-db/test/src/triedb.rs | 205 ++++++++++++++++++++++++++++ 7 files changed, 517 insertions(+), 8 deletions(-) diff --git a/trie-db/src/fatdb.rs b/trie-db/src/fatdb.rs index 16883394..f3a3fc43 100644 --- a/trie-db/src/fatdb.rs +++ b/trie-db/src/fatdb.rs @@ -18,7 +18,7 @@ use super::{ }; use hash_db::{HashDBRef, Hasher}; -use crate::{rstd::boxed::Box, TrieDBBuilder}; +use crate::{rstd::boxed::Box, MerkleValue, TrieDBBuilder}; /// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database. /// Additionaly it stores inserted hash-key mappings for later retrieval. @@ -72,6 +72,13 @@ where self.raw.get_with(L::Hash::hash(key).as_ref(), query) } + fn lookup_first_descendant( + &self, + key: &[u8], + ) -> Result>>, TrieHash, CError> { + self.raw.lookup_first_descendant(key) + } + fn iter<'a>( &'a self, ) -> Result< diff --git a/trie-db/src/lib.rs b/trie-db/src/lib.rs index b09372b2..29b78bbe 100644 --- a/trie-db/src/lib.rs +++ b/trie-db/src/lib.rs @@ -277,6 +277,17 @@ pub trait Trie { query: Q, ) -> Result, TrieHash, CError>; + /// Look up the [`MerkleValue`] of the node that is the closest descendant for the provided + /// key. + /// + /// When the provided key leads to a node, then the merkle value of that node + /// is returned. However, if the key does not lead to a node, then the merkle value + /// of the closest descendant is returned. `None` if no such descendant exists. + fn lookup_first_descendant( + &self, + key: &[u8], + ) -> Result>>, TrieHash, CError>; + /// Returns a depth-first iterator over the elements of trie. fn iter<'a>( &'a self, @@ -404,6 +415,13 @@ impl<'db, 'cache, L: TrieLayout> Trie for TrieKinds<'db, 'cache, L> { wrapper!(self, get_with, key, query) } + fn lookup_first_descendant( + &self, + key: &[u8], + ) -> Result>>, TrieHash, CError> { + wrapper!(self, lookup_first_descendant, key) + } + fn iter<'a>( &'a self, ) -> Result< @@ -738,3 +756,18 @@ impl From for BytesWeak { Self(rstd::sync::Arc::downgrade(&bytes.0)) } } + +/// Either the `hash` or `value` of a node depending on its size. +/// +/// If the size of the node `value` is bigger or equal than `MAX_INLINE_VALUE` the `hash` is +/// returned. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MerkleValue { + /// The merkle value is the node data itself when the + /// node data is smaller than `MAX_INLINE_VALUE`. + /// + /// Note: The case of inline nodes. + Node(Vec), + /// The merkle value is the hash of the node. + Hash(H), +} diff --git a/trie-db/src/lookup.rs b/trie-db/src/lookup.rs index 90bdcb5e..7ca838eb 100644 --- a/trie-db/src/lookup.rs +++ b/trie-db/src/lookup.rs @@ -18,9 +18,9 @@ use crate::{ nibble::NibbleSlice, node::{decode_hash, Node, NodeHandle, NodeHandleOwned, NodeOwned, Value, ValueOwned}, node_codec::NodeCodec, - rstd::boxed::Box, - Bytes, CError, CachedValue, DBValue, Query, RecordedForKey, Result, TrieAccess, TrieCache, - TrieError, TrieHash, TrieLayout, TrieRecorder, + rstd::{boxed::Box, vec::Vec}, + Bytes, CError, CachedValue, DBValue, MerkleValue, Query, RecordedForKey, Result, TrieAccess, + TrieCache, TrieError, TrieHash, TrieLayout, TrieRecorder, }; use hash_db::{HashDBRef, Hasher, Prefix}; @@ -133,6 +133,227 @@ where recorder.record(get_access()); } } + /// Look up the merkle value (hash) of the node that is the closest descendant for the provided + /// key. + /// + /// When the provided key leads to a node, then the merkle value (hash) of that node + /// is returned. However, if the key does not lead to a node, then the merkle value + /// of the closest descendant is returned. `None` if no such descendant exists. + pub fn lookup_first_descendant( + mut self, + full_key: &[u8], + nibble_key: NibbleSlice, + ) -> Result>>, TrieHash, CError> { + let mut partial = nibble_key; + let mut hash = self.hash; + let mut key_nibbles = 0; + + let mut cache = self.cache.take(); + + // this loop iterates through non-inline nodes. + for depth in 0.. { + // Ensure the owned node reference lives long enough. + // Value is never read, but the reference is. + let mut _owned_node = NodeOwned::Empty; + + // The binary encoded data of the node fetched from the database. + // + // Populated by `get_owned_node` to avoid one extra allocation by not + // calling `NodeOwned::to_encoded` when computing the hash of inlined nodes. + let mut node_data = Vec::new(); + + // Get the owned node representation from the database. + let mut get_owned_node = |depth: i32| { + let data = match self.db.get(&hash, nibble_key.mid(key_nibbles).left()) { + Some(value) => value, + None => + return Err(Box::new(match depth { + 0 => TrieError::InvalidStateRoot(hash), + _ => TrieError::IncompleteDatabase(hash), + })), + }; + + let decoded = match L::Codec::decode(&data[..]) { + Ok(node) => node, + Err(e) => return Err(Box::new(TrieError::DecoderError(hash, e))), + }; + + let owned = decoded.to_owned_node::()?; + node_data = data; + Ok(owned) + }; + + let mut node = if let Some(cache) = &mut cache { + let node = cache.get_or_insert_node(hash, &mut || get_owned_node(depth))?; + + self.record(|| TrieAccess::NodeOwned { hash, node_owned: node }); + + node + } else { + _owned_node = get_owned_node(depth)?; + + self.record(|| TrieAccess::EncodedNode { + hash, + encoded_node: node_data.as_slice().into(), + }); + + &_owned_node + }; + + // this loop iterates through all inline children (usually max 1) + // without incrementing the depth. + let mut is_inline = false; + loop { + let next_node = match node { + NodeOwned::Leaf(slice, _) => { + // The leaf slice can be longer than remainder of the provided key + // (descendent), but not the other way around. + if !slice.starts_with_slice(&partial) { + self.record(|| TrieAccess::NonExisting { full_key }); + return Ok(None) + } + + if partial.len() != slice.len() { + self.record(|| TrieAccess::NonExisting { full_key }); + } + + let res = is_inline + .then(|| MerkleValue::Node(node_data)) + .unwrap_or_else(|| MerkleValue::Hash(hash)); + return Ok(Some(res)) + }, + NodeOwned::Extension(slice, item) => { + if partial.len() < slice.len() { + self.record(|| TrieAccess::NonExisting { full_key }); + + // Extension slice can be longer than remainder of the provided key + // (descendent), ensure the extension slice starts with the remainder + // of the provided key. + return if slice.starts_with_slice(&partial) { + let res = is_inline + .then(|| MerkleValue::Node(node_data)) + .unwrap_or_else(|| MerkleValue::Hash(hash)); + Ok(Some(res)) + } else { + Ok(None) + } + } + + // Remainder of the provided key is longer than the extension slice, + // must advance the node iteration if and only if keys share + // a common prefix. + if partial.starts_with_vec(&slice) { + // Empties the partial key if the extension slice is longer. + partial = partial.mid(slice.len()); + key_nibbles += slice.len(); + item + } else { + self.record(|| TrieAccess::NonExisting { full_key }); + + return Ok(None) + } + }, + NodeOwned::Branch(children, value) => + if partial.is_empty() { + if value.is_none() { + self.record(|| TrieAccess::NonExisting { full_key }); + } + let res = is_inline + .then(|| MerkleValue::Node(node_data)) + .unwrap_or_else(|| MerkleValue::Hash(hash)); + return Ok(Some(res)) + } else { + match &children[partial.at(0) as usize] { + Some(x) => { + partial = partial.mid(1); + key_nibbles += 1; + x + }, + None => { + self.record(|| TrieAccess::NonExisting { full_key }); + + return Ok(None) + }, + } + }, + NodeOwned::NibbledBranch(slice, children, value) => { + // Not enough remainder key to continue the search. + if partial.len() < slice.len() { + self.record(|| TrieAccess::NonExisting { full_key }); + + // Branch slice starts with the remainder key, there's nothing to + // advance. + return if slice.starts_with_slice(&partial) { + let res = is_inline + .then(|| MerkleValue::Node(node_data)) + .unwrap_or_else(|| MerkleValue::Hash(hash)); + Ok(Some(res)) + } else { + Ok(None) + } + } + + // Partial key is longer or equal than the branch slice. + // Ensure partial key starts with the branch slice. + if !partial.starts_with_vec(&slice) { + self.record(|| TrieAccess::NonExisting { full_key }); + return Ok(None) + } + + // Partial key starts with the branch slice. + if partial.len() == slice.len() { + if value.is_none() { + self.record(|| TrieAccess::NonExisting { full_key }); + } + + let res = is_inline + .then(|| MerkleValue::Node(node_data)) + .unwrap_or_else(|| MerkleValue::Hash(hash)); + return Ok(Some(res)) + } else { + match &children[partial.at(slice.len()) as usize] { + Some(x) => { + partial = partial.mid(slice.len() + 1); + key_nibbles += slice.len() + 1; + x + }, + None => { + self.record(|| TrieAccess::NonExisting { full_key }); + + return Ok(None) + }, + } + } + }, + NodeOwned::Empty => { + self.record(|| TrieAccess::NonExisting { full_key }); + + return Ok(None) + }, + NodeOwned::Value(_, _) => { + unreachable!( + "`NodeOwned::Value` can not be reached by using the hash of a node. \ + `NodeOwned::Value` is only constructed when loading a value into memory, \ + which needs to have a different hash than any node; qed", + ) + }, + }; + + // check if new node data is inline or hash. + match next_node { + NodeHandleOwned::Hash(new_hash) => { + hash = *new_hash; + break + }, + NodeHandleOwned::Inline(inline_node) => { + node = &inline_node; + is_inline = true; + }, + } + } + } + Ok(None) + } /// Look up the given `nibble_key`. /// diff --git a/trie-db/src/nibble/nibblevec.rs b/trie-db/src/nibble/nibblevec.rs index f612585a..94a24194 100644 --- a/trie-db/src/nibble/nibblevec.rs +++ b/trie-db/src/nibble/nibblevec.rs @@ -235,6 +235,25 @@ impl NibbleVec { true } + /// Same as [`Self::starts_with`] but using [`NibbleSlice`]. + pub fn starts_with_slice(&self, other: &NibbleSlice) -> bool { + if self.len() < other.len() { + return false + } + + match self.as_nibbleslice() { + Some(slice) => slice.starts_with(&other), + None => { + for i in 0..other.len() { + if self.at(i) != other.at(i) { + return false + } + } + true + }, + } + } + /// Return an iterator over `Partial` bytes representation. pub fn right_iter<'a>(&'a self) -> impl Iterator + 'a { let require_padding = self.len % nibble_ops::NIBBLE_PER_BYTE != 0; diff --git a/trie-db/src/sectriedb.rs b/trie-db/src/sectriedb.rs index ccfbd971..2885c069 100644 --- a/trie-db/src/sectriedb.rs +++ b/trie-db/src/sectriedb.rs @@ -13,8 +13,8 @@ // limitations under the License. use crate::{ - rstd::boxed::Box, triedb::TrieDB, CError, DBValue, Query, Result, Trie, TrieDBBuilder, - TrieHash, TrieItem, TrieIterator, TrieKeyItem, TrieLayout, + rstd::boxed::Box, triedb::TrieDB, CError, DBValue, MerkleValue, Query, Result, Trie, + TrieDBBuilder, TrieHash, TrieItem, TrieIterator, TrieKeyItem, TrieLayout, }; use hash_db::{HashDBRef, Hasher}; @@ -75,6 +75,13 @@ where self.raw.get_with(L::Hash::hash(key).as_ref(), query) } + fn lookup_first_descendant( + &self, + key: &[u8], + ) -> Result>>, TrieHash, CError> { + self.raw.lookup_first_descendant(key) + } + fn iter<'a>( &'a self, ) -> Result< diff --git a/trie-db/src/triedb.rs b/trie-db/src/triedb.rs index ad6166eb..310deff3 100644 --- a/trie-db/src/triedb.rs +++ b/trie-db/src/triedb.rs @@ -18,8 +18,8 @@ use crate::{ nibble::NibbleSlice, node::{decode_hash, NodeHandle, OwnedNode}, rstd::boxed::Box, - CError, DBValue, Query, Result, Trie, TrieAccess, TrieCache, TrieError, TrieHash, TrieItem, - TrieIterator, TrieKeyItem, TrieLayout, TrieRecorder, + CError, DBValue, MerkleValue, Query, Result, Trie, TrieAccess, TrieCache, TrieError, TrieHash, + TrieItem, TrieIterator, TrieKeyItem, TrieLayout, TrieRecorder, }; #[cfg(feature = "std")] use crate::{ @@ -252,6 +252,23 @@ where .look_up(key, NibbleSlice::new(key)) } + fn lookup_first_descendant( + &self, + key: &[u8], + ) -> Result>>, TrieHash, CError> { + let mut cache = self.cache.as_ref().map(|c| c.borrow_mut()); + let mut recorder = self.recorder.as_ref().map(|r| r.borrow_mut()); + + Lookup:: { + db: self.db, + query: |_: &[u8]| (), + hash: *self.root, + cache: cache.as_mut().map(|c| &mut ***c as &mut dyn TrieCache), + recorder: recorder.as_mut().map(|r| &mut ***r as &mut dyn TrieRecorder>), + } + .lookup_first_descendant(key, NibbleSlice::new(key)) + } + fn iter<'a>( &'a self, ) -> Result< diff --git a/trie-db/test/src/triedb.rs b/trie-db/test/src/triedb.rs index 9825ab50..04608838 100644 --- a/trie-db/test/src/triedb.rs +++ b/trie-db/test/src/triedb.rs @@ -644,6 +644,211 @@ fn test_recorder_with_cache_get_hash_internal() { } } +test_layouts!(test_merkle_value, test_merkle_value_internal); +fn test_merkle_value_internal() { + let mut memdb = MemoryDB::, DBValue>::default(); + let mut root = Default::default(); + + // Data set. + let key_value = vec![ + (b"A".to_vec(), vec![1; 64]), + (b"AA".to_vec(), vec![2; 64]), + (b"AAAA".to_vec(), vec![3; 64]), + (b"AAB".to_vec(), vec![4; 64]), + (b"AABBBB".to_vec(), vec![4; 1]), + (b"AB".to_vec(), vec![5; 1]), + (b"B".to_vec(), vec![6; 1]), + ]; + { + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + for (key, value) in &key_value { + t.insert(key, value).unwrap(); + } + } + + // Ensure we can fetch the merkle values for all present keys. + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + for (key, _) in &key_value { + trie.lookup_first_descendant(key).unwrap().unwrap(); + } + + // Key is not present and has no descedant, but shares a prefix. + let hash = trie.lookup_first_descendant(b"AAAAX").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AABX").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AABC").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"ABX").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AABBBBX").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"BX").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AC").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"BC").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AAAAX").unwrap(); + assert!(hash.is_none()); + // Key shares the first nibble with b"A". + let hash = trie.lookup_first_descendant(b"C").unwrap(); + assert!(hash.is_none()); + + // Key not present, but has a descendent. + let hash = trie.lookup_first_descendant(b"AAA").unwrap().unwrap(); + let expected = trie.lookup_first_descendant(b"AAAA").unwrap().unwrap(); + assert_eq!(hash, expected); + let hash = trie.lookup_first_descendant(b"AABB").unwrap().unwrap(); + let expected = trie.lookup_first_descendant(b"AABBBB").unwrap().unwrap(); + assert_eq!(hash, expected); + let hash = trie.lookup_first_descendant(b"AABBB").unwrap().unwrap(); + let expected = trie.lookup_first_descendant(b"AABBBB").unwrap().unwrap(); + assert_eq!(hash, expected); + + // Prefix AABB in between AAB and AABBBB, but has different ending char. + let hash = trie.lookup_first_descendant(b"AABBX").unwrap(); + assert!(hash.is_none()); +} + +test_layouts!(test_merkle_value_single_key, test_merkle_value_single_key_internal); +fn test_merkle_value_single_key_internal() { + let mut memdb = MemoryDB::, DBValue>::default(); + let mut root = Default::default(); + + // Data set. + let key_value = vec![(b"AAA".to_vec(), vec![1; 64])]; + { + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + for (key, value) in &key_value { + t.insert(key, value).unwrap(); + } + } + + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + + let hash = trie.lookup_first_descendant(b"AA").unwrap().unwrap(); + let expected = trie.lookup_first_descendant(b"AAA").unwrap().unwrap(); + assert_eq!(hash, expected); + + // Trie does not contain AAC or AAAA. + let hash = trie.lookup_first_descendant(b"AAC").unwrap(); + assert!(hash.is_none()); + let hash = trie.lookup_first_descendant(b"AAAA").unwrap(); + assert!(hash.is_none()); +} + +test_layouts!(test_merkle_value_branches, test_merkle_value_branches_internal); +fn test_merkle_value_branches_internal() { + let mut memdb = MemoryDB::, DBValue>::default(); + let mut root = Default::default(); + + // Data set. + let key_value = vec![(b"AAAA".to_vec(), vec![1; 64]), (b"AABA".to_vec(), vec![2; 64])]; + { + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + for (key, value) in &key_value { + t.insert(key, value).unwrap(); + } + } + + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + + // The hash is returned from the branch node. + let hash = trie.lookup_first_descendant(b"A").unwrap().unwrap(); + let aaaa_hash = trie.lookup_first_descendant(b"AAAA").unwrap().unwrap(); + let aaba_hash = trie.lookup_first_descendant(b"AABA").unwrap().unwrap(); + // Ensure the hash is not from any leaf. + assert_ne!(hash, aaaa_hash); + assert_ne!(hash, aaba_hash); +} + +test_layouts!(test_merkle_value_empty_trie, test_merkle_value_empty_trie_internal); +fn test_merkle_value_empty_trie_internal() { + let mut memdb = MemoryDB::, DBValue>::default(); + let mut root = Default::default(); + + { + // Valid state root. + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + t.insert(&[], &[]).unwrap(); + } + + // Data set is empty. + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + + let hash = trie.lookup_first_descendant(b"").unwrap(); + assert!(hash.is_none()); + + let hash = trie.lookup_first_descendant(b"A").unwrap(); + assert!(hash.is_none()); + + let hash = trie.lookup_first_descendant(b"AA").unwrap(); + assert!(hash.is_none()); + + let hash = trie.lookup_first_descendant(b"AAA").unwrap(); + assert!(hash.is_none()); + + let hash = trie.lookup_first_descendant(b"AAAA").unwrap(); + assert!(hash.is_none()); +} + +test_layouts!(test_merkle_value_modification, test_merkle_value_modification_internal); +fn test_merkle_value_modification_internal() { + let mut memdb = MemoryDB::, DBValue>::default(); + let mut root = Default::default(); + + let key_value = vec![(b"AAAA".to_vec(), vec![1; 64]), (b"AABA".to_vec(), vec![2; 64])]; + { + let mut t = TrieDBMutBuilder::::new(&mut memdb, &mut root).build(); + for (key, value) in &key_value { + t.insert(key, value).unwrap(); + } + } + + let (a_hash_lhs, aaaa_hash_lhs, aaba_hash_lhs) = { + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + + // The hash is returned from the branch node. + let hash = trie.lookup_first_descendant(b"A").unwrap().unwrap(); + let aaaa_hash = trie.lookup_first_descendant(b"AAAA").unwrap().unwrap(); + let aaba_hash = trie.lookup_first_descendant(b"AABA").unwrap().unwrap(); + + // Ensure the hash is not from any leaf. + assert_ne!(hash, aaaa_hash); + assert_ne!(hash, aaba_hash); + + (hash, aaaa_hash, aaba_hash) + }; + + // Modify AABA and expect AAAA to return the same merkle value. + { + let mut t = TrieDBMutBuilder::::from_existing(&mut memdb, &mut root).build(); + t.insert(b"AABA", &vec![3; 64]).unwrap(); + } + + let (a_hash_rhs, aaaa_hash_rhs, aaba_hash_rhs) = { + let trie = TrieDBBuilder::::new(&memdb, &root).build(); + + // The hash is returned from the branch node. + let hash = trie.lookup_first_descendant(b"A").unwrap().unwrap(); + let aaaa_hash = trie.lookup_first_descendant(b"AAAA").unwrap().unwrap(); + let aaba_hash = trie.lookup_first_descendant(b"AABA").unwrap().unwrap(); + + // Ensure the hash is not from any leaf. + assert_ne!(hash, aaaa_hash); + assert_ne!(hash, aaba_hash); + + (hash, aaaa_hash, aaba_hash) + }; + + // AAAA was not modified. + assert_eq!(aaaa_hash_lhs, aaaa_hash_rhs); + // Changes to AABA must propagate to the root. + assert_ne!(aaba_hash_lhs, aaba_hash_rhs); + assert_ne!(a_hash_lhs, a_hash_rhs); +} + test_layouts!(iterator_seek_with_recorder, iterator_seek_with_recorder_internal); fn iterator_seek_with_recorder_internal() { let d = vec![b"A".to_vec(), b"AA".to_vec(), b"AB".to_vec(), b"B".to_vec()];