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