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