reth_trie_common/
updates.rs

1use crate::{utils::extend_sorted_vec, BranchNodeCompact, HashBuilder, Nibbles};
2use alloc::{
3    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
4    vec::Vec,
5};
6use alloy_primitives::{
7    map::{B256Map, B256Set, HashMap, HashSet},
8    FixedBytes, B256,
9};
10
11/// The aggregation of trie updates.
12#[derive(PartialEq, Eq, Clone, Default, Debug)]
13#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
14pub struct TrieUpdates {
15    /// Collection of updated intermediate account nodes indexed by full path.
16    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
17    pub account_nodes: HashMap<Nibbles, BranchNodeCompact>,
18    /// Collection of removed intermediate account nodes indexed by full path.
19    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
20    pub removed_nodes: HashSet<Nibbles>,
21    /// Collection of updated storage tries indexed by the hashed address.
22    pub storage_tries: B256Map<StorageTrieUpdates>,
23}
24
25impl TrieUpdates {
26    /// Returns `true` if the updates are empty.
27    pub fn is_empty(&self) -> bool {
28        self.account_nodes.is_empty() &&
29            self.removed_nodes.is_empty() &&
30            self.storage_tries.is_empty()
31    }
32
33    /// Returns reference to updated account nodes.
34    pub const fn account_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
35        &self.account_nodes
36    }
37
38    /// Returns a reference to removed account nodes.
39    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
40        &self.removed_nodes
41    }
42
43    /// Returns a reference to updated storage tries.
44    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdates> {
45        &self.storage_tries
46    }
47
48    /// Extends the trie updates.
49    pub fn extend(&mut self, other: Self) {
50        self.extend_common(&other);
51        self.account_nodes.extend(exclude_empty_from_pair(other.account_nodes));
52        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
53        for (hashed_address, storage_trie) in other.storage_tries {
54            self.storage_tries.entry(hashed_address).or_default().extend(storage_trie);
55        }
56    }
57
58    /// Extends the trie updates.
59    ///
60    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
61    pub fn extend_ref(&mut self, other: &Self) {
62        self.extend_common(other);
63        self.account_nodes.extend(exclude_empty_from_pair(
64            other.account_nodes.iter().map(|(k, v)| (*k, v.clone())),
65        ));
66        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied()));
67        for (hashed_address, storage_trie) in &other.storage_tries {
68            self.storage_tries.entry(*hashed_address).or_default().extend_ref(storage_trie);
69        }
70    }
71
72    fn extend_common(&mut self, other: &Self) {
73        self.account_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
74    }
75
76    /// Extend trie updates with sorted data, converting directly into the unsorted `HashMap`
77    /// representation. This is more efficient than first converting to `TrieUpdates` and
78    /// then extending, as it avoids creating intermediate `HashMap` allocations.
79    ///
80    /// This top-level helper merges account nodes and delegates each account's storage trie to
81    /// [`StorageTrieUpdates::extend_from_sorted`].
82    pub fn extend_from_sorted(&mut self, sorted: &TrieUpdatesSorted) {
83        // Reserve capacity for account nodes
84        let new_nodes_count = sorted.account_nodes.len();
85        self.account_nodes.reserve(new_nodes_count);
86
87        // Insert account nodes from sorted (only non-None entries)
88        for (nibbles, maybe_node) in &sorted.account_nodes {
89            if nibbles.is_empty() {
90                continue;
91            }
92            match maybe_node {
93                Some(node) => {
94                    self.removed_nodes.remove(nibbles);
95                    self.account_nodes.insert(*nibbles, node.clone());
96                }
97                None => {
98                    self.account_nodes.remove(nibbles);
99                    self.removed_nodes.insert(*nibbles);
100                }
101            }
102        }
103
104        // Extend storage tries
105        self.storage_tries.reserve(sorted.storage_tries.len());
106        for (hashed_address, sorted_storage) in &sorted.storage_tries {
107            self.storage_tries
108                .entry(*hashed_address)
109                .or_default()
110                .extend_from_sorted(sorted_storage);
111        }
112    }
113
114    /// Insert storage updates for a given hashed address.
115    pub fn insert_storage_updates(
116        &mut self,
117        hashed_address: B256,
118        storage_updates: StorageTrieUpdates,
119    ) {
120        if storage_updates.is_empty() {
121            return;
122        }
123        let existing = self.storage_tries.insert(hashed_address, storage_updates);
124        debug_assert!(existing.is_none());
125    }
126
127    /// Finalize state trie updates.
128    pub fn finalize(
129        &mut self,
130        hash_builder: HashBuilder,
131        removed_keys: HashSet<Nibbles>,
132        destroyed_accounts: B256Set,
133    ) {
134        // Retrieve updated nodes from hash builder.
135        let (_, updated_nodes) = hash_builder.split();
136        self.account_nodes.extend(exclude_empty_from_pair(updated_nodes));
137
138        // Add deleted node paths.
139        self.removed_nodes.extend(exclude_empty(removed_keys));
140
141        // Add deleted storage tries for destroyed accounts.
142        for destroyed in destroyed_accounts {
143            self.storage_tries.entry(destroyed).or_default().set_deleted(true);
144        }
145    }
146
147    /// Converts trie updates into [`TrieUpdatesSorted`].
148    pub fn into_sorted(mut self) -> TrieUpdatesSorted {
149        let mut account_nodes = self
150            .account_nodes
151            .drain()
152            .map(|(path, node)| {
153                // Updated nodes take precedence over removed nodes.
154                self.removed_nodes.remove(&path);
155                (path, Some(node))
156            })
157            .collect::<Vec<_>>();
158
159        account_nodes.extend(self.removed_nodes.drain().map(|path| (path, None)));
160        account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
161
162        let storage_tries = self
163            .storage_tries
164            .drain()
165            .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted()))
166            .collect();
167        TrieUpdatesSorted { account_nodes, storage_tries }
168    }
169
170    /// Converts trie updates into [`TrieUpdatesSortedRef`].
171    pub fn into_sorted_ref<'a>(&'a self) -> TrieUpdatesSortedRef<'a> {
172        let mut account_nodes = self.account_nodes.iter().collect::<Vec<_>>();
173        account_nodes.sort_unstable_by(|a, b| a.0.cmp(b.0));
174
175        TrieUpdatesSortedRef {
176            removed_nodes: self.removed_nodes.iter().collect::<BTreeSet<_>>(),
177            account_nodes,
178            storage_tries: self
179                .storage_tries
180                .iter()
181                .map(|m| (*m.0, m.1.into_sorted_ref().clone()))
182                .collect(),
183        }
184    }
185
186    /// Clears the nodes and storage trie maps in this `TrieUpdates`.
187    pub fn clear(&mut self) {
188        self.account_nodes.clear();
189        self.removed_nodes.clear();
190        self.storage_tries.clear();
191    }
192}
193
194/// Trie updates for storage trie of a single account.
195#[derive(PartialEq, Eq, Clone, Default, Debug)]
196#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
197pub struct StorageTrieUpdates {
198    /// Flag indicating whether the trie was deleted.
199    pub is_deleted: bool,
200    /// Collection of updated storage trie nodes.
201    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
202    pub storage_nodes: HashMap<Nibbles, BranchNodeCompact>,
203    /// Collection of removed storage trie nodes.
204    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
205    pub removed_nodes: HashSet<Nibbles>,
206}
207
208#[cfg(feature = "test-utils")]
209impl StorageTrieUpdates {
210    /// Creates a new storage trie updates that are not marked as deleted.
211    pub fn new(updates: impl IntoIterator<Item = (Nibbles, BranchNodeCompact)>) -> Self {
212        Self { storage_nodes: exclude_empty_from_pair(updates).collect(), ..Default::default() }
213    }
214}
215
216impl StorageTrieUpdates {
217    /// Returns empty storage trie updates with `deleted` set to `true`.
218    pub fn deleted() -> Self {
219        Self {
220            is_deleted: true,
221            storage_nodes: HashMap::default(),
222            removed_nodes: HashSet::default(),
223        }
224    }
225
226    /// Returns the length of updated nodes.
227    pub fn len(&self) -> usize {
228        (self.is_deleted as usize) + self.storage_nodes.len() + self.removed_nodes.len()
229    }
230
231    /// Returns `true` if the trie was deleted.
232    pub const fn is_deleted(&self) -> bool {
233        self.is_deleted
234    }
235
236    /// Returns reference to updated storage nodes.
237    pub const fn storage_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
238        &self.storage_nodes
239    }
240
241    /// Returns reference to removed storage nodes.
242    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
243        &self.removed_nodes
244    }
245
246    /// Returns `true` if storage updates are empty.
247    pub fn is_empty(&self) -> bool {
248        !self.is_deleted && self.storage_nodes.is_empty() && self.removed_nodes.is_empty()
249    }
250
251    /// Sets `deleted` flag on the storage trie.
252    pub const fn set_deleted(&mut self, deleted: bool) {
253        self.is_deleted = deleted;
254    }
255
256    /// Extends storage trie updates.
257    pub fn extend(&mut self, other: Self) {
258        self.extend_common(&other);
259        self.storage_nodes.extend(exclude_empty_from_pair(other.storage_nodes));
260        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
261    }
262
263    /// Extends storage trie updates.
264    ///
265    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
266    pub fn extend_ref(&mut self, other: &Self) {
267        self.extend_common(other);
268        self.storage_nodes.extend(exclude_empty_from_pair(
269            other.storage_nodes.iter().map(|(k, v)| (*k, v.clone())),
270        ));
271        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied()));
272    }
273
274    fn extend_common(&mut self, other: &Self) {
275        if other.is_deleted {
276            self.storage_nodes.clear();
277            self.removed_nodes.clear();
278        }
279        self.is_deleted |= other.is_deleted;
280        self.storage_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
281    }
282
283    /// Extend storage trie updates with sorted data, converting directly into the unsorted
284    /// `HashMap` representation. This is more efficient than first converting to
285    /// `StorageTrieUpdates` and then extending, as it avoids creating intermediate `HashMap`
286    /// allocations.
287    ///
288    /// This is invoked from [`TrieUpdates::extend_from_sorted`] for each account.
289    pub fn extend_from_sorted(&mut self, sorted: &StorageTrieUpdatesSorted) {
290        if sorted.is_deleted {
291            self.storage_nodes.clear();
292            self.removed_nodes.clear();
293        }
294        self.is_deleted |= sorted.is_deleted;
295
296        // Reserve capacity for storage nodes
297        let new_nodes_count = sorted.storage_nodes.len();
298        self.storage_nodes.reserve(new_nodes_count);
299
300        // Remove nodes marked as removed and insert new nodes
301        for (nibbles, maybe_node) in &sorted.storage_nodes {
302            if nibbles.is_empty() {
303                continue;
304            }
305            if let Some(node) = maybe_node {
306                self.removed_nodes.remove(nibbles);
307                self.storage_nodes.insert(*nibbles, node.clone());
308            } else {
309                self.storage_nodes.remove(nibbles);
310                self.removed_nodes.insert(*nibbles);
311            }
312        }
313    }
314
315    /// Finalize storage trie updates for by taking updates from walker and hash builder.
316    pub fn finalize(&mut self, hash_builder: HashBuilder, removed_keys: HashSet<Nibbles>) {
317        // Retrieve updated nodes from hash builder.
318        let (_, updated_nodes) = hash_builder.split();
319        self.storage_nodes.extend(exclude_empty_from_pair(updated_nodes));
320
321        // Add deleted node paths.
322        self.removed_nodes.extend(exclude_empty(removed_keys));
323    }
324
325    /// Convert storage trie updates into [`StorageTrieUpdatesSorted`].
326    pub fn into_sorted(mut self) -> StorageTrieUpdatesSorted {
327        let mut storage_nodes = self
328            .storage_nodes
329            .into_iter()
330            .map(|(path, node)| {
331                // Updated nodes take precedence over removed nodes.
332                self.removed_nodes.remove(&path);
333                (path, Some(node))
334            })
335            .collect::<Vec<_>>();
336
337        storage_nodes.extend(self.removed_nodes.into_iter().map(|path| (path, None)));
338        storage_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
339
340        StorageTrieUpdatesSorted { is_deleted: self.is_deleted, storage_nodes }
341    }
342
343    /// Convert storage trie updates into [`StorageTrieUpdatesSortedRef`].
344    pub fn into_sorted_ref(&self) -> StorageTrieUpdatesSortedRef<'_> {
345        StorageTrieUpdatesSortedRef {
346            is_deleted: self.is_deleted,
347            removed_nodes: self.removed_nodes.iter().collect::<BTreeSet<_>>(),
348            storage_nodes: self.storage_nodes.iter().collect::<BTreeMap<_, _>>(),
349        }
350    }
351}
352
353/// Serializes and deserializes any [`HashSet`] that includes [`Nibbles`] elements, by using the
354/// hex-encoded packed representation.
355///
356/// This also sorts the set before serializing.
357#[cfg(any(test, feature = "serde"))]
358mod serde_nibbles_set {
359    use crate::Nibbles;
360    use alloc::{
361        string::{String, ToString},
362        vec::Vec,
363    };
364    use alloy_primitives::map::HashSet;
365    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
366
367    pub(super) fn serialize<S>(map: &HashSet<Nibbles>, serializer: S) -> Result<S::Ok, S::Error>
368    where
369        S: Serializer,
370    {
371        let mut storage_nodes =
372            map.iter().map(|elem| alloy_primitives::hex::encode(elem.pack())).collect::<Vec<_>>();
373        storage_nodes.sort_unstable();
374        storage_nodes.serialize(serializer)
375    }
376
377    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<HashSet<Nibbles>, D::Error>
378    where
379        D: Deserializer<'de>,
380    {
381        Vec::<String>::deserialize(deserializer)?
382            .into_iter()
383            .map(|node| {
384                Ok(Nibbles::unpack(
385                    alloy_primitives::hex::decode(node)
386                        .map_err(|err| D::Error::custom(err.to_string()))?,
387                ))
388            })
389            .collect::<Result<HashSet<_>, _>>()
390    }
391}
392
393/// Serializes and deserializes any [`HashMap`] that uses [`Nibbles`] as keys, by using the
394/// hex-encoded packed representation.
395///
396/// This also sorts the map's keys before encoding and serializing.
397#[cfg(any(test, feature = "serde"))]
398mod serde_nibbles_map {
399    use crate::Nibbles;
400    use alloc::{
401        string::{String, ToString},
402        vec::Vec,
403    };
404    use alloy_primitives::{hex, map::HashMap};
405    use core::marker::PhantomData;
406    use serde::{
407        de::{Error, MapAccess, Visitor},
408        ser::SerializeMap,
409        Deserialize, Deserializer, Serialize, Serializer,
410    };
411
412    pub(super) fn serialize<S, T>(
413        map: &HashMap<Nibbles, T>,
414        serializer: S,
415    ) -> Result<S::Ok, S::Error>
416    where
417        S: Serializer,
418        T: Serialize,
419    {
420        let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
421        let mut storage_nodes = Vec::from_iter(map);
422        storage_nodes.sort_unstable_by_key(|node| node.0);
423        for (k, v) in storage_nodes {
424            // pack, then hex encode the Nibbles
425            let packed = alloy_primitives::hex::encode(k.pack());
426            map_serializer.serialize_entry(&packed, &v)?;
427        }
428        map_serializer.end()
429    }
430
431    pub(super) fn deserialize<'de, D, T>(deserializer: D) -> Result<HashMap<Nibbles, T>, D::Error>
432    where
433        D: Deserializer<'de>,
434        T: Deserialize<'de>,
435    {
436        struct NibblesMapVisitor<T> {
437            marker: PhantomData<T>,
438        }
439
440        impl<'de, T> Visitor<'de> for NibblesMapVisitor<T>
441        where
442            T: Deserialize<'de>,
443        {
444            type Value = HashMap<Nibbles, T>;
445
446            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
447                formatter.write_str("a map with hex-encoded Nibbles keys")
448            }
449
450            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
451            where
452                A: MapAccess<'de>,
453            {
454                let mut result = HashMap::with_capacity_and_hasher(
455                    map.size_hint().unwrap_or(0),
456                    Default::default(),
457                );
458
459                while let Some((key, value)) = map.next_entry::<String, T>()? {
460                    let decoded_key =
461                        hex::decode(&key).map_err(|err| Error::custom(err.to_string()))?;
462
463                    let nibbles = Nibbles::unpack(&decoded_key);
464
465                    result.insert(nibbles, value);
466                }
467
468                Ok(result)
469            }
470        }
471
472        deserializer.deserialize_map(NibblesMapVisitor { marker: PhantomData })
473    }
474}
475
476/// Sorted trie updates reference used for serializing trie to file.
477#[derive(PartialEq, Eq, Clone, Default, Debug)]
478#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))]
479pub struct TrieUpdatesSortedRef<'a> {
480    /// Sorted collection of updated state nodes with corresponding paths.
481    pub account_nodes: Vec<(&'a Nibbles, &'a BranchNodeCompact)>,
482    /// The set of removed state node keys.
483    pub removed_nodes: BTreeSet<&'a Nibbles>,
484    /// Storage tries stored by hashed address of the account the trie belongs to.
485    pub storage_tries: BTreeMap<FixedBytes<32>, StorageTrieUpdatesSortedRef<'a>>,
486}
487
488/// Sorted trie updates used for lookups and insertions.
489#[derive(PartialEq, Eq, Clone, Default, Debug)]
490#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
491pub struct TrieUpdatesSorted {
492    /// Sorted collection of updated state nodes with corresponding paths. None indicates that a
493    /// node was removed.
494    account_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
495    /// Storage tries stored by hashed address of the account the trie belongs to.
496    storage_tries: B256Map<StorageTrieUpdatesSorted>,
497}
498
499impl TrieUpdatesSorted {
500    /// Creates a new `TrieUpdatesSorted` with the given account nodes and storage tries.
501    ///
502    /// # Panics
503    ///
504    /// In debug mode, panics if `account_nodes` is not sorted by the `Nibbles` key,
505    /// or if any storage trie's `storage_nodes` is not sorted by its `Nibbles` key.
506    pub fn new(
507        account_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
508        storage_tries: B256Map<StorageTrieUpdatesSorted>,
509    ) -> Self {
510        debug_assert!(
511            account_nodes.is_sorted_by_key(|item| &item.0),
512            "account_nodes must be sorted by Nibbles key"
513        );
514        debug_assert!(
515            storage_tries.values().all(|storage_trie| {
516                storage_trie.storage_nodes.is_sorted_by_key(|item| &item.0)
517            }),
518            "all storage_nodes in storage_tries must be sorted by Nibbles key"
519        );
520        Self { account_nodes, storage_tries }
521    }
522
523    /// Returns `true` if the updates are empty.
524    pub fn is_empty(&self) -> bool {
525        self.account_nodes.is_empty() && self.storage_tries.is_empty()
526    }
527
528    /// Returns reference to updated account nodes.
529    pub fn account_nodes_ref(&self) -> &[(Nibbles, Option<BranchNodeCompact>)] {
530        &self.account_nodes
531    }
532
533    /// Returns reference to updated storage tries.
534    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdatesSorted> {
535        &self.storage_tries
536    }
537
538    /// Returns the total number of updates including account nodes and all storage updates.
539    pub fn total_len(&self) -> usize {
540        self.account_nodes.len() +
541            self.storage_tries.values().map(|storage| storage.len()).sum::<usize>()
542    }
543
544    /// Extends the trie updates with another set of sorted updates.
545    ///
546    /// This merges the account nodes and storage tries from `other` into `self`.
547    /// Account nodes are merged and re-sorted, with `other`'s values taking precedence
548    /// for duplicate keys.
549    pub fn extend_ref(&mut self, other: &Self) {
550        // Extend account nodes
551        extend_sorted_vec(&mut self.account_nodes, &other.account_nodes);
552
553        // Merge storage tries
554        for (hashed_address, storage_trie) in &other.storage_tries {
555            self.storage_tries
556                .entry(*hashed_address)
557                .and_modify(|existing| existing.extend_ref(storage_trie))
558                .or_insert_with(|| storage_trie.clone());
559        }
560    }
561
562    /// Clears all account nodes and storage tries.
563    pub fn clear(&mut self) {
564        self.account_nodes.clear();
565        self.storage_tries.clear();
566    }
567}
568
569impl AsRef<Self> for TrieUpdatesSorted {
570    fn as_ref(&self) -> &Self {
571        self
572    }
573}
574
575impl From<TrieUpdatesSorted> for TrieUpdates {
576    fn from(sorted: TrieUpdatesSorted) -> Self {
577        let mut account_nodes = HashMap::default();
578        let mut removed_nodes = HashSet::default();
579
580        for (nibbles, node) in sorted.account_nodes {
581            if let Some(node) = node {
582                account_nodes.insert(nibbles, node);
583            } else {
584                removed_nodes.insert(nibbles);
585            }
586        }
587
588        let storage_tries = sorted
589            .storage_tries
590            .into_iter()
591            .map(|(address, storage)| (address, storage.into()))
592            .collect();
593
594        Self { account_nodes, removed_nodes, storage_tries }
595    }
596}
597
598/// Sorted storage trie updates reference used for serializing to file.
599#[derive(PartialEq, Eq, Clone, Default, Debug)]
600#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))]
601pub struct StorageTrieUpdatesSortedRef<'a> {
602    /// Flag indicating whether the trie has been deleted/wiped.
603    pub is_deleted: bool,
604    /// Sorted collection of updated storage nodes with corresponding paths.
605    pub storage_nodes: BTreeMap<&'a Nibbles, &'a BranchNodeCompact>,
606    /// The set of removed storage node keys.
607    pub removed_nodes: BTreeSet<&'a Nibbles>,
608}
609
610/// Sorted trie updates used for lookups and insertions.
611#[derive(PartialEq, Eq, Clone, Default, Debug)]
612#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
613pub struct StorageTrieUpdatesSorted {
614    /// Flag indicating whether the trie has been deleted/wiped.
615    pub is_deleted: bool,
616    /// Sorted collection of updated storage nodes with corresponding paths. None indicates a node
617    /// is removed.
618    pub storage_nodes: Vec<(Nibbles, Option<BranchNodeCompact>)>,
619}
620
621impl StorageTrieUpdatesSorted {
622    /// Returns `true` if the trie was deleted.
623    pub const fn is_deleted(&self) -> bool {
624        self.is_deleted
625    }
626
627    /// Returns reference to updated storage nodes.
628    pub fn storage_nodes_ref(&self) -> &[(Nibbles, Option<BranchNodeCompact>)] {
629        &self.storage_nodes
630    }
631
632    /// Returns the total number of storage node updates.
633    pub const fn len(&self) -> usize {
634        self.storage_nodes.len()
635    }
636
637    /// Returns `true` if there are no storage node updates.
638    pub const fn is_empty(&self) -> bool {
639        self.storage_nodes.is_empty()
640    }
641
642    /// Extends the storage trie updates with another set of sorted updates.
643    ///
644    /// If `other` is marked as deleted, this will be marked as deleted and all nodes cleared.
645    /// Otherwise, nodes are merged with `other`'s values taking precedence for duplicates.
646    pub fn extend_ref(&mut self, other: &Self) {
647        if other.is_deleted {
648            self.is_deleted = true;
649            self.storage_nodes.clear();
650            self.storage_nodes.extend(other.storage_nodes.iter().cloned());
651            return;
652        }
653
654        // Extend storage nodes
655        extend_sorted_vec(&mut self.storage_nodes, &other.storage_nodes);
656        self.is_deleted = self.is_deleted || other.is_deleted;
657    }
658}
659
660/// Excludes empty nibbles from the given iterator.
661fn exclude_empty(iter: impl IntoIterator<Item = Nibbles>) -> impl Iterator<Item = Nibbles> {
662    iter.into_iter().filter(|n| !n.is_empty())
663}
664
665/// Excludes empty nibbles from the given iterator of pairs where the nibbles are the key.
666fn exclude_empty_from_pair<V>(
667    iter: impl IntoIterator<Item = (Nibbles, V)>,
668) -> impl Iterator<Item = (Nibbles, V)> {
669    iter.into_iter().filter(|(n, _)| !n.is_empty())
670}
671
672impl From<StorageTrieUpdatesSorted> for StorageTrieUpdates {
673    fn from(sorted: StorageTrieUpdatesSorted) -> Self {
674        let mut storage_nodes = HashMap::default();
675        let mut removed_nodes = HashSet::default();
676
677        for (nibbles, node) in sorted.storage_nodes {
678            if let Some(node) = node {
679                storage_nodes.insert(nibbles, node);
680            } else {
681                removed_nodes.insert(nibbles);
682            }
683        }
684
685        Self { is_deleted: sorted.is_deleted, storage_nodes, removed_nodes }
686    }
687}
688
689#[cfg(test)]
690mod tests {
691    use super::*;
692    use alloy_primitives::B256;
693
694    #[test]
695    fn test_trie_updates_sorted_extend_ref() {
696        // Test extending with empty updates
697        let mut updates1 = TrieUpdatesSorted::default();
698        let updates2 = TrieUpdatesSorted::default();
699        updates1.extend_ref(&updates2);
700        assert_eq!(updates1.account_nodes.len(), 0);
701        assert_eq!(updates1.storage_tries.len(), 0);
702
703        // Test extending account nodes
704        let mut updates1 = TrieUpdatesSorted {
705            account_nodes: vec![
706                (Nibbles::from_nibbles_unchecked([0x01]), Some(BranchNodeCompact::default())),
707                (Nibbles::from_nibbles_unchecked([0x03]), None),
708            ],
709            storage_tries: B256Map::default(),
710        };
711        let updates2 = TrieUpdatesSorted {
712            account_nodes: vec![
713                (Nibbles::from_nibbles_unchecked([0x02]), Some(BranchNodeCompact::default())),
714                (Nibbles::from_nibbles_unchecked([0x03]), Some(BranchNodeCompact::default())), /* Override */
715            ],
716            storage_tries: B256Map::default(),
717        };
718        updates1.extend_ref(&updates2);
719        assert_eq!(updates1.account_nodes.len(), 3);
720        // Should be sorted: 0x01, 0x02, 0x03
721        assert_eq!(updates1.account_nodes[0].0, Nibbles::from_nibbles_unchecked([0x01]));
722        assert_eq!(updates1.account_nodes[1].0, Nibbles::from_nibbles_unchecked([0x02]));
723        assert_eq!(updates1.account_nodes[2].0, Nibbles::from_nibbles_unchecked([0x03]));
724        // 0x03 should have Some value from updates2 (override)
725        assert!(updates1.account_nodes[2].1.is_some());
726
727        // Test extending storage tries
728        let storage_trie1 = StorageTrieUpdatesSorted {
729            is_deleted: false,
730            storage_nodes: vec![(
731                Nibbles::from_nibbles_unchecked([0x0a]),
732                Some(BranchNodeCompact::default()),
733            )],
734        };
735        let storage_trie2 = StorageTrieUpdatesSorted {
736            is_deleted: false,
737            storage_nodes: vec![(Nibbles::from_nibbles_unchecked([0x0b]), None)],
738        };
739
740        let hashed_address1 = B256::from([1; 32]);
741        let hashed_address2 = B256::from([2; 32]);
742
743        let mut updates1 = TrieUpdatesSorted {
744            account_nodes: vec![],
745            storage_tries: B256Map::from_iter([(hashed_address1, storage_trie1.clone())]),
746        };
747        let updates2 = TrieUpdatesSorted {
748            account_nodes: vec![],
749            storage_tries: B256Map::from_iter([
750                (hashed_address1, storage_trie2),
751                (hashed_address2, storage_trie1),
752            ]),
753        };
754        updates1.extend_ref(&updates2);
755        assert_eq!(updates1.storage_tries.len(), 2);
756        assert!(updates1.storage_tries.contains_key(&hashed_address1));
757        assert!(updates1.storage_tries.contains_key(&hashed_address2));
758        // Check that storage trie for hashed_address1 was extended
759        let merged_storage = &updates1.storage_tries[&hashed_address1];
760        assert_eq!(merged_storage.storage_nodes.len(), 2);
761    }
762
763    #[test]
764    fn test_storage_trie_updates_sorted_extend_ref_deleted() {
765        // Test case 1: Extending with a deleted storage trie that has nodes
766        let mut storage1 = StorageTrieUpdatesSorted {
767            is_deleted: false,
768            storage_nodes: vec![
769                (Nibbles::from_nibbles_unchecked([0x01]), Some(BranchNodeCompact::default())),
770                (Nibbles::from_nibbles_unchecked([0x02]), None),
771            ],
772        };
773
774        let storage2 = StorageTrieUpdatesSorted {
775            is_deleted: true,
776            storage_nodes: vec![
777                (Nibbles::from_nibbles_unchecked([0x03]), Some(BranchNodeCompact::default())),
778                (Nibbles::from_nibbles_unchecked([0x04]), None),
779            ],
780        };
781
782        storage1.extend_ref(&storage2);
783
784        // Should be marked as deleted
785        assert!(storage1.is_deleted);
786        // Original nodes should be cleared, but other's nodes should be added
787        assert_eq!(storage1.storage_nodes.len(), 2);
788        assert_eq!(storage1.storage_nodes[0].0, Nibbles::from_nibbles_unchecked([0x03]));
789        assert_eq!(storage1.storage_nodes[1].0, Nibbles::from_nibbles_unchecked([0x04]));
790
791        // Test case 2: Extending a deleted storage trie with more nodes
792        let mut storage3 = StorageTrieUpdatesSorted {
793            is_deleted: true,
794            storage_nodes: vec![(
795                Nibbles::from_nibbles_unchecked([0x05]),
796                Some(BranchNodeCompact::default()),
797            )],
798        };
799
800        let storage4 = StorageTrieUpdatesSorted {
801            is_deleted: true,
802            storage_nodes: vec![
803                (Nibbles::from_nibbles_unchecked([0x06]), Some(BranchNodeCompact::default())),
804                (Nibbles::from_nibbles_unchecked([0x07]), None),
805            ],
806        };
807
808        storage3.extend_ref(&storage4);
809
810        // Should remain deleted
811        assert!(storage3.is_deleted);
812        // Should have nodes from other (original cleared then extended)
813        assert_eq!(storage3.storage_nodes.len(), 2);
814        assert_eq!(storage3.storage_nodes[0].0, Nibbles::from_nibbles_unchecked([0x06]));
815        assert_eq!(storage3.storage_nodes[1].0, Nibbles::from_nibbles_unchecked([0x07]));
816    }
817
818    /// Test extending with storage tries adds both nodes and removed nodes correctly
819    #[test]
820    fn test_trie_updates_extend_from_sorted_with_storage_tries() {
821        let hashed_address = B256::from([1; 32]);
822
823        let mut updates = TrieUpdates::default();
824
825        let storage_trie = StorageTrieUpdatesSorted {
826            is_deleted: false,
827            storage_nodes: vec![
828                (Nibbles::from_nibbles_unchecked([0x0a]), Some(BranchNodeCompact::default())),
829                (Nibbles::from_nibbles_unchecked([0x0b]), None),
830            ],
831        };
832
833        let sorted = TrieUpdatesSorted {
834            account_nodes: vec![],
835            storage_tries: B256Map::from_iter([(hashed_address, storage_trie)]),
836        };
837
838        updates.extend_from_sorted(&sorted);
839
840        assert_eq!(updates.storage_tries.len(), 1);
841        let storage = updates.storage_tries.get(&hashed_address).unwrap();
842        assert!(!storage.is_deleted);
843        assert_eq!(storage.storage_nodes.len(), 1);
844        assert!(storage.removed_nodes.contains(&Nibbles::from_nibbles_unchecked([0x0b])));
845    }
846
847    /// Test deleted=true clears old storage nodes before adding new ones (critical edge case)
848    #[test]
849    fn test_trie_updates_extend_from_sorted_with_deleted_storage() {
850        let hashed_address = B256::from([1; 32]);
851
852        let mut updates = TrieUpdates::default();
853        updates.storage_tries.insert(
854            hashed_address,
855            StorageTrieUpdates {
856                is_deleted: false,
857                storage_nodes: HashMap::from_iter([(
858                    Nibbles::from_nibbles_unchecked([0x01]),
859                    BranchNodeCompact::default(),
860                )]),
861                removed_nodes: Default::default(),
862            },
863        );
864
865        let storage_trie = StorageTrieUpdatesSorted {
866            is_deleted: true,
867            storage_nodes: vec![(
868                Nibbles::from_nibbles_unchecked([0x0a]),
869                Some(BranchNodeCompact::default()),
870            )],
871        };
872
873        let sorted = TrieUpdatesSorted {
874            account_nodes: vec![],
875            storage_tries: B256Map::from_iter([(hashed_address, storage_trie)]),
876        };
877
878        updates.extend_from_sorted(&sorted);
879
880        let storage = updates.storage_tries.get(&hashed_address).unwrap();
881        assert!(storage.is_deleted);
882        // After deletion, old nodes should be cleared
883        assert_eq!(storage.storage_nodes.len(), 1);
884        assert!(storage.storage_nodes.contains_key(&Nibbles::from_nibbles_unchecked([0x0a])));
885    }
886
887    /// Test non-deleted storage merges nodes and tracks removed nodes
888    #[test]
889    fn test_storage_trie_updates_extend_from_sorted_non_deleted() {
890        let mut storage = StorageTrieUpdates {
891            is_deleted: false,
892            storage_nodes: HashMap::from_iter([(
893                Nibbles::from_nibbles_unchecked([0x01]),
894                BranchNodeCompact::default(),
895            )]),
896            removed_nodes: Default::default(),
897        };
898
899        let sorted = StorageTrieUpdatesSorted {
900            is_deleted: false,
901            storage_nodes: vec![
902                (Nibbles::from_nibbles_unchecked([0x02]), Some(BranchNodeCompact::default())),
903                (Nibbles::from_nibbles_unchecked([0x03]), None),
904            ],
905        };
906
907        storage.extend_from_sorted(&sorted);
908
909        assert!(!storage.is_deleted);
910        assert_eq!(storage.storage_nodes.len(), 2);
911        assert!(storage.removed_nodes.contains(&Nibbles::from_nibbles_unchecked([0x03])));
912    }
913
914    /// Test deleted=true clears old nodes before extending (edge case)
915    #[test]
916    fn test_storage_trie_updates_extend_from_sorted_deleted() {
917        let mut storage = StorageTrieUpdates {
918            is_deleted: false,
919            storage_nodes: HashMap::from_iter([(
920                Nibbles::from_nibbles_unchecked([0x01]),
921                BranchNodeCompact::default(),
922            )]),
923            removed_nodes: Default::default(),
924        };
925
926        let sorted = StorageTrieUpdatesSorted {
927            is_deleted: true,
928            storage_nodes: vec![(
929                Nibbles::from_nibbles_unchecked([0x0a]),
930                Some(BranchNodeCompact::default()),
931            )],
932        };
933
934        storage.extend_from_sorted(&sorted);
935
936        assert!(storage.is_deleted);
937        // Old nodes should be cleared when deleted
938        assert_eq!(storage.storage_nodes.len(), 1);
939        assert!(storage.storage_nodes.contains_key(&Nibbles::from_nibbles_unchecked([0x0a])));
940    }
941
942    /// Test empty nibbles are filtered out during conversion (edge case bug)
943    #[test]
944    fn test_trie_updates_extend_from_sorted_filters_empty_nibbles() {
945        let mut updates = TrieUpdates::default();
946
947        let sorted = TrieUpdatesSorted {
948            account_nodes: vec![
949                (Nibbles::default(), Some(BranchNodeCompact::default())), // Empty nibbles
950                (Nibbles::from_nibbles_unchecked([0x01]), Some(BranchNodeCompact::default())),
951            ],
952            storage_tries: B256Map::default(),
953        };
954
955        updates.extend_from_sorted(&sorted);
956
957        // Empty nibbles should be filtered out
958        assert_eq!(updates.account_nodes.len(), 1);
959        assert!(updates.account_nodes.contains_key(&Nibbles::from_nibbles_unchecked([0x01])));
960        assert!(!updates.account_nodes.contains_key(&Nibbles::default()));
961    }
962}
963
964/// Bincode-compatible trie updates type serde implementations.
965#[cfg(feature = "serde-bincode-compat")]
966pub mod serde_bincode_compat {
967    use crate::{BranchNodeCompact, Nibbles};
968    use alloc::borrow::Cow;
969    use alloy_primitives::map::{B256Map, HashMap, HashSet};
970    use serde::{Deserialize, Deserializer, Serialize, Serializer};
971    use serde_with::{DeserializeAs, SerializeAs};
972
973    /// Bincode-compatible [`super::TrieUpdates`] serde implementation.
974    ///
975    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
976    /// ```rust
977    /// use reth_trie_common::{serde_bincode_compat, updates::TrieUpdates};
978    /// use serde::{Deserialize, Serialize};
979    /// use serde_with::serde_as;
980    ///
981    /// #[serde_as]
982    /// #[derive(Serialize, Deserialize)]
983    /// struct Data {
984    ///     #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
985    ///     trie_updates: TrieUpdates,
986    /// }
987    /// ```
988    #[derive(Debug, Serialize, Deserialize)]
989    pub struct TrieUpdates<'a> {
990        account_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
991        removed_nodes: Cow<'a, HashSet<Nibbles>>,
992        storage_tries: B256Map<StorageTrieUpdates<'a>>,
993    }
994
995    impl<'a> From<&'a super::TrieUpdates> for TrieUpdates<'a> {
996        fn from(value: &'a super::TrieUpdates) -> Self {
997            Self {
998                account_nodes: Cow::Borrowed(&value.account_nodes),
999                removed_nodes: Cow::Borrowed(&value.removed_nodes),
1000                storage_tries: value.storage_tries.iter().map(|(k, v)| (*k, v.into())).collect(),
1001            }
1002        }
1003    }
1004
1005    impl<'a> From<TrieUpdates<'a>> for super::TrieUpdates {
1006        fn from(value: TrieUpdates<'a>) -> Self {
1007            Self {
1008                account_nodes: value.account_nodes.into_owned(),
1009                removed_nodes: value.removed_nodes.into_owned(),
1010                storage_tries: value
1011                    .storage_tries
1012                    .into_iter()
1013                    .map(|(k, v)| (k, v.into()))
1014                    .collect(),
1015            }
1016        }
1017    }
1018
1019    impl SerializeAs<super::TrieUpdates> for TrieUpdates<'_> {
1020        fn serialize_as<S>(source: &super::TrieUpdates, serializer: S) -> Result<S::Ok, S::Error>
1021        where
1022            S: Serializer,
1023        {
1024            TrieUpdates::from(source).serialize(serializer)
1025        }
1026    }
1027
1028    impl<'de> DeserializeAs<'de, super::TrieUpdates> for TrieUpdates<'de> {
1029        fn deserialize_as<D>(deserializer: D) -> Result<super::TrieUpdates, D::Error>
1030        where
1031            D: Deserializer<'de>,
1032        {
1033            TrieUpdates::deserialize(deserializer).map(Into::into)
1034        }
1035    }
1036
1037    /// Bincode-compatible [`super::StorageTrieUpdates`] serde implementation.
1038    ///
1039    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1040    /// ```rust
1041    /// use reth_trie_common::{serde_bincode_compat, updates::StorageTrieUpdates};
1042    /// use serde::{Deserialize, Serialize};
1043    /// use serde_with::serde_as;
1044    ///
1045    /// #[serde_as]
1046    /// #[derive(Serialize, Deserialize)]
1047    /// struct Data {
1048    ///     #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
1049    ///     trie_updates: StorageTrieUpdates,
1050    /// }
1051    /// ```
1052    #[derive(Debug, Serialize, Deserialize)]
1053    pub struct StorageTrieUpdates<'a> {
1054        is_deleted: bool,
1055        storage_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
1056        removed_nodes: Cow<'a, HashSet<Nibbles>>,
1057    }
1058
1059    impl<'a> From<&'a super::StorageTrieUpdates> for StorageTrieUpdates<'a> {
1060        fn from(value: &'a super::StorageTrieUpdates) -> Self {
1061            Self {
1062                is_deleted: value.is_deleted,
1063                storage_nodes: Cow::Borrowed(&value.storage_nodes),
1064                removed_nodes: Cow::Borrowed(&value.removed_nodes),
1065            }
1066        }
1067    }
1068
1069    impl<'a> From<StorageTrieUpdates<'a>> for super::StorageTrieUpdates {
1070        fn from(value: StorageTrieUpdates<'a>) -> Self {
1071            Self {
1072                is_deleted: value.is_deleted,
1073                storage_nodes: value.storage_nodes.into_owned(),
1074                removed_nodes: value.removed_nodes.into_owned(),
1075            }
1076        }
1077    }
1078
1079    impl SerializeAs<super::StorageTrieUpdates> for StorageTrieUpdates<'_> {
1080        fn serialize_as<S>(
1081            source: &super::StorageTrieUpdates,
1082            serializer: S,
1083        ) -> Result<S::Ok, S::Error>
1084        where
1085            S: Serializer,
1086        {
1087            StorageTrieUpdates::from(source).serialize(serializer)
1088        }
1089    }
1090
1091    impl<'de> DeserializeAs<'de, super::StorageTrieUpdates> for StorageTrieUpdates<'de> {
1092        fn deserialize_as<D>(deserializer: D) -> Result<super::StorageTrieUpdates, D::Error>
1093        where
1094            D: Deserializer<'de>,
1095        {
1096            StorageTrieUpdates::deserialize(deserializer).map(Into::into)
1097        }
1098    }
1099
1100    #[cfg(test)]
1101    mod tests {
1102        use crate::{
1103            serde_bincode_compat,
1104            updates::{StorageTrieUpdates, TrieUpdates},
1105            BranchNodeCompact, Nibbles,
1106        };
1107        use alloy_primitives::B256;
1108        use serde::{Deserialize, Serialize};
1109        use serde_with::serde_as;
1110
1111        #[test]
1112        fn test_trie_updates_bincode_roundtrip() {
1113            #[serde_as]
1114            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1115            struct Data {
1116                #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
1117                trie_updates: TrieUpdates,
1118            }
1119
1120            let mut data = Data { trie_updates: TrieUpdates::default() };
1121            let encoded = bincode::serialize(&data).unwrap();
1122            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1123            assert_eq!(decoded, data);
1124
1125            data.trie_updates
1126                .removed_nodes
1127                .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1128            let encoded = bincode::serialize(&data).unwrap();
1129            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1130            assert_eq!(decoded, data);
1131
1132            data.trie_updates.account_nodes.insert(
1133                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1134                BranchNodeCompact::default(),
1135            );
1136            let encoded = bincode::serialize(&data).unwrap();
1137            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1138            assert_eq!(decoded, data);
1139
1140            data.trie_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
1141            let encoded = bincode::serialize(&data).unwrap();
1142            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1143            assert_eq!(decoded, data);
1144        }
1145
1146        #[test]
1147        fn test_storage_trie_updates_bincode_roundtrip() {
1148            #[serde_as]
1149            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1150            struct Data {
1151                #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
1152                trie_updates: StorageTrieUpdates,
1153            }
1154
1155            let mut data = Data { trie_updates: StorageTrieUpdates::default() };
1156            let encoded = bincode::serialize(&data).unwrap();
1157            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1158            assert_eq!(decoded, data);
1159
1160            data.trie_updates
1161                .removed_nodes
1162                .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1163            let encoded = bincode::serialize(&data).unwrap();
1164            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1165            assert_eq!(decoded, data);
1166
1167            data.trie_updates.storage_nodes.insert(
1168                Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1169                BranchNodeCompact::default(),
1170            );
1171            let encoded = bincode::serialize(&data).unwrap();
1172            let decoded: Data = bincode::deserialize(&encoded).unwrap();
1173            assert_eq!(decoded, data);
1174        }
1175    }
1176}
1177
1178#[cfg(all(test, feature = "serde"))]
1179mod serde_tests {
1180    use super::*;
1181
1182    #[test]
1183    fn test_trie_updates_serde_roundtrip() {
1184        let mut default_updates = TrieUpdates::default();
1185        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1186        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1187        assert_eq!(updates_deserialized, default_updates);
1188
1189        default_updates
1190            .removed_nodes
1191            .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1192        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1193        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1194        assert_eq!(updates_deserialized, default_updates);
1195
1196        default_updates.account_nodes.insert(
1197            Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1198            BranchNodeCompact::default(),
1199        );
1200        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1201        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1202        assert_eq!(updates_deserialized, default_updates);
1203
1204        default_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
1205        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1206        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
1207        assert_eq!(updates_deserialized, default_updates);
1208    }
1209
1210    #[test]
1211    fn test_storage_trie_updates_serde_roundtrip() {
1212        let mut default_updates = StorageTrieUpdates::default();
1213        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1214        let updates_deserialized: StorageTrieUpdates =
1215            serde_json::from_str(&updates_serialized).unwrap();
1216        assert_eq!(updates_deserialized, default_updates);
1217
1218        default_updates
1219            .removed_nodes
1220            .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f]));
1221        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1222        let updates_deserialized: StorageTrieUpdates =
1223            serde_json::from_str(&updates_serialized).unwrap();
1224        assert_eq!(updates_deserialized, default_updates);
1225
1226        default_updates.storage_nodes.insert(
1227            Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]),
1228            BranchNodeCompact::default(),
1229        );
1230        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
1231        let updates_deserialized: StorageTrieUpdates =
1232            serde_json::from_str(&updates_serialized).unwrap();
1233        assert_eq!(updates_deserialized, default_updates);
1234    }
1235}