reth_trie_common/
updates.rs

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