reth_trie_common/
updates.rs

1use crate::{BranchNodeCompact, HashBuilder, Nibbles};
2use alloc::vec::Vec;
3use alloy_primitives::{
4    map::{B256Map, B256Set, HashMap, HashSet},
5    B256,
6};
7
8/// The aggregation of trie updates.
9#[derive(PartialEq, Eq, Clone, Default, Debug)]
10#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
11pub struct TrieUpdates {
12    /// Collection of updated intermediate account nodes indexed by full path.
13    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
14    pub account_nodes: HashMap<Nibbles, BranchNodeCompact>,
15    /// Collection of removed intermediate account nodes indexed by full path.
16    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
17    pub removed_nodes: HashSet<Nibbles>,
18    /// Collection of updated storage tries indexed by the hashed address.
19    pub storage_tries: B256Map<StorageTrieUpdates>,
20}
21
22impl TrieUpdates {
23    /// Returns `true` if the updates are empty.
24    pub fn is_empty(&self) -> bool {
25        self.account_nodes.is_empty() &&
26            self.removed_nodes.is_empty() &&
27            self.storage_tries.is_empty()
28    }
29
30    /// Returns reference to updated account nodes.
31    pub const fn account_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
32        &self.account_nodes
33    }
34
35    /// Returns a reference to removed account nodes.
36    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
37        &self.removed_nodes
38    }
39
40    /// Returns a reference to updated storage tries.
41    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdates> {
42        &self.storage_tries
43    }
44
45    /// Extends the trie updates.
46    pub fn extend(&mut self, other: Self) {
47        self.extend_common(&other);
48        self.account_nodes.extend(exclude_empty_from_pair(other.account_nodes));
49        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
50        for (hashed_address, storage_trie) in other.storage_tries {
51            self.storage_tries.entry(hashed_address).or_default().extend(storage_trie);
52        }
53    }
54
55    /// Extends the trie updates.
56    ///
57    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
58    pub fn extend_ref(&mut self, other: &Self) {
59        self.extend_common(other);
60        self.account_nodes.extend(exclude_empty_from_pair(
61            other.account_nodes.iter().map(|(k, v)| (k.clone(), v.clone())),
62        ));
63        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().cloned()));
64        for (hashed_address, storage_trie) in &other.storage_tries {
65            self.storage_tries.entry(*hashed_address).or_default().extend_ref(storage_trie);
66        }
67    }
68
69    fn extend_common(&mut self, other: &Self) {
70        self.account_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
71    }
72
73    /// Insert storage updates for a given hashed address.
74    pub fn insert_storage_updates(
75        &mut self,
76        hashed_address: B256,
77        storage_updates: StorageTrieUpdates,
78    ) {
79        if storage_updates.is_empty() {
80            return;
81        }
82        let existing = self.storage_tries.insert(hashed_address, storage_updates);
83        debug_assert!(existing.is_none());
84    }
85
86    /// Finalize state trie updates.
87    pub fn finalize(
88        &mut self,
89        hash_builder: HashBuilder,
90        removed_keys: HashSet<Nibbles>,
91        destroyed_accounts: B256Set,
92    ) {
93        // Retrieve updated nodes from hash builder.
94        let (_, updated_nodes) = hash_builder.split();
95        self.account_nodes.extend(exclude_empty_from_pair(updated_nodes));
96
97        // Add deleted node paths.
98        self.removed_nodes.extend(exclude_empty(removed_keys));
99
100        // Add deleted storage tries for destroyed accounts.
101        for destroyed in destroyed_accounts {
102            self.storage_tries.entry(destroyed).or_default().set_deleted(true);
103        }
104    }
105
106    /// Converts trie updates into [`TrieUpdatesSorted`].
107    pub fn into_sorted(self) -> TrieUpdatesSorted {
108        let mut account_nodes = Vec::from_iter(self.account_nodes);
109        account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
110        let storage_tries = self
111            .storage_tries
112            .into_iter()
113            .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted()))
114            .collect();
115        TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries }
116    }
117}
118
119/// Trie updates for storage trie of a single account.
120#[derive(PartialEq, Eq, Clone, Default, Debug)]
121#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))]
122pub struct StorageTrieUpdates {
123    /// Flag indicating whether the trie was deleted.
124    pub is_deleted: bool,
125    /// Collection of updated storage trie nodes.
126    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_map"))]
127    pub storage_nodes: HashMap<Nibbles, BranchNodeCompact>,
128    /// Collection of removed storage trie nodes.
129    #[cfg_attr(any(test, feature = "serde"), serde(with = "serde_nibbles_set"))]
130    pub removed_nodes: HashSet<Nibbles>,
131}
132
133#[cfg(feature = "test-utils")]
134impl StorageTrieUpdates {
135    /// Creates a new storage trie updates that are not marked as deleted.
136    pub fn new(updates: impl IntoIterator<Item = (Nibbles, BranchNodeCompact)>) -> Self {
137        Self { storage_nodes: exclude_empty_from_pair(updates).collect(), ..Default::default() }
138    }
139}
140
141impl StorageTrieUpdates {
142    /// Returns empty storage trie updates with `deleted` set to `true`.
143    pub fn deleted() -> Self {
144        Self {
145            is_deleted: true,
146            storage_nodes: HashMap::default(),
147            removed_nodes: HashSet::default(),
148        }
149    }
150
151    /// Returns the length of updated nodes.
152    pub fn len(&self) -> usize {
153        (self.is_deleted as usize) + self.storage_nodes.len() + self.removed_nodes.len()
154    }
155
156    /// Returns `true` if the trie was deleted.
157    pub const fn is_deleted(&self) -> bool {
158        self.is_deleted
159    }
160
161    /// Returns reference to updated storage nodes.
162    pub const fn storage_nodes_ref(&self) -> &HashMap<Nibbles, BranchNodeCompact> {
163        &self.storage_nodes
164    }
165
166    /// Returns reference to removed storage nodes.
167    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
168        &self.removed_nodes
169    }
170
171    /// Returns `true` if storage updates are empty.
172    pub fn is_empty(&self) -> bool {
173        !self.is_deleted && self.storage_nodes.is_empty() && self.removed_nodes.is_empty()
174    }
175
176    /// Sets `deleted` flag on the storage trie.
177    pub const fn set_deleted(&mut self, deleted: bool) {
178        self.is_deleted = deleted;
179    }
180
181    /// Extends storage trie updates.
182    pub fn extend(&mut self, other: Self) {
183        self.extend_common(&other);
184        self.storage_nodes.extend(exclude_empty_from_pair(other.storage_nodes));
185        self.removed_nodes.extend(exclude_empty(other.removed_nodes));
186    }
187
188    /// Extends storage trie updates.
189    ///
190    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
191    pub fn extend_ref(&mut self, other: &Self) {
192        self.extend_common(other);
193        self.storage_nodes.extend(exclude_empty_from_pair(
194            other.storage_nodes.iter().map(|(k, v)| (k.clone(), v.clone())),
195        ));
196        self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().cloned()));
197    }
198
199    fn extend_common(&mut self, other: &Self) {
200        if other.is_deleted {
201            self.storage_nodes.clear();
202            self.removed_nodes.clear();
203        }
204        self.is_deleted |= other.is_deleted;
205        self.storage_nodes.retain(|nibbles, _| !other.removed_nodes.contains(nibbles));
206    }
207
208    /// Finalize storage trie updates for by taking updates from walker and hash builder.
209    pub fn finalize(&mut self, hash_builder: HashBuilder, removed_keys: HashSet<Nibbles>) {
210        // Retrieve updated nodes from hash builder.
211        let (_, updated_nodes) = hash_builder.split();
212        self.storage_nodes.extend(exclude_empty_from_pair(updated_nodes));
213
214        // Add deleted node paths.
215        self.removed_nodes.extend(exclude_empty(removed_keys));
216    }
217
218    /// Convert storage trie updates into [`StorageTrieUpdatesSorted`].
219    pub fn into_sorted(self) -> StorageTrieUpdatesSorted {
220        let mut storage_nodes = Vec::from_iter(self.storage_nodes);
221        storage_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
222        StorageTrieUpdatesSorted {
223            is_deleted: self.is_deleted,
224            removed_nodes: self.removed_nodes,
225            storage_nodes,
226        }
227    }
228}
229
230/// Serializes and deserializes any [`HashSet`] that includes [`Nibbles`] elements, by using the
231/// hex-encoded packed representation.
232///
233/// This also sorts the set before serializing.
234#[cfg(any(test, feature = "serde"))]
235mod serde_nibbles_set {
236    use crate::Nibbles;
237    use alloc::{
238        string::{String, ToString},
239        vec::Vec,
240    };
241    use alloy_primitives::map::HashSet;
242    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
243
244    pub(super) fn serialize<S>(map: &HashSet<Nibbles>, serializer: S) -> Result<S::Ok, S::Error>
245    where
246        S: Serializer,
247    {
248        let mut storage_nodes =
249            map.iter().map(|elem| alloy_primitives::hex::encode(elem.pack())).collect::<Vec<_>>();
250        storage_nodes.sort_unstable();
251        storage_nodes.serialize(serializer)
252    }
253
254    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<HashSet<Nibbles>, D::Error>
255    where
256        D: Deserializer<'de>,
257    {
258        Vec::<String>::deserialize(deserializer)?
259            .into_iter()
260            .map(|node| {
261                Ok(Nibbles::unpack(
262                    alloy_primitives::hex::decode(node)
263                        .map_err(|err| D::Error::custom(err.to_string()))?,
264                ))
265            })
266            .collect::<Result<HashSet<_>, _>>()
267    }
268}
269
270/// Serializes and deserializes any [`HashMap`] that uses [`Nibbles`] as keys, by using the
271/// hex-encoded packed representation.
272///
273/// This also sorts the map's keys before encoding and serializing.
274#[cfg(any(test, feature = "serde"))]
275mod serde_nibbles_map {
276    use crate::Nibbles;
277    use alloc::{
278        string::{String, ToString},
279        vec::Vec,
280    };
281    use alloy_primitives::{hex, map::HashMap};
282    use core::marker::PhantomData;
283    use serde::{
284        de::{Error, MapAccess, Visitor},
285        ser::SerializeMap,
286        Deserialize, Deserializer, Serialize, Serializer,
287    };
288
289    pub(super) fn serialize<S, T>(
290        map: &HashMap<Nibbles, T>,
291        serializer: S,
292    ) -> Result<S::Ok, S::Error>
293    where
294        S: Serializer,
295        T: Serialize,
296    {
297        let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
298        let mut storage_nodes = Vec::from_iter(map);
299        storage_nodes.sort_unstable_by_key(|node| node.0);
300        for (k, v) in storage_nodes {
301            // pack, then hex encode the Nibbles
302            let packed = alloy_primitives::hex::encode(k.pack());
303            map_serializer.serialize_entry(&packed, &v)?;
304        }
305        map_serializer.end()
306    }
307
308    pub(super) fn deserialize<'de, D, T>(deserializer: D) -> Result<HashMap<Nibbles, T>, D::Error>
309    where
310        D: Deserializer<'de>,
311        T: Deserialize<'de>,
312    {
313        struct NibblesMapVisitor<T> {
314            marker: PhantomData<T>,
315        }
316
317        impl<'de, T> Visitor<'de> for NibblesMapVisitor<T>
318        where
319            T: Deserialize<'de>,
320        {
321            type Value = HashMap<Nibbles, T>;
322
323            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324                formatter.write_str("a map with hex-encoded Nibbles keys")
325            }
326
327            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
328            where
329                A: MapAccess<'de>,
330            {
331                let mut result = HashMap::with_capacity_and_hasher(
332                    map.size_hint().unwrap_or(0),
333                    Default::default(),
334                );
335
336                while let Some((key, value)) = map.next_entry::<String, T>()? {
337                    let decoded_key =
338                        hex::decode(&key).map_err(|err| Error::custom(err.to_string()))?;
339
340                    let nibbles = Nibbles::unpack(&decoded_key);
341
342                    result.insert(nibbles, value);
343                }
344
345                Ok(result)
346            }
347        }
348
349        deserializer.deserialize_map(NibblesMapVisitor { marker: PhantomData })
350    }
351}
352
353/// Sorted trie updates used for lookups and insertions.
354#[derive(PartialEq, Eq, Clone, Default, Debug)]
355pub struct TrieUpdatesSorted {
356    /// Sorted collection of updated state nodes with corresponding paths.
357    pub account_nodes: Vec<(Nibbles, BranchNodeCompact)>,
358    /// The set of removed state node keys.
359    pub removed_nodes: HashSet<Nibbles>,
360    /// Storage tries stored by hashed address of the account the trie belongs to.
361    pub storage_tries: B256Map<StorageTrieUpdatesSorted>,
362}
363
364impl TrieUpdatesSorted {
365    /// Returns reference to updated account nodes.
366    pub fn account_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] {
367        &self.account_nodes
368    }
369
370    /// Returns reference to removed account nodes.
371    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
372        &self.removed_nodes
373    }
374
375    /// Returns reference to updated storage tries.
376    pub const fn storage_tries_ref(&self) -> &B256Map<StorageTrieUpdatesSorted> {
377        &self.storage_tries
378    }
379}
380
381/// Sorted trie updates used for lookups and insertions.
382#[derive(PartialEq, Eq, Clone, Default, Debug)]
383pub struct StorageTrieUpdatesSorted {
384    /// Flag indicating whether the trie has been deleted/wiped.
385    pub is_deleted: bool,
386    /// Sorted collection of updated storage nodes with corresponding paths.
387    pub storage_nodes: Vec<(Nibbles, BranchNodeCompact)>,
388    /// The set of removed storage node keys.
389    pub removed_nodes: HashSet<Nibbles>,
390}
391
392impl StorageTrieUpdatesSorted {
393    /// Returns `true` if the trie was deleted.
394    pub const fn is_deleted(&self) -> bool {
395        self.is_deleted
396    }
397
398    /// Returns reference to updated storage nodes.
399    pub fn storage_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] {
400        &self.storage_nodes
401    }
402
403    /// Returns reference to removed storage nodes.
404    pub const fn removed_nodes_ref(&self) -> &HashSet<Nibbles> {
405        &self.removed_nodes
406    }
407}
408
409/// Excludes empty nibbles from the given iterator.
410fn exclude_empty(iter: impl IntoIterator<Item = Nibbles>) -> impl Iterator<Item = Nibbles> {
411    iter.into_iter().filter(|n| !n.is_empty())
412}
413
414/// Excludes empty nibbles from the given iterator of pairs where the nibbles are the key.
415fn exclude_empty_from_pair<V>(
416    iter: impl IntoIterator<Item = (Nibbles, V)>,
417) -> impl Iterator<Item = (Nibbles, V)> {
418    iter.into_iter().filter(|(n, _)| !n.is_empty())
419}
420
421/// Bincode-compatible trie updates type serde implementations.
422#[cfg(feature = "serde-bincode-compat")]
423pub mod serde_bincode_compat {
424    use crate::{BranchNodeCompact, Nibbles};
425    use alloc::borrow::Cow;
426    use alloy_primitives::map::{B256Map, HashMap, HashSet};
427    use serde::{Deserialize, Deserializer, Serialize, Serializer};
428    use serde_with::{DeserializeAs, SerializeAs};
429
430    /// Bincode-compatible [`super::TrieUpdates`] serde implementation.
431    ///
432    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
433    /// ```rust
434    /// use reth_trie_common::{serde_bincode_compat, updates::TrieUpdates};
435    /// use serde::{Deserialize, Serialize};
436    /// use serde_with::serde_as;
437    ///
438    /// #[serde_as]
439    /// #[derive(Serialize, Deserialize)]
440    /// struct Data {
441    ///     #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
442    ///     trie_updates: TrieUpdates,
443    /// }
444    /// ```
445    #[derive(Debug, Serialize, Deserialize)]
446    pub struct TrieUpdates<'a> {
447        account_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
448        removed_nodes: Cow<'a, HashSet<Nibbles>>,
449        storage_tries: B256Map<StorageTrieUpdates<'a>>,
450    }
451
452    impl<'a> From<&'a super::TrieUpdates> for TrieUpdates<'a> {
453        fn from(value: &'a super::TrieUpdates) -> Self {
454            Self {
455                account_nodes: Cow::Borrowed(&value.account_nodes),
456                removed_nodes: Cow::Borrowed(&value.removed_nodes),
457                storage_tries: value.storage_tries.iter().map(|(k, v)| (*k, v.into())).collect(),
458            }
459        }
460    }
461
462    impl<'a> From<TrieUpdates<'a>> for super::TrieUpdates {
463        fn from(value: TrieUpdates<'a>) -> Self {
464            Self {
465                account_nodes: value.account_nodes.into_owned(),
466                removed_nodes: value.removed_nodes.into_owned(),
467                storage_tries: value
468                    .storage_tries
469                    .into_iter()
470                    .map(|(k, v)| (k, v.into()))
471                    .collect(),
472            }
473        }
474    }
475
476    impl SerializeAs<super::TrieUpdates> for TrieUpdates<'_> {
477        fn serialize_as<S>(source: &super::TrieUpdates, serializer: S) -> Result<S::Ok, S::Error>
478        where
479            S: Serializer,
480        {
481            TrieUpdates::from(source).serialize(serializer)
482        }
483    }
484
485    impl<'de> DeserializeAs<'de, super::TrieUpdates> for TrieUpdates<'de> {
486        fn deserialize_as<D>(deserializer: D) -> Result<super::TrieUpdates, D::Error>
487        where
488            D: Deserializer<'de>,
489        {
490            TrieUpdates::deserialize(deserializer).map(Into::into)
491        }
492    }
493
494    /// Bincode-compatible [`super::StorageTrieUpdates`] serde implementation.
495    ///
496    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
497    /// ```rust
498    /// use reth_trie_common::{serde_bincode_compat, updates::StorageTrieUpdates};
499    /// use serde::{Deserialize, Serialize};
500    /// use serde_with::serde_as;
501    ///
502    /// #[serde_as]
503    /// #[derive(Serialize, Deserialize)]
504    /// struct Data {
505    ///     #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
506    ///     trie_updates: StorageTrieUpdates,
507    /// }
508    /// ```
509    #[derive(Debug, Serialize, Deserialize)]
510    pub struct StorageTrieUpdates<'a> {
511        is_deleted: bool,
512        storage_nodes: Cow<'a, HashMap<Nibbles, BranchNodeCompact>>,
513        removed_nodes: Cow<'a, HashSet<Nibbles>>,
514    }
515
516    impl<'a> From<&'a super::StorageTrieUpdates> for StorageTrieUpdates<'a> {
517        fn from(value: &'a super::StorageTrieUpdates) -> Self {
518            Self {
519                is_deleted: value.is_deleted,
520                storage_nodes: Cow::Borrowed(&value.storage_nodes),
521                removed_nodes: Cow::Borrowed(&value.removed_nodes),
522            }
523        }
524    }
525
526    impl<'a> From<StorageTrieUpdates<'a>> for super::StorageTrieUpdates {
527        fn from(value: StorageTrieUpdates<'a>) -> Self {
528            Self {
529                is_deleted: value.is_deleted,
530                storage_nodes: value.storage_nodes.into_owned(),
531                removed_nodes: value.removed_nodes.into_owned(),
532            }
533        }
534    }
535
536    impl SerializeAs<super::StorageTrieUpdates> for StorageTrieUpdates<'_> {
537        fn serialize_as<S>(
538            source: &super::StorageTrieUpdates,
539            serializer: S,
540        ) -> Result<S::Ok, S::Error>
541        where
542            S: Serializer,
543        {
544            StorageTrieUpdates::from(source).serialize(serializer)
545        }
546    }
547
548    impl<'de> DeserializeAs<'de, super::StorageTrieUpdates> for StorageTrieUpdates<'de> {
549        fn deserialize_as<D>(deserializer: D) -> Result<super::StorageTrieUpdates, D::Error>
550        where
551            D: Deserializer<'de>,
552        {
553            StorageTrieUpdates::deserialize(deserializer).map(Into::into)
554        }
555    }
556
557    #[cfg(test)]
558    mod tests {
559        use crate::{
560            serde_bincode_compat,
561            updates::{StorageTrieUpdates, TrieUpdates},
562            BranchNodeCompact, Nibbles,
563        };
564        use alloy_primitives::B256;
565        use serde::{Deserialize, Serialize};
566        use serde_with::serde_as;
567
568        #[test]
569        fn test_trie_updates_bincode_roundtrip() {
570            #[serde_as]
571            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
572            struct Data {
573                #[serde_as(as = "serde_bincode_compat::updates::TrieUpdates")]
574                trie_updates: TrieUpdates,
575            }
576
577            let mut data = Data { trie_updates: TrieUpdates::default() };
578            let encoded = bincode::serialize(&data).unwrap();
579            let decoded: Data = bincode::deserialize(&encoded).unwrap();
580            assert_eq!(decoded, data);
581
582            data.trie_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f]));
583            let encoded = bincode::serialize(&data).unwrap();
584            let decoded: Data = bincode::deserialize(&encoded).unwrap();
585            assert_eq!(decoded, data);
586
587            data.trie_updates.account_nodes.insert(
588                Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]),
589                BranchNodeCompact::default(),
590            );
591            let encoded = bincode::serialize(&data).unwrap();
592            let decoded: Data = bincode::deserialize(&encoded).unwrap();
593            assert_eq!(decoded, data);
594
595            data.trie_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
596            let encoded = bincode::serialize(&data).unwrap();
597            let decoded: Data = bincode::deserialize(&encoded).unwrap();
598            assert_eq!(decoded, data);
599        }
600
601        #[test]
602        fn test_storage_trie_updates_bincode_roundtrip() {
603            #[serde_as]
604            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
605            struct Data {
606                #[serde_as(as = "serde_bincode_compat::updates::StorageTrieUpdates")]
607                trie_updates: StorageTrieUpdates,
608            }
609
610            let mut data = Data { trie_updates: StorageTrieUpdates::default() };
611            let encoded = bincode::serialize(&data).unwrap();
612            let decoded: Data = bincode::deserialize(&encoded).unwrap();
613            assert_eq!(decoded, data);
614
615            data.trie_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f]));
616            let encoded = bincode::serialize(&data).unwrap();
617            let decoded: Data = bincode::deserialize(&encoded).unwrap();
618            assert_eq!(decoded, data);
619
620            data.trie_updates.storage_nodes.insert(
621                Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]),
622                BranchNodeCompact::default(),
623            );
624            let encoded = bincode::serialize(&data).unwrap();
625            let decoded: Data = bincode::deserialize(&encoded).unwrap();
626            assert_eq!(decoded, data);
627        }
628    }
629}
630
631#[cfg(all(test, feature = "serde"))]
632mod tests {
633    use super::*;
634
635    #[test]
636    fn test_trie_updates_serde_roundtrip() {
637        let mut default_updates = TrieUpdates::default();
638        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
639        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
640        assert_eq!(updates_deserialized, default_updates);
641
642        default_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f]));
643        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
644        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
645        assert_eq!(updates_deserialized, default_updates);
646
647        default_updates
648            .account_nodes
649            .insert(Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default());
650        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
651        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
652        assert_eq!(updates_deserialized, default_updates);
653
654        default_updates.storage_tries.insert(B256::default(), StorageTrieUpdates::default());
655        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
656        let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap();
657        assert_eq!(updates_deserialized, default_updates);
658    }
659
660    #[test]
661    fn test_storage_trie_updates_serde_roundtrip() {
662        let mut default_updates = StorageTrieUpdates::default();
663        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
664        let updates_deserialized: StorageTrieUpdates =
665            serde_json::from_str(&updates_serialized).unwrap();
666        assert_eq!(updates_deserialized, default_updates);
667
668        default_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f]));
669        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
670        let updates_deserialized: StorageTrieUpdates =
671            serde_json::from_str(&updates_serialized).unwrap();
672        assert_eq!(updates_deserialized, default_updates);
673
674        default_updates
675            .storage_nodes
676            .insert(Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default());
677        let updates_serialized = serde_json::to_string(&default_updates).unwrap();
678        let updates_deserialized: StorageTrieUpdates =
679            serde_json::from_str(&updates_serialized).unwrap();
680        assert_eq!(updates_deserialized, default_updates);
681    }
682}