Skip to main content

reth_trie_common/
updates.rs

1use crate::{
2    utils::{extend_sorted_vec, kway_merge_disjoint_sorted, kway_merge_sorted},
3    BranchNodeCompact, HashBuilder, Nibbles,
4};
5use alloc::{
6    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
7    vec::Vec,
8};
9use alloy_primitives::{
10    map::{B256Map, HashMap, HashSet},
11    FixedBytes, B256,
12};
13
14/// The aggregation of trie updates.
15#[derive(PartialEq, Eq, Clone, Default, Debug)]
16#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
17pub struct TrieUpdates {
18    /// Collection of updated intermediate account nodes indexed by full path.
19    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
20    pub account_nodes: HashMap<Nibbles, BranchNodeCompact>,
21    /// Collection of removed intermediate account nodes indexed by full path.
22    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
23    pub removed_nodes: HashSet<Nibbles>,
24    /// Collection of updated storage tries indexed by the hashed address.
25    pub storage_tries: B256Map<StorageTrieUpdates>,
26}
27
28impl TrieUpdates {
29    /// Creates a new `TrieUpdates` with pre-allocated capacity.
30    pub fn with_capacity(account_nodes: usize, storage_tries: usize) -> Self {
31        Self {
32            account_nodes: HashMap::with_capacity_and_hasher(account_nodes, Default::default()),
33            removed_nodes: HashSet::with_capacity_and_hasher(account_nodes / 4, Default::default()),
34            storage_tries: B256Map::with_capacity_and_hasher(storage_tries, Default::default()),
35        }
36    }
37
38    /// Returns `true` if the updates are empty.
39    pub fn is_empty(&self) -> bool {
40        self.account_nodes.is_empty() &&
41            self.removed_nodes.is_empty() &&
42            self.storage_tries.is_empty()
43    }
44
45    /// Returns reference to updated account nodes.
46    pub const fn account_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
47        &self.account_nodes
48    }
49
50    /// Returns a reference to removed account nodes.
51    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
52        &self.removed_nodes
53    }
54
55    /// Returns a reference to updated storage tries.
56    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdates> {
57        &self.storage_tries
58    }
59
60    /// Extends the trie updates.
61    pub fn extend(&mut self, other: Self) {
62        self.extend_common(&other);
63        self.account_nodes.extend(exclude_empty_from_pair(other.account_nodes));
64        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
65        for (hashed_address, storage_trie) in other.storage_tries {
66            self.storage_tries.entry(hashed_address).or_default().extend(storage_trie);
67        }
68    }
69
70    /// Extends the trie updates.
71    ///
72    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
73    pub fn extend_ref(&mut self, other: &Self) {
74        self.extend_common(other);
75        self.account_nodes.extend(exclude_empty_from_pair(
76            other.account_nodes.iter().map(|(k, v)| (*k, v.clone())),
77        ));
78        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied()));
79        for (hashed_address, storage_trie) in &other.storage_tries {
80            self.storage_tries.entry(*hashed_address).or_default().extend_ref(storage_trie);
81        }
82    }
83
84    fn extend_common(&mut self, other: &Self) {
85        self.account_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
86    }
87
88    /// Extend trie updates with sorted data, converting directly into the unsorted `HashMap`
89    /// representation. This is more efficient than first converting to `TrieUpdates` and
90    /// then extending, as it avoids creating intermediate `HashMap` allocations.
91    ///
92    /// This top-level helper merges account nodes and delegates each account's storage trie to
93    /// [`StorageTrieUpdates::extend_from_sorted`].
94    pub fn extend_from_sorted(&mut self, sorted: &TrieUpdatesSorted) {
95        // Reserve capacity for account nodes
96        let new_nodes_count = sorted.account_nodes.len();
97        self.account_nodes.reserve(new_nodes_count);
98
99        // Insert account nodes from sorted (only non-None entries)
100        for (nibbles, maybe_node) in &sorted.account_nodes {
101            if nibbles.is_empty() {
102                continue;
103            }
104            match maybe_node {
105                Some(node) => {
106                    self.removed_nodes.remove(nibbles);
107                    self.account_nodes.insert(*nibbles, node.clone());
108                }
109                None => {
110                    self.account_nodes.remove(nibbles);
111                    self.removed_nodes.insert(*nibbles);
112                }
113            }
114        }
115
116        // Extend storage tries
117        self.storage_tries.reserve(sorted.storage_tries.len());
118        for (hashed_address, sorted_storage) in &sorted.storage_tries {
119            self.storage_tries
120                .entry(*hashed_address)
121                .or_default()
122                .extend_from_sorted(sorted_storage);
123        }
124    }
125
126    /// Insert storage updates for a given hashed address.
127    pub fn insert_storage_updates(
128        &mut self,
129        hashed_address: B256,
130        storage_updates: StorageTrieUpdates,
131    ) {
132        if storage_updates.is_empty() {
133            return;
134        }
135        let existing = self.storage_tries.insert(hashed_address, storage_updates);
136        debug_assert!(existing.is_none());
137    }
138
139    /// Finalize state trie updates.
140    pub fn finalize(
141        &mut self,
142        hash_builder: HashBuilder,
143        removed_keys: HashSet<Nibbles>,
144        destroyed_storage_trie_nodes: B256Map<Vec<Nibbles>>,
145    ) {
146        // Retrieve updated nodes from hash builder.
147        let (_, updated_nodes) = hash_builder.split();
148        self.account_nodes.extend(exclude_empty_from_pair(updated_nodes));
149
150        // Add deleted node paths.
151        self.removed_nodes.extend(exclude_empty(removed_keys));
152
153        // Add removed storage trie nodes for destroyed accounts.
154        for (hashed_address, removed_nodes) in destroyed_storage_trie_nodes {
155            let storage_updates = self.storage_tries.entry(hashed_address).or_default();
156            storage_updates.removed_nodes.extend(removed_nodes);
157        }
158    }
159
160    /// Converts trie updates into [`TrieUpdatesSorted`].
161    pub fn into_sorted(mut self) -> TrieUpdatesSorted {
162        let mut account_nodes = self
163            .account_nodes
164            .drain()
165            .map(|(path, node)| {
166                // Updated nodes take precedence over removed nodes.
167                self.removed_nodes.remove(&path);
168                (path, Some(node))
169            })
170            .collect::<Vec<_>>();
171
172        account_nodes.extend(self.removed_nodes.drain().map(|path| (path, None)));
173        account_nodes.sort_unstable_by_key(|a| a.0);
174
175        let storage_tries = self
176            .storage_tries
177            .drain()
178            .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted()))
179            .collect();
180        TrieUpdatesSorted { account_nodes, storage_tries }
181    }
182
183    /// Creates a sorted copy without consuming self.
184    /// More efficient than `.clone().into_sorted()` as it avoids cloning `HashMap` metadata.
185    pub fn clone_into_sorted(&self) -> TrieUpdatesSorted {
186        let mut account_nodes = self
187            .account_nodes
188            .iter()
189            .map(|(path, node)| (*path, Some(node.clone())))
190            .collect::<Vec<_>>();
191
192        // Add removed nodes that aren't already updated (updated nodes take precedence)
193        account_nodes.extend(
194            self.removed_nodes
195                .iter()
196                .filter(|path| !self.account_nodes.contains_key(*path))
197                .map(|path| (*path, None)),
198        );
199        account_nodes.sort_unstable_by_key(|a| a.0);
200
201        let storage_tries = self
202            .storage_tries
203            .iter()
204            .map(|(&hashed_address, updates)| (hashed_address, updates.clone_into_sorted()))
205            .collect();
206        TrieUpdatesSorted { account_nodes, storage_tries }
207    }
208
209    /// Converts trie updates into [`TrieUpdatesSortedRef`].
210    pub fn into_sorted_ref(&self) -> TrieUpdatesSortedRef<'_> {
211        let mut account_nodes = self.account_nodes.iter().collect::<Vec<_>>();
212        account_nodes.sort_unstable_by(|a, b| a.0.cmp(b.0));
213
214        TrieUpdatesSortedRef {
215            removed_nodes: self.removed_nodes.iter().collect::<BTreeSet<_>>(),
216            account_nodes,
217            storage_tries: self
218                .storage_tries
219                .iter()
220                .map(|m| (*m.0, m.1.into_sorted_ref()))
221                .collect(),
222        }
223    }
224
225    /// Clears the nodes and storage trie maps in this `TrieUpdates`.
226    pub fn clear(&mut self) {
227        self.account_nodes.clear();
228        self.removed_nodes.clear();
229        self.storage_tries.clear();
230    }
231}
232
233/// Trie updates for storage trie of a single account.
234#[derive(PartialEq, Eq, Clone, Default, Debug)]
235#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
236pub struct StorageTrieUpdates {
237    /// Collection of updated storage trie nodes.
238    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
239    pub storage_nodes: HashMap<Nibbles, BranchNodeCompact>,
240    /// Collection of removed storage trie nodes.
241    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
242    pub removed_nodes: HashSet<Nibbles>,
243}
244
245#[cfg(feature = "test-utils")]
246impl StorageTrieUpdates {
247    /// Creates storage trie updates from the provided nodes.
248    pub fn new(updates: impl IntoIterator<Item = (Nibbles, BranchNodeCompact)>) -> Self {
249        Self { storage_nodes: exclude_empty_from_pair(updates).collect(), ..Default::default() }
250    }
251}
252
253impl StorageTrieUpdates {
254    /// Returns the length of updated nodes.
255    pub fn len(&self) -> usize {
256        self.storage_nodes.len() + self.removed_nodes.len()
257    }
258
259    /// Returns reference to updated storage nodes.
260    pub const fn storage_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
261        &self.storage_nodes
262    }
263
264    /// Returns reference to removed storage nodes.
265    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
266        &self.removed_nodes
267    }
268
269    /// Returns `true` if storage updates are empty.
270    pub fn is_empty(&self) -> bool {
271        self.storage_nodes.is_empty() && self.removed_nodes.is_empty()
272    }
273
274    /// Extends storage trie updates.
275    pub fn extend(&mut self, other: Self) {
276        self.extend_common(&other);
277        self.storage_nodes.extend(exclude_empty_from_pair(other.storage_nodes));
278        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
279    }
280
281    /// Extends storage trie updates.
282    ///
283    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
284    pub fn extend_ref(&mut self, other: &Self) {
285        self.extend_common(other);
286        self.storage_nodes.extend(exclude_empty_from_pair(
287            other.storage_nodes.iter().map(|(k, v)| (*k, v.clone())),
288        ));
289        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied()));
290    }
291
292    fn extend_common(&mut self, other: &Self) {
293        self.storage_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
294    }
295
296    /// Extend storage trie updates with sorted data, converting directly into the unsorted
297    /// `HashMap` representation. This is more efficient than first converting to
298    /// `StorageTrieUpdates` and then extending, as it avoids creating intermediate `HashMap`
299    /// allocations.
300    ///
301    /// This is invoked from [`TrieUpdates::extend_from_sorted`] for each account.
302    pub fn extend_from_sorted(&mut self, sorted: &StorageTrieUpdatesSorted) {
303        // Reserve capacity for storage nodes
304        let new_nodes_count = sorted.storage_nodes.len();
305        self.storage_nodes.reserve(new_nodes_count);
306
307        // Remove nodes marked as removed and insert new nodes
308        for (nibbles, maybe_node) in &sorted.storage_nodes {
309            if nibbles.is_empty() {
310                continue;
311            }
312            if let Some(node) = maybe_node {
313                self.removed_nodes.remove(nibbles);
314                self.storage_nodes.insert(*nibbles, node.clone());
315            } else {
316                self.storage_nodes.remove(nibbles);
317                self.removed_nodes.insert(*nibbles);
318            }
319        }
320    }
321
322    /// Finalize storage trie updates for by taking updates from walker and hash builder.
323    pub fn finalize(&mut self, hash_builder: HashBuilder, removed_keys: HashSet<Nibbles>) {
324        // Retrieve updated nodes from hash builder.
325        let (_, updated_nodes) = hash_builder.split();
326        self.storage_nodes.extend(exclude_empty_from_pair(updated_nodes));
327
328        // Add deleted node paths.
329        self.removed_nodes.extend(exclude_empty(removed_keys));
330    }
331
332    /// Convert storage trie updates into [`StorageTrieUpdatesSorted`].
333    pub fn into_sorted(mut self) -> StorageTrieUpdatesSorted {
334        let mut storage_nodes = self
335            .storage_nodes
336            .into_iter()
337            .map(|(path, node)| {
338                // Updated nodes take precedence over removed nodes.
339                self.removed_nodes.remove(&path);
340                (path, Some(node))
341            })
342            .collect::<Vec<_>>();
343
344        storage_nodes.extend(self.removed_nodes.into_iter().map(|path| (path, None)));
345        storage_nodes.sort_unstable_by_key(|a| a.0);
346
347        StorageTrieUpdatesSorted { storage_nodes }
348    }
349
350    /// Creates a sorted copy without consuming self.
351    /// More efficient than `.clone().into_sorted()` as it avoids cloning `HashMap` metadata.
352    pub fn clone_into_sorted(&self) -> StorageTrieUpdatesSorted {
353        let mut storage_nodes = self
354            .storage_nodes
355            .iter()
356            .map(|(path, node)| (*path, Some(node.clone())))
357            .collect::<Vec<_>>();
358
359        // Add removed nodes that aren't already updated (updated nodes take precedence)
360        storage_nodes.extend(
361            self.removed_nodes
362                .iter()
363                .filter(|path| !self.storage_nodes.contains_key(*path))
364                .map(|path| (*path, None)),
365        );
366        storage_nodes.sort_unstable_by_key(|a| a.0);
367
368        StorageTrieUpdatesSorted { storage_nodes }
369    }
370
371    /// Convert storage trie updates into [`StorageTrieUpdatesSortedRef`].
372    pub fn into_sorted_ref(&self) -> StorageTrieUpdatesSortedRef<'_> {
373        StorageTrieUpdatesSortedRef {
374            removed_nodes: self.removed_nodes.iter().collect::<BTreeSet<_>>(),
375            storage_nodes: self.storage_nodes.iter().collect::<BTreeMap<_, _>>(),
376        }
377    }
378}
379
380/// Serializes and deserializes any [`HashSet`] that includes [`Nibbles`] elements, by using the
381/// hex-encoded packed representation.
382///
383/// This also sorts the set before serializing.
384#[cfg(any(test, feature = "serde"))]
385mod serde_nibbles_set {
386    use crate::Nibbles;
387    use alloc::{
388        string::{String, ToString},
389        vec::Vec,
390    };
391    use alloy_primitives::map::HashSet;
392    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
393
394    pub(super) fn serialize<S>(map: &HashSet<Nibbles>, serializer: S) -> Result<S::Ok, S::Error>
395    where
396        S: Serializer,
397    {
398        let mut storage_nodes =
399            map.iter().map(|elem| alloy_primitives::hex::encode(elem.pack())).collect::<Vec<_>>();
400        storage_nodes.sort_unstable();
401        storage_nodes.serialize(serializer)
402    }
403
404    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<HashSet<Nibbles>, D::Error>
405    where
406        D: Deserializer<'de>,
407    {
408        Vec::<String>::deserialize(deserializer)?
409            .into_iter()
410            .map(|node| {
411                Ok(Nibbles::unpack(
412                    alloy_primitives::hex::decode(node)
413                        .map_err(|err| D::Error::custom(err.to_string()))?,
414                ))
415            })
416            .collect::<Result<HashSet<_>, _>>()
417    }
418}
419
420/// Serializes and deserializes any [`HashMap`] that uses [`Nibbles`] as keys, by using the
421/// hex-encoded packed representation.
422///
423/// This also sorts the map's keys before encoding and serializing.
424#[cfg(any(test, feature = "serde"))]
425mod serde_nibbles_map {
426    use crate::Nibbles;
427    use alloc::{
428        string::{String, ToString},
429        vec::Vec,
430    };
431    use alloy_primitives::{hex, map::HashMap};
432    use core::marker::PhantomData;
433    use serde::{
434        de::{Error, MapAccess, Visitor},
435        ser::SerializeMap,
436        Deserialize, Deserializer, Serialize, Serializer,
437    };
438
439    pub(super) fn serialize<S, T>(
440        map: &HashMap<Nibbles, T>,
441        serializer: S,
442    ) -> Result<S::Ok, S::Error>
443    where
444        S: Serializer,
445        T: Serialize,
446    {
447        let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
448        let mut storage_nodes = Vec::from_iter(map);
449        storage_nodes.sort_unstable_by_key(|node| node.0);
450        for (k, v) in storage_nodes {
451            // pack, then hex encode the Nibbles
452            let packed = alloy_primitives::hex::encode(k.pack());
453            map_serializer.serialize_entry(&packed, &v)?;
454        }
455        map_serializer.end()
456    }
457
458    pub(super) fn deserialize<'de, D, T>(deserializer: D) -> Result<HashMap<Nibbles, T>, D::Error>
459    where
460        D: Deserializer<'de>,
461        T: Deserialize<'de>,
462    {
463        struct NibblesMapVisitor<T> {
464            marker: PhantomData<T>,
465        }
466
467        impl<'de, T> Visitor<'de> for NibblesMapVisitor<T>
468        where
469            T: Deserialize<'de>,
470        {
471            type Value = HashMap<Nibbles, T>;
472
473            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
474                formatter.write_str("a map with hex-encoded Nibbles keys")
475            }
476
477            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
478            where
479                A: MapAccess<'de>,
480            {
481                let mut result = HashMap::with_capacity_and_hasher(
482                    map.size_hint().unwrap_or(0),
483                    Default::default(),
484                );
485
486                while let Some((key, value)) = map.next_entry::<String, T>()? {
487                    let decoded_key =
488                        hex::decode(&key).map_err(|err| Error::custom(err.to_string()))?;
489
490                    let nibbles = Nibbles::unpack(&decoded_key);
491
492                    result.insert(nibbles, value);
493                }
494
495                Ok(result)
496            }
497        }
498
499        deserializer.deserialize_map(NibblesMapVisitor { marker: PhantomData })
500    }
501}
502
503/// Sorted trie updates reference used for serializing trie to file.
504#[derive(PartialEq, Eq, Clone, Default, Debug)]
505#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))]
506pub struct TrieUpdatesSortedRef<'a> {
507    /// Sorted collection of updated state nodes with corresponding paths.
508    pub account_nodes: Vec<(&'a Nibbles, &'a BranchNodeCompact)>,
509    /// The set of removed state node keys.
510    pub removed_nodes: BTreeSet<&'a Nibbles>,
511    /// Storage tries stored by hashed address of the account the trie belongs to.
512    pub storage_tries: BTreeMap<FixedBytes<32>, StorageTrieUpdatesSortedRef<'a>>,
513}
514
515/// Sorted trie updates used for lookups and insertions.
516#[derive(PartialEq, Eq, Clone, Default, Debug)]
517#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
518pub struct TrieUpdatesSorted {
519    /// Sorted collection of updated state nodes with corresponding paths. None indicates that a
520    /// node was removed.
521    account_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
522    /// Storage tries stored by hashed address of the account the trie belongs to.
523    storage_tries: B256Map<StorageTrieUpdatesSorted>,
524}
525
526impl TrieUpdatesSorted {
527    /// Creates a new `TrieUpdatesSorted` with the given account nodes and storage tries.
528    ///
529    /// # Panics
530    ///
531    /// In debug mode, panics if `account_nodes` is not sorted by the `Nibbles` key,
532    /// or if any storage trie's `storage_nodes` is not sorted by its `Nibbles` key.
533    pub fn new(
534        account_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
535        storage_tries: B256Map<StorageTrieUpdatesSorted>,
536    ) -> Self {
537        debug_assert!(
538            account_nodes.is_sorted_by_key(|item| &item.0),
539            "account_nodes must be sorted by Nibbles key"
540        );
541        debug_assert!(
542            storage_tries.values().all(|storage_trie| {
543                storage_trie.storage_nodes.is_sorted_by_key(|item| &item.0)
544            }),
545            "all storage_nodes in storage_tries must be sorted by Nibbles key"
546        );
547        Self { account_nodes, storage_tries }
548    }
549
550    /// Returns `true` if the updates are empty.
551    pub fn is_empty(&self) -> bool {
552        self.account_nodes.is_empty() && self.storage_tries.is_empty()
553    }
554
555    /// Returns reference to updated account nodes.
556    pub fn account_nodes_ref(&self) -> &[(Nibbles, Option<BranchNodeCompact>)] {
557        &self.account_nodes
558    }
559
560    /// Returns reference to updated storage tries.
561    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdatesSorted> {
562        &self.storage_tries
563    }
564
565    /// Returns the total number of updates including account nodes and all storage updates.
566    pub fn total_len(&self) -> usize {
567        self.account_nodes.len() +
568            self.storage_tries.values().map(|storage| storage.len()).sum::<usize>()
569    }
570
571    /// Extends the trie updates with another set of sorted updates.
572    ///
573    /// This merges the account nodes and storage tries from `other` into `self`.
574    /// Account nodes are merged and re-sorted, with `other`'s values taking precedence
575    /// for duplicate keys.
576    ///
577    /// Sorts the account nodes after extending. Sorts the storage tries after extending, for each
578    /// storage trie.
579    pub fn extend_ref_and_sort(&mut self, other: &Self) {
580        // Extend account nodes
581        extend_sorted_vec(&mut self.account_nodes, &other.account_nodes);
582
583        // Merge storage tries
584        for (hashed_address, storage_trie) in &other.storage_tries {
585            self.storage_tries
586                .entry(*hashed_address)
587                .and_modify(|existing| existing.extend_ref(storage_trie))
588                .or_insert_with(|| storage_trie.clone());
589        }
590    }
591
592    /// Clears all account nodes and storage tries.
593    pub fn clear(&mut self) {
594        self.account_nodes.clear();
595        self.storage_tries.clear();
596    }
597
598    /// Batch-merge sorted trie updates. Iterator yields **newest to oldest**.
599    ///
600    /// For small batches, uses `extend_ref_and_sort` loop.
601    /// For large batches, uses k-way merge for O(n log k) complexity.
602    pub fn merge_batch<T: AsRef<Self> + From<Self>>(iter: impl IntoIterator<Item = T>) -> T {
603        let items: alloc::vec::Vec<_> = iter.into_iter().collect();
604        match items.len() {
605            0 => Self::default().into(),
606            1 => items.into_iter().next().expect("len == 1"),
607            _ => Self::merge_slice(&items).into(),
608        }
609    }
610
611    /// Batch-merge sorted trie updates from a slice. Slice is **newest to oldest**.
612    ///
613    /// This variant takes a slice reference directly, avoiding iterator collection overhead.
614    /// For small batches, uses `extend_ref_and_sort` loop.
615    /// For large batches, uses k-way merge for O(n log k) complexity.
616    pub fn merge_slice<T: AsRef<Self>>(items: &[T]) -> Self {
617        const THRESHOLD: usize = 30;
618
619        let k = items.len();
620
621        if k == 0 {
622            return Self::default();
623        }
624        if k == 1 {
625            return items[0].as_ref().clone();
626        }
627
628        if k < THRESHOLD {
629            // Small k: extend loop, oldest-to-newest so newer overrides older.
630            let mut iter = items.iter().rev();
631            let mut acc = iter.next().expect("k > 0").as_ref().clone();
632            for next in iter {
633                acc.extend_ref_and_sort(next.as_ref());
634            }
635            return acc;
636        }
637
638        // Large k: k-way merge.
639        let account_nodes =
640            kway_merge_sorted(items.iter().map(|i| i.as_ref().account_nodes.as_slice()));
641
642        struct StorageAcc<'a> {
643            slices: Vec<&'a [(Nibbles, Option<BranchNodeCompact>)]>,
644        }
645
646        let mut acc: B256Map<StorageAcc<'_>> = B256Map::default();
647
648        for item in items {
649            for (addr, storage) in &item.as_ref().storage_tries {
650                let entry = acc.entry(*addr).or_insert_with(|| StorageAcc { slices: Vec::new() });
651                entry.slices.push(storage.storage_nodes.as_slice());
652            }
653        }
654
655        let storage_tries = acc
656            .into_iter()
657            .map(|(addr, entry)| {
658                let storage_nodes = kway_merge_sorted(entry.slices);
659                (addr, StorageTrieUpdatesSorted { storage_nodes })
660            })
661            .collect();
662
663        Self { account_nodes, storage_tries }
664    }
665
666    /// Merges the batch and removes overlapping keys whose mask values all differ from the merged
667    /// batch value.
668    ///
669    /// Account trie nodes are masked at the top level, while storage trie entries are masked at the
670    /// node level. For duplicate keys in the batch, later items take precedence over earlier ones.
671    /// An overlapping entry is retained if any mask value is equal to the merged batch value. The
672    /// order of the mask does not matter. An empty mask merges the batch without filtering.
673    pub fn disjointed_merge_batch<'a>(batch: &[&'a Self], mask: &[&'a Self]) -> Self {
674        let account_node_count = batch.iter().map(|item| item.account_nodes.len()).sum();
675        let mut account_nodes = Vec::with_capacity(account_node_count);
676        account_nodes.extend(kway_merge_disjoint_sorted(
677            batch.iter().rev().map(|item| item.account_nodes.as_slice()),
678            mask.iter().map(|item| item.account_nodes.as_slice()),
679        ));
680
681        struct StorageAcc<'a> {
682            node_count: usize,
683            slices: Vec<&'a [(Nibbles, Option<BranchNodeCompact>)]>,
684        }
685
686        #[derive(Default)]
687        struct StorageMaskAcc<'a> {
688            slices: Vec<&'a [(Nibbles, Option<BranchNodeCompact>)]>,
689        }
690
691        let mut storage_tries = B256Map::with_capacity_and_hasher(
692            batch.iter().map(|item| item.storage_tries.len()).sum(),
693            Default::default(),
694        );
695
696        for item in batch.iter().rev() {
697            for (hashed_address, storage_trie) in &item.storage_tries {
698                let entry = storage_tries
699                    .entry(*hashed_address)
700                    .or_insert_with(|| StorageAcc { node_count: 0, slices: Vec::new() });
701                entry.slices.push(storage_trie.storage_nodes.as_slice());
702                entry.node_count += storage_trie.storage_nodes.len();
703            }
704        }
705
706        let mut storage_masks: B256Map<StorageMaskAcc<'a>> = B256Map::with_capacity_and_hasher(
707            mask.iter().map(|item| item.storage_tries.len()).sum(),
708            Default::default(),
709        );
710        for item in mask {
711            for (hashed_address, storage_trie) in &item.storage_tries {
712                let entry = storage_masks.entry(*hashed_address).or_default();
713                entry.slices.push(storage_trie.storage_nodes.as_slice());
714            }
715        }
716
717        let storage_tries = storage_tries
718            .into_iter()
719            .filter_map(|(hashed_address, entry)| {
720                let node_count = entry.node_count;
721                let storage_nodes = match storage_masks.get(&hashed_address) {
722                    Some(mask_entry) => {
723                        let mut storage_nodes = Vec::with_capacity(node_count);
724                        storage_nodes.extend(kway_merge_disjoint_sorted(
725                            entry.slices,
726                            mask_entry.slices.iter().copied(),
727                        ));
728                        storage_nodes
729                    }
730                    None => kway_merge_sorted(entry.slices),
731                };
732
733                (!storage_nodes.is_empty() || mask.is_empty())
734                    .then_some((hashed_address, StorageTrieUpdatesSorted { storage_nodes }))
735            })
736            .collect();
737
738        Self::new(account_nodes, storage_tries)
739    }
740}
741
742impl AsRef<Self> for TrieUpdatesSorted {
743    fn as_ref(&self) -> &Self {
744        self
745    }
746}
747
748impl From<TrieUpdatesSorted> for TrieUpdates {
749    fn from(sorted: TrieUpdatesSorted) -> Self {
750        let mut account_nodes = HashMap::default();
751        let mut removed_nodes = HashSet::default();
752
753        for (nibbles, node) in sorted.account_nodes {
754            if let Some(node) = node {
755                account_nodes.insert(nibbles, node);
756            } else {
757                removed_nodes.insert(nibbles);
758            }
759        }
760
761        let storage_tries = sorted
762            .storage_tries
763            .into_iter()
764            .map(|(address, storage)| (address, storage.into()))
765            .collect();
766
767        Self { account_nodes, removed_nodes, storage_tries }
768    }
769}
770
771/// Sorted storage trie updates reference used for serializing to file.
772#[derive(PartialEq, Eq, Clone, Default, Debug)]
773#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))]
774pub struct StorageTrieUpdatesSortedRef<'a> {
775    /// Sorted collection of updated storage nodes with corresponding paths.
776    pub storage_nodes: BTreeMap<&'a Nibbles, &'a BranchNodeCompact>,
777    /// The set of removed storage node keys.
778    pub removed_nodes: BTreeSet<&'a Nibbles>,
779}
780
781/// Sorted trie updates used for lookups and insertions.
782#[derive(PartialEq, Eq, Clone, Default, Debug)]
783#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
784pub struct StorageTrieUpdatesSorted {
785    /// Sorted collection of updated storage nodes with corresponding paths. None indicates a node
786    /// is removed.
787    pub storage_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
788}
789
790impl StorageTrieUpdatesSorted {
791    /// Returns reference to updated storage nodes.
792    pub fn storage_nodes_ref(&self) -> &[(Nibbles, Option<BranchNodeCompact>)] {
793        &self.storage_nodes
794    }
795
796    /// Returns the total number of storage node updates.
797    pub const fn len(&self) -> usize {
798        self.storage_nodes.len()
799    }
800
801    /// Returns `true` if there are no storage node updates.
802    pub const fn is_empty(&self) -> bool {
803        self.storage_nodes.is_empty()
804    }
805
806    /// Extends the storage trie updates with another set of sorted updates.
807    pub fn extend_ref(&mut self, other: &Self) {
808        extend_sorted_vec(&mut self.storage_nodes, &other.storage_nodes);
809    }
810
811    /// Batch-merge sorted storage trie updates. Iterator yields **newest to oldest**.
812    pub fn merge_batch<'a>(updates: impl IntoIterator<Item = &'a Self>) -> Self {
813        let updates: Vec<_> = updates.into_iter().collect();
814        Self {
815            storage_nodes: kway_merge_sorted(updates.iter().map(|u| u.storage_nodes.as_slice())),
816        }
817    }
818}
819
820/// Excludes empty nibbles from the given iterator.
821fn exclude_empty(iter: impl IntoIterator<Item = Nibbles>) -> impl Iterator<Item = Nibbles> {
822    iter.into_iter().filter(|n| !n.is_empty())
823}
824
825/// Excludes empty nibbles from the given iterator of pairs where the nibbles are the key.
826fn exclude_empty_from_pair<V>(
827    iter: impl IntoIterator<Item = (Nibbles, V)>,
828) -> impl Iterator<Item = (Nibbles, V)> {
829    iter.into_iter().filter(|(n, _)| !n.is_empty())
830}
831
832impl From<StorageTrieUpdatesSorted> for StorageTrieUpdates {
833    fn from(sorted: StorageTrieUpdatesSorted) -> Self {
834        let mut storage_nodes = HashMap::default();
835        let mut removed_nodes = HashSet::default();
836
837        for (nibbles, node) in sorted.storage_nodes {
838            if let Some(node) = node {
839                storage_nodes.insert(nibbles, node);
840            } else {
841                removed_nodes.insert(nibbles);
842            }
843        }
844
845        Self { storage_nodes, removed_nodes }
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852    use alloy_primitives::B256;
853
854    #[test]
855    fn test_finalize_keeps_storage_updates_after_destroyed_node_removal() {
856        let hashed_address = B256::with_last_byte(1);
857        let path = Nibbles::from_nibbles_unchecked([0x01]);
858        let node = BranchNodeCompact::default();
859        let mut updates = TrieUpdates {
860            storage_tries: B256Map::from_iter([(
861                hashed_address,
862                StorageTrieUpdates {
863                    storage_nodes: HashMap::from_iter([(path, node.clone())]),
864                    ..Default::default()
865                },
866            )]),
867            ..Default::default()
868        };
869
870        updates.finalize(
871            HashBuilder::default(),
872            Default::default(),
873            B256Map::from_iter([(hashed_address, vec![path])]),
874        );
875
876        assert_eq!(
877            updates.into_sorted().storage_tries[&hashed_address].storage_nodes,
878            vec![(path, Some(node))]
879        );
880    }
881
882    #[test]
883    fn test_trie_updates_sorted_extend_ref() {
884        // Test extending with empty updates
885        let mut updates1 = TrieUpdatesSorted::default();
886        let updates2 = TrieUpdatesSorted::default();
887        updates1.extend_ref_and_sort(&updates2);
888        assert_eq!(updates1.account_nodes.len(), 0);
889        assert_eq!(updates1.storage_tries.len(), 0);
890
891        // Test extending account nodes
892        let mut updates1 = TrieUpdatesSorted {
893            account_nodes: vec![
894                (Nibbles::from_nibbles_unchecked([0x01]), Some(BranchNodeCompact::default())),
895                (Nibbles::from_nibbles_unchecked([0x03]), None),
896            ],
897            storage_tries: B256Map::default(),
898        };
899        let updates2 = TrieUpdatesSorted {
900            account_nodes: vec![
901                (Nibbles::from_nibbles_unchecked([0x02]), Some(BranchNodeCompact::default())),
902                (Nibbles::from_nibbles_unchecked([0x03]), Some(BranchNodeCompact::default())), /* Override */
903            ],
904            storage_tries: B256Map::default(),
905        };
906        updates1.extend_ref_and_sort(&updates2);
907        assert_eq!(updates1.account_nodes.len(), 3);
908        // Should be sorted: 0x01, 0x02, 0x03
909        assert_eq!(updates1.account_nodes[0].0, Nibbles::from_nibbles_unchecked([0x01]));
910        assert_eq!(updates1.account_nodes[1].0, Nibbles::from_nibbles_unchecked([0x02]));
911        assert_eq!(updates1.account_nodes[2].0, Nibbles::from_nibbles_unchecked([0x03]));
912        // 0x03 should have Some value from updates2 (override)
913        assert!(updates1.account_nodes[2].1.is_some());
914
915        // Test extending storage tries
916        let storage_trie1 = StorageTrieUpdatesSorted {
917            storage_nodes: vec![(
918                Nibbles::from_nibbles_unchecked([0x0a]),
919                Some(BranchNodeCompact::default()),
920            )],
921        };
922        let storage_trie2 = StorageTrieUpdatesSorted {
923            storage_nodes: vec![(Nibbles::from_nibbles_unchecked([0x0b]), None)],
924        };
925
926        let hashed_address1 = B256::from([1; 32]);
927        let hashed_address2 = B256::from([2; 32]);
928
929        let mut updates1 = TrieUpdatesSorted {
930            account_nodes: vec![],
931            storage_tries: B256Map::from_iter([(hashed_address1, storage_trie1.clone())]),
932        };
933        let updates2 = TrieUpdatesSorted {
934            account_nodes: vec![],
935            storage_tries: B256Map::from_iter([
936                (hashed_address1, storage_trie2),
937                (hashed_address2, storage_trie1),
938            ]),
939        };
940        updates1.extend_ref_and_sort(&updates2);
941        assert_eq!(updates1.storage_tries.len(), 2);
942        assert!(updates1.storage_tries.contains_key(&hashed_address1));
943        assert!(updates1.storage_tries.contains_key(&hashed_address2));
944        // Check that storage trie for hashed_address1 was extended
945        let merged_storage = &updates1.storage_tries[&hashed_address1];
946        assert_eq!(merged_storage.storage_nodes.len(), 2);
947    }
948
949    #[test]
950    fn test_trie_updates_sorted_disjointed_merge_batch() {
951        let kept_node = Nibbles::from_nibbles_unchecked([0x01]);
952        let removed_node = Nibbles::from_nibbles_unchecked([0x02]);
953        let kept_storage = B256::from([3; 32]);
954        let slot1 = Nibbles::from_nibbles_unchecked([0x0a]);
955        let slot2 = Nibbles::from_nibbles_unchecked([0x0b]);
956
957        let older = TrieUpdatesSorted::new(
958            vec![(kept_node, Some(BranchNodeCompact::default())), (removed_node, None)],
959            B256Map::from_iter([(
960                kept_storage,
961                StorageTrieUpdatesSorted { storage_nodes: vec![(slot1, None)] },
962            )]),
963        );
964
965        let newer = TrieUpdatesSorted::new(
966            vec![(kept_node, None)],
967            B256Map::from_iter([(
968                kept_storage,
969                StorageTrieUpdatesSorted {
970                    storage_nodes: vec![(slot1, Some(BranchNodeCompact::default())), (slot2, None)],
971                },
972            )]),
973        );
974
975        let remove_a = TrieUpdatesSorted::new(
976            vec![(removed_node, Some(BranchNodeCompact::default()))],
977            B256Map::from_iter([(
978                kept_storage,
979                StorageTrieUpdatesSorted {
980                    storage_nodes: vec![(slot2, Some(BranchNodeCompact::default()))],
981                },
982            )]),
983        );
984
985        let remove_b = TrieUpdatesSorted::new(
986            vec![(Nibbles::from_nibbles_unchecked([0x0f]), Some(BranchNodeCompact::default()))],
987            B256Map::default(),
988        );
989
990        let result =
991            TrieUpdatesSorted::disjointed_merge_batch(&[&older, &newer], &[&remove_b, &remove_a]);
992
993        assert_eq!(result.account_nodes, vec![(kept_node, None)]);
994        assert_eq!(result.storage_tries.len(), 1);
995        assert_eq!(
996            result.storage_tries.get(&kept_storage),
997            Some(&StorageTrieUpdatesSorted {
998                storage_nodes: vec![(slot1, Some(BranchNodeCompact::default()))],
999            })
1000        );
1001    }
1002
1003    #[test]
1004    fn test_trie_updates_sorted_disjointed_merge_batch_empty_mask_merges_batch() {
1005        let node = Nibbles::from_nibbles_unchecked([0x01]);
1006        let storage = B256::with_last_byte(2);
1007        let storage_node = Nibbles::from_nibbles_unchecked([0x03]);
1008        let empty_storage = B256::with_last_byte(4);
1009        let older = TrieUpdatesSorted::new(
1010            vec![(node, Some(BranchNodeCompact::default()))],
1011            B256Map::from_iter([
1012                (storage, StorageTrieUpdatesSorted { storage_nodes: vec![(storage_node, None)] }),
1013                (empty_storage, StorageTrieUpdatesSorted::default()),
1014            ]),
1015        );
1016        let newer = TrieUpdatesSorted::new(
1017            vec![(node, None)],
1018            B256Map::from_iter([(
1019                storage,
1020                StorageTrieUpdatesSorted {
1021                    storage_nodes: vec![(storage_node, Some(BranchNodeCompact::default()))],
1022                },
1023            )]),
1024        );
1025        let expected = TrieUpdatesSorted::merge_batch(vec![newer.clone(), older.clone()]);
1026
1027        let result = TrieUpdatesSorted::disjointed_merge_batch(&[&older, &newer], &[]);
1028
1029        assert_eq!(result, expected);
1030    }
1031
1032    #[test]
1033    fn test_trie_updates_sorted_disjointed_merge_batch_removes_overlapping_batch_key() {
1034        let overlapping_node = Nibbles::from_nibbles_unchecked([0x03]);
1035
1036        let older = TrieUpdatesSorted::new(
1037            vec![(overlapping_node, Some(BranchNodeCompact::default()))],
1038            B256Map::default(),
1039        );
1040
1041        let newer = TrieUpdatesSorted::new(vec![(overlapping_node, None)], B256Map::default());
1042
1043        let remove = TrieUpdatesSorted::new(
1044            vec![(overlapping_node, Some(BranchNodeCompact::default()))],
1045            B256Map::default(),
1046        );
1047
1048        let result = TrieUpdatesSorted::disjointed_merge_batch(&[&older, &newer], &[&remove]);
1049
1050        assert!(result.account_nodes.is_empty());
1051    }
1052
1053    #[test]
1054    fn test_trie_updates_sorted_disjointed_merge_batch_keeps_equal_overlaps() {
1055        fn branch(mask: u16) -> BranchNodeCompact {
1056            BranchNodeCompact::new(mask, 0, 0, vec![], None)
1057        }
1058
1059        let node_path = Nibbles::from_nibbles_unchecked([0x03]);
1060        let deleted_node_path = Nibbles::from_nibbles_unchecked([0x04]);
1061        let storage = B256::from([5; 32]);
1062        let deleted_storage = B256::from([6; 32]);
1063        let storage_node_path = Nibbles::from_nibbles_unchecked([0x0c]);
1064        let deleted_storage_node_path = Nibbles::from_nibbles_unchecked([0x0d]);
1065        let batch = TrieUpdatesSorted::new(
1066            vec![(node_path, Some(branch(0b1010_0101))), (deleted_node_path, None)],
1067            B256Map::from_iter([
1068                (
1069                    storage,
1070                    StorageTrieUpdatesSorted {
1071                        storage_nodes: vec![(storage_node_path, Some(branch(0b0011_1100)))],
1072                    },
1073                ),
1074                (
1075                    deleted_storage,
1076                    StorageTrieUpdatesSorted {
1077                        storage_nodes: vec![(deleted_storage_node_path, None)],
1078                    },
1079                ),
1080            ]),
1081        );
1082        let different_mask = TrieUpdatesSorted::new(
1083            vec![
1084                (node_path, Some(branch(0b0101_1010))),
1085                (deleted_node_path, Some(branch(0b1111_0000))),
1086            ],
1087            B256Map::from_iter([
1088                (
1089                    storage,
1090                    StorageTrieUpdatesSorted {
1091                        storage_nodes: vec![(storage_node_path, Some(branch(0b1100_0011)))],
1092                    },
1093                ),
1094                (
1095                    deleted_storage,
1096                    StorageTrieUpdatesSorted {
1097                        storage_nodes: vec![(deleted_storage_node_path, Some(branch(0b0000_1111)))],
1098                    },
1099                ),
1100            ]),
1101        );
1102        let equal_mask = batch.clone();
1103
1104        let result =
1105            TrieUpdatesSorted::disjointed_merge_batch(&[&batch], &[&different_mask, &equal_mask]);
1106        let reversed =
1107            TrieUpdatesSorted::disjointed_merge_batch(&[&batch], &[&equal_mask, &different_mask]);
1108
1109        assert_eq!(result, batch);
1110        assert_eq!(reversed, result);
1111    }
1112
1113    #[test]
1114    fn test_trie_updates_sorted_disjointed_merge_batch_uses_exact_key_masking() {
1115        let hashed_address = B256::from([7; 32]);
1116        let grandparent = Nibbles::from_nibbles_unchecked([0x05]);
1117        let parent = Nibbles::from_nibbles_unchecked([0x05, 0x04]);
1118        let child = Nibbles::from_nibbles_unchecked([0x05, 0x04, 0x03]);
1119        let different_node = BranchNodeCompact::new(1, 0, 0, vec![], None);
1120
1121        let batch = TrieUpdatesSorted::new(
1122            vec![
1123                (grandparent, Some(BranchNodeCompact::default())),
1124                (parent, Some(BranchNodeCompact::default())),
1125                (child, Some(BranchNodeCompact::default())),
1126            ],
1127            B256Map::from_iter([(
1128                hashed_address,
1129                StorageTrieUpdatesSorted {
1130                    storage_nodes: vec![
1131                        (grandparent, Some(BranchNodeCompact::default())),
1132                        (parent, Some(BranchNodeCompact::default())),
1133                        (child, Some(BranchNodeCompact::default())),
1134                    ],
1135                },
1136            )]),
1137        );
1138        let mask = TrieUpdatesSorted::new(
1139            vec![
1140                (grandparent, Some(different_node.clone())),
1141                (parent, Some(different_node.clone())),
1142            ],
1143            B256Map::from_iter([(
1144                hashed_address,
1145                StorageTrieUpdatesSorted {
1146                    storage_nodes: vec![
1147                        (grandparent, Some(different_node.clone())),
1148                        (parent, Some(different_node)),
1149                    ],
1150                },
1151            )]),
1152        );
1153
1154        let result = TrieUpdatesSorted::disjointed_merge_batch(&[&batch], &[&mask]);
1155
1156        assert_eq!(result.account_nodes, vec![(child, Some(BranchNodeCompact::default()))]);
1157        assert_eq!(
1158            result.storage_tries.get(&hashed_address),
1159            Some(&StorageTrieUpdatesSorted {
1160                storage_nodes: vec![(child, Some(BranchNodeCompact::default()))],
1161            })
1162        );
1163    }
1164
1165    #[test]
1166    fn test_trie_updates_sorted_disjointed_merge_batch_ignores_empty_storage_mask() {
1167        let storage = B256::from([6; 32]);
1168        let slot = Nibbles::from_nibbles_unchecked([0x0d]);
1169
1170        let batch = TrieUpdatesSorted::new(
1171            vec![],
1172            B256Map::from_iter([(
1173                storage,
1174                StorageTrieUpdatesSorted {
1175                    storage_nodes: vec![(slot, Some(BranchNodeCompact::default()))],
1176                },
1177            )]),
1178        );
1179        let mask = TrieUpdatesSorted::new(
1180            vec![],
1181            B256Map::from_iter([(storage, StorageTrieUpdatesSorted { storage_nodes: vec![] })]),
1182        );
1183
1184        let result = TrieUpdatesSorted::disjointed_merge_batch(&[&batch], &[&mask]);
1185
1186        assert_eq!(
1187            result.storage_tries.get(&storage),
1188            Some(&StorageTrieUpdatesSorted {
1189                storage_nodes: vec![(slot, Some(BranchNodeCompact::default()))],
1190            })
1191        );
1192    }
1193
1194    /// Test extending with storage tries adds both nodes and removed nodes correctly
1195    #[test]
1196    fn test_trie_updates_extend_from_sorted_with_storage_tries() {
1197        let hashed_address = B256::from([1; 32]);
1198
1199        let mut updates = TrieUpdates::default();
1200
1201        let storage_trie = StorageTrieUpdatesSorted {
1202            storage_nodes: vec![
1203                (Nibbles::from_nibbles_unchecked([0x0a]), Some(BranchNodeCompact::default())),
1204                (Nibbles::from_nibbles_unchecked([0x0b]), None),
1205            ],
1206        };
1207
1208        let sorted = TrieUpdatesSorted {
1209            account_nodes: vec![],
1210            storage_tries: B256Map::from_iter([(hashed_address, storage_trie)]),
1211        };
1212
1213        updates.extend_from_sorted(&sorted);
1214
1215        assert_eq!(updates.storage_tries.len(), 1);
1216        let storage = updates.storage_tries.get(&hashed_address).unwrap();
1217        assert_eq!(storage.storage_nodes.len(), 1);
1218        assert!(storage.removed_nodes.contains(&Nibbles::from_nibbles_unchecked([0x0b])));
1219    }
1220
1221    /// Test storage merges nodes and tracks removed nodes
1222    #[test]
1223    fn test_storage_trie_updates_extend_from_sorted_non_deleted() {
1224        let mut storage = StorageTrieUpdates {
1225            storage_nodes: HashMap::from_iter([(
1226                Nibbles::from_nibbles_unchecked([0x01]),
1227                BranchNodeCompact::default(),
1228            )]),
1229            removed_nodes: Default::default(),
1230        };
1231
1232        let sorted = StorageTrieUpdatesSorted {
1233            storage_nodes: vec![
1234                (Nibbles::from_nibbles_unchecked([0x02]), Some(BranchNodeCompact::default())),
1235                (Nibbles::from_nibbles_unchecked([0x03]), None),
1236            ],
1237        };
1238
1239        storage.extend_from_sorted(&sorted);
1240        assert_eq!(storage.storage_nodes.len(), 2);
1241        assert!(storage.removed_nodes.contains(&Nibbles::from_nibbles_unchecked([0x03])));
1242    }
1243
1244    /// Test empty nibbles are filtered out during conversion (edge case bug)
1245    #[test]
1246    fn test_trie_updates_extend_from_sorted_filters_empty_nibbles() {
1247        let mut updates = TrieUpdates::default();
1248
1249        let sorted = TrieUpdatesSorted {
1250            account_nodes: vec![
1251                (Nibbles::default(), Some(BranchNodeCompact::default())), // Empty nibbles
1252                (Nibbles::from_nibbles_unchecked([0x01]), Some(BranchNodeCompact::default())),
1253            ],
1254            storage_tries: B256Map::default(),
1255        };
1256
1257        updates.extend_from_sorted(&sorted);
1258
1259        // Empty nibbles should be filtered out
1260        assert_eq!(updates.account_nodes.len(), 1);
1261        assert!(updates.account_nodes.contains_key(&Nibbles::from_nibbles_unchecked([0x01])));
1262        assert!(!updates.account_nodes.contains_key(&Nibbles::default()));
1263    }
1264}
1265
1266/// Bincode-compatible trie updates type serde implementations.
1267#[cfg(feature = "serde-bincode-compat")]
1268pub mod serde_bincode_compat {
1269    use crate::{BranchNodeCompact, Nibbles};
1270    use alloc::{borrow::Cow, vec::Vec};
1271    use alloy_primitives::map::{B256Map, HashMap, HashSet};
1272    use core::fmt;
1273    use serde::{
1274        de::{Error as _, SeqAccess, Visitor},
1275        Deserialize, Deserializer, Serialize, Serializer,
1276    };
1277    use serde_with::{DeserializeAs, SerializeAs};
1278
1279    /// Bincode-compatible [`super::TrieUpdates`] serde implementation.
1280    ///
1281    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1282    /// ```rust
1283    /// use reth_trie_common::{serde_bincode_compat, updates::TrieUpdates};
1284    /// use serde::{Deserialize, Serialize};
1285    /// use serde_with::serde_as;
1286    ///
1287    /// #[serde_as]
1288    /// #[derive(Serialize, Deserialize)]
1289    /// struct Data {
1290    ///     #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
1291    ///     trie_updates: TrieUpdates,
1292    /// }
1293    /// ```
1294    #[derive(Debug, Serialize, Deserialize)]
1295    pub struct TrieUpdates<'a> {
1296        account_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
1297        removed_nodes: Cow<'a, HashSet<Nibbles>>,
1298        storage_tries: B256Map<StorageTrieUpdates<'a>>,
1299    }
1300
1301    impl<'a> From<&'a super::TrieUpdates> for TrieUpdates<'a> {
1302        fn from(value: &'a super::TrieUpdates) -> Self {
1303            Self {
1304                account_nodes: Cow::Borrowed(&value.account_nodes),
1305                removed_nodes: Cow::Borrowed(&value.removed_nodes),
1306                storage_tries: value.storage_tries.iter().map(|(k, v)| (*k, v.into())).collect(),
1307            }
1308        }
1309    }
1310
1311    impl<'a> From<TrieUpdates<'a>> for super::TrieUpdates {
1312        fn from(value: TrieUpdates<'a>) -> Self {
1313            Self {
1314                account_nodes: value.account_nodes.into_owned(),
1315                removed_nodes: value.removed_nodes.into_owned(),
1316                storage_tries: value
1317                    .storage_tries
1318                    .into_iter()
1319                    .map(|(k, v)| (k, v.into()))
1320                    .collect(),
1321            }
1322        }
1323    }
1324
1325    impl SerializeAs<super::TrieUpdates> for TrieUpdates<'_> {
1326        fn serialize_as<S>(source: &super::TrieUpdates, serializer: S) -> Result<S::Ok, S::Error>
1327        where
1328            S: Serializer,
1329        {
1330            TrieUpdates::from(source).serialize(serializer)
1331        }
1332    }
1333
1334    impl<'de> DeserializeAs<'de, super::TrieUpdates> for TrieUpdates<'de> {
1335        fn deserialize_as<D>(deserializer: D) -> Result<super::TrieUpdates, D::Error>
1336        where
1337            D: Deserializer<'de>,
1338        {
1339            TrieUpdates::deserialize(deserializer).map(Into::into)
1340        }
1341    }
1342
1343    /// Bincode-compatible [`super::StorageTrieUpdates`] serde implementation.
1344    ///
1345    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1346    /// ```rust
1347    /// use reth_trie_common::{serde_bincode_compat, updates::StorageTrieUpdates};
1348    /// use serde::{Deserialize, Serialize};
1349    /// use serde_with::serde_as;
1350    ///
1351    /// #[serde_as]
1352    /// #[derive(Serialize, Deserialize)]
1353    /// struct Data {
1354    ///     #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
1355    ///     trie_updates: StorageTrieUpdates,
1356    /// }
1357    /// ```
1358    #[derive(Debug, Serialize, Deserialize)]
1359    pub struct StorageTrieUpdates<'a> {
1360        storage_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
1361        removed_nodes: Cow<'a, HashSet<Nibbles>>,
1362    }
1363
1364    impl<'a> From<&'a super::StorageTrieUpdates> for StorageTrieUpdates<'a> {
1365        fn from(value: &'a super::StorageTrieUpdates) -> Self {
1366            Self {
1367                storage_nodes: Cow::Borrowed(&value.storage_nodes),
1368                removed_nodes: Cow::Borrowed(&value.removed_nodes),
1369            }
1370        }
1371    }
1372
1373    impl<'a> From<StorageTrieUpdates<'a>> for super::StorageTrieUpdates {
1374        fn from(value: StorageTrieUpdates<'a>) -> Self {
1375            Self {
1376                storage_nodes: value.storage_nodes.into_owned(),
1377                removed_nodes: value.removed_nodes.into_owned(),
1378            }
1379        }
1380    }
1381
1382    impl SerializeAs<super::StorageTrieUpdates> for StorageTrieUpdates<'_> {
1383        fn serialize_as<S>(
1384            source: &super::StorageTrieUpdates,
1385            serializer: S,
1386        ) -> Result<S::Ok, S::Error>
1387        where
1388            S: Serializer,
1389        {
1390            StorageTrieUpdates::from(source).serialize(serializer)
1391        }
1392    }
1393
1394    impl<'de> DeserializeAs<'de, super::StorageTrieUpdates> for StorageTrieUpdates<'de> {
1395        fn deserialize_as<D>(deserializer: D) -> Result<super::StorageTrieUpdates, D::Error>
1396        where
1397            D: Deserializer<'de>,
1398        {
1399            StorageTrieUpdates::deserialize(deserializer).map(Into::into)
1400        }
1401    }
1402
1403    /// Bincode-compatible [`super::TrieUpdatesSorted`] serde implementation.
1404    ///
1405    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1406    /// ```rust
1407    /// use reth_trie_common::{serde_bincode_compat, updates::TrieUpdatesSorted};
1408    /// use serde::{Deserialize, Serialize};
1409    /// use serde_with::serde_as;
1410    ///
1411    /// #[serde_as]
1412    /// #[derive(Serialize, Deserialize)]
1413    /// struct Data {
1414    ///     #[serde_as(as = "serde_bincode_compat::updates::TrieUpdatesSorted")]
1415    ///     trie_updates: TrieUpdatesSorted,
1416    /// }
1417    /// ```
1418    #[derive(Debug, Serialize, Deserialize)]
1419    pub struct TrieUpdatesSorted<'a> {
1420        account_nodes: Cow<'a, [(Nibbles, Option<BranchNodeCompact>)]>,
1421        storage_tries: B256Map<StorageTrieUpdatesSorted<'a>>,
1422    }
1423
1424    impl<'a> From<&'a super::TrieUpdatesSorted> for TrieUpdatesSorted<'a> {
1425        fn from(value: &'a super::TrieUpdatesSorted) -> Self {
1426            Self {
1427                account_nodes: Cow::Borrowed(&value.account_nodes),
1428                storage_tries: value.storage_tries.iter().map(|(k, v)| (*k, v.into())).collect(),
1429            }
1430        }
1431    }
1432
1433    impl<'a> From<TrieUpdatesSorted<'a>> for super::TrieUpdatesSorted {
1434        fn from(value: TrieUpdatesSorted<'a>) -> Self {
1435            Self {
1436                account_nodes: value.account_nodes.into_owned(),
1437                storage_tries: value
1438                    .storage_tries
1439                    .into_iter()
1440                    .map(|(k, v)| (k, v.into()))
1441                    .collect(),
1442            }
1443        }
1444    }
1445
1446    impl SerializeAs<super::TrieUpdatesSorted> for TrieUpdatesSorted<'_> {
1447        fn serialize_as<S>(
1448            source: &super::TrieUpdatesSorted,
1449            serializer: S,
1450        ) -> Result<S::Ok, S::Error>
1451        where
1452            S: Serializer,
1453        {
1454            TrieUpdatesSorted::from(source).serialize(serializer)
1455        }
1456    }
1457
1458    impl<'de> DeserializeAs<'de, super::TrieUpdatesSorted> for TrieUpdatesSorted<'de> {
1459        fn deserialize_as<D>(deserializer: D) -> Result<super::TrieUpdatesSorted, D::Error>
1460        where
1461            D: Deserializer<'de>,
1462        {
1463            TrieUpdatesSorted::deserialize(deserializer).map(Into::into)
1464        }
1465    }
1466
1467    /// Bincode-compatible [`super::StorageTrieUpdatesSorted`] serde implementation.
1468    ///
1469    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1470    /// ```rust
1471    /// use reth_trie_common::{serde_bincode_compat, updates::StorageTrieUpdatesSorted};
1472    /// use serde::{Deserialize, Serialize};
1473    /// use serde_with::serde_as;
1474    ///
1475    /// #[serde_as]
1476    /// #[derive(Serialize, Deserialize)]
1477    /// struct Data {
1478    ///     #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdatesSorted")]
1479    ///     trie_updates: StorageTrieUpdatesSorted,
1480    /// }
1481    /// ```
1482    #[derive(Debug, Serialize)]
1483    pub struct StorageTrieUpdatesSorted<'a> {
1484        storage_nodes: Cow<'a, [(Nibbles, Option<BranchNodeCompact>)]>,
1485    }
1486
1487    impl<'de, 'a> Deserialize<'de> for StorageTrieUpdatesSorted<'a> {
1488        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1489        where
1490            D: Deserializer<'de>,
1491        {
1492            struct StorageTrieUpdatesSortedVisitor;
1493
1494            impl<'de> Visitor<'de> for StorageTrieUpdatesSortedVisitor {
1495                type Value = Vec<(Nibbles, Option<BranchNodeCompact>)>;
1496
1497                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1498                    formatter.write_str("storage trie updates with an optional legacy wipe marker")
1499                }
1500
1501                fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1502                where
1503                    A: SeqAccess<'de>,
1504                {
1505                    let len = seq.size_hint().unwrap_or_default();
1506                    let storage_nodes = match len {
1507                        1 => {
1508                            seq.next_element()?.ok_or_else(|| A::Error::invalid_length(0, &self))?
1509                        }
1510                        2 => {
1511                            let _: bool = seq
1512                                .next_element()?
1513                                .ok_or_else(|| A::Error::invalid_length(0, &self))?;
1514                            seq.next_element()?.ok_or_else(|| A::Error::invalid_length(1, &self))?
1515                        }
1516                        _ => return Err(A::Error::invalid_length(len, &self)),
1517                    };
1518
1519                    Ok(storage_nodes)
1520                }
1521            }
1522
1523            // Bincode uses the supplied tuple length for the current format, while MessagePack
1524            // exposes the encoded sequence length so legacy ExEx WAL entries can still be read.
1525            deserializer
1526                .deserialize_tuple(1, StorageTrieUpdatesSortedVisitor)
1527                .map(|storage_nodes| Self { storage_nodes: Cow::Owned(storage_nodes) })
1528        }
1529    }
1530
1531    impl<'a> From<&'a super::StorageTrieUpdatesSorted> for StorageTrieUpdatesSorted<'a> {
1532        fn from(value: &'a super::StorageTrieUpdatesSorted) -> Self {
1533            Self { storage_nodes: Cow::Borrowed(&value.storage_nodes) }
1534        }
1535    }
1536
1537    impl<'a> From<StorageTrieUpdatesSorted<'a>> for super::StorageTrieUpdatesSorted {
1538        fn from(value: StorageTrieUpdatesSorted<'a>) -> Self {
1539            Self { storage_nodes: value.storage_nodes.into_owned() }
1540        }
1541    }
1542
1543    impl SerializeAs<super::StorageTrieUpdatesSorted> for StorageTrieUpdatesSorted<'_> {
1544        fn serialize_as<S>(
1545            source: &super::StorageTrieUpdatesSorted,
1546            serializer: S,
1547        ) -> Result<S::Ok, S::Error>
1548        where
1549            S: Serializer,
1550        {
1551            StorageTrieUpdatesSorted::from(source).serialize(serializer)
1552        }
1553    }
1554
1555    impl<'de> DeserializeAs<'de, super::StorageTrieUpdatesSorted> for StorageTrieUpdatesSorted<'de> {
1556        fn deserialize_as<D>(deserializer: D) -> Result<super::StorageTrieUpdatesSorted, D::Error>
1557        where
1558            D: Deserializer<'de>,
1559        {
1560            StorageTrieUpdatesSorted::deserialize(deserializer).map(Into::into)
1561        }
1562    }
1563
1564    #[cfg(test)]
1565    mod tests {
1566        use crate::{
1567            serde_bincode_compat,
1568            updates::{
1569                StorageTrieUpdates, StorageTrieUpdatesSorted, TrieUpdates, TrieUpdatesSorted,
1570            },
1571            BranchNodeCompact, Nibbles,
1572        };
1573        use alloy_primitives::B256;
1574        use serde::{Deserialize, Serialize};
1575        use serde_with::serde_as;
1576
1577        #[test]
1578        fn test_trie_updates_bincode_roundtrip() {
1579            #[serde_as]
1580            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1581            struct Data {
1582                #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
1583                trie_updates: TrieUpdates,
1584            }
1585
1586            let mut data = Data { trie_updates: TrieUpdates::default() };
1587            let encoded = bincode::serialize(&data).unwrap();
1588            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1589            assert_eq!(decoded, data);
1590
1591            data.trie_updates
1592                .removed_nodes
1593                .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1594            let encoded = bincode::serialize(&data).unwrap();
1595            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1596            assert_eq!(decoded, data);
1597
1598            data.trie_updates.account_nodes.insert(
1599                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1600                BranchNodeCompact::default(),
1601            );
1602            let encoded = bincode::serialize(&data).unwrap();
1603            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1604            assert_eq!(decoded, data);
1605
1606            data.trie_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
1607            let encoded = bincode::serialize(&data).unwrap();
1608            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1609            assert_eq!(decoded, data);
1610        }
1611
1612        #[test]
1613        fn test_storage_trie_updates_bincode_roundtrip() {
1614            #[serde_as]
1615            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1616            struct Data {
1617                #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
1618                trie_updates: StorageTrieUpdates,
1619            }
1620
1621            let mut data = Data { trie_updates: StorageTrieUpdates::default() };
1622            let encoded = bincode::serialize(&data).unwrap();
1623            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1624            assert_eq!(decoded, data);
1625
1626            data.trie_updates
1627                .removed_nodes
1628                .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1629            let encoded = bincode::serialize(&data).unwrap();
1630            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1631            assert_eq!(decoded, data);
1632
1633            data.trie_updates.storage_nodes.insert(
1634                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1635                BranchNodeCompact::default(),
1636            );
1637            let encoded = bincode::serialize(&data).unwrap();
1638            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1639            assert_eq!(decoded, data);
1640        }
1641
1642        #[test]
1643        fn test_trie_updates_sorted_bincode_roundtrip() {
1644            #[serde_as]
1645            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1646            struct Data {
1647                #[serde_as(as = "serde_bincode_compat::updates::TrieUpdatesSorted")]
1648                trie_updates: TrieUpdatesSorted,
1649            }
1650
1651            let mut data = Data { trie_updates: TrieUpdatesSorted::default() };
1652            let encoded = bincode::serialize(&data).unwrap();
1653            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1654            assert_eq!(decoded, data);
1655
1656            data.trie_updates.account_nodes.push((
1657                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1658                Some(BranchNodeCompact::default()),
1659            ));
1660            let encoded = bincode::serialize(&data).unwrap();
1661            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1662            assert_eq!(decoded, data);
1663
1664            data.trie_updates
1665                .account_nodes
1666                .push((Nibbles::from_nibbles_unchecked([0x0f, 0x0f, 0x0f, 0x0f]), None));
1667            let encoded = bincode::serialize(&data).unwrap();
1668            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1669            assert_eq!(decoded, data);
1670
1671            data.trie_updates
1672                .storage_tries
1673                .insert(B256::default(), StorageTrieUpdatesSorted::default());
1674            let encoded = bincode::serialize(&data).unwrap();
1675            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1676            assert_eq!(decoded, data);
1677        }
1678
1679        #[test]
1680        fn test_storage_trie_updates_sorted_bincode_roundtrip() {
1681            #[serde_as]
1682            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1683            struct Data {
1684                #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdatesSorted")]
1685                trie_updates: StorageTrieUpdatesSorted,
1686            }
1687
1688            let mut data = Data { trie_updates: StorageTrieUpdatesSorted::default() };
1689            let encoded = bincode::serialize(&data).unwrap();
1690            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1691            assert_eq!(decoded, data);
1692
1693            data.trie_updates.storage_nodes.push((
1694                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1695                Some(BranchNodeCompact::default()),
1696            ));
1697            let encoded = bincode::serialize(&data).unwrap();
1698            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1699            assert_eq!(decoded, data);
1700
1701            data.trie_updates
1702                .storage_nodes
1703                .push((Nibbles::from_nibbles_unchecked([0x0a, 0x0a, 0x0a, 0x0a]), None));
1704            let encoded = bincode::serialize(&data).unwrap();
1705            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1706            assert_eq!(decoded, data);
1707        }
1708    }
1709}
1710
1711#[cfg(all(test, feature = "serde"))]
1712mod serde_tests {
1713    use super::*;
1714
1715    #[test]
1716    fn test_trie_updates_serde_roundtrip() {
1717        let mut default_updates = TrieUpdates::default();
1718        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1719        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1720        assert_eq!(updates_deserialized, default_updates);
1721
1722        default_updates
1723            .removed_nodes
1724            .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1725        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1726        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1727        assert_eq!(updates_deserialized, default_updates);
1728
1729        default_updates.account_nodes.insert(
1730            Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1731            BranchNodeCompact::default(),
1732        );
1733        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1734        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1735        assert_eq!(updates_deserialized, default_updates);
1736
1737        default_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
1738        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1739        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1740        assert_eq!(updates_deserialized, default_updates);
1741    }
1742
1743    #[test]
1744    fn test_storage_trie_updates_serde_roundtrip() {
1745        let mut default_updates = StorageTrieUpdates::default();
1746        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1747        let updates_deserialized: StorageTrieUpdates =
1748            serde_json::from_str(&updates_serialized).unwrap();
1749        assert_eq!(updates_deserialized, default_updates);
1750
1751        default_updates
1752            .removed_nodes
1753            .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1754        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1755        let updates_deserialized: StorageTrieUpdates =
1756            serde_json::from_str(&updates_serialized).unwrap();
1757        assert_eq!(updates_deserialized, default_updates);
1758
1759        default_updates.storage_nodes.insert(
1760            Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1761            BranchNodeCompact::default(),
1762        );
1763        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1764        let updates_deserialized: StorageTrieUpdates =
1765            serde_json::from_str(&updates_serialized).unwrap();
1766        assert_eq!(updates_deserialized, default_updates);
1767    }
1768}