hashmark/
diff.rs

1use crate::merkle::{Merkle, Node};
2use crate::path::{FieldPath, PathSegment};
3
4/// Result of comparing two Merkle trees.
5#[derive(Debug, Clone, PartialEq)]
6pub enum Diff {
7    /// The two trees are identical.
8    Same,
9    /// The two trees differ.
10    Different(DiffDetail),
11}
12
13/// Details about how two Merkle trees differ.
14#[derive(Debug, Clone, PartialEq)]
15pub enum DiffDetail {
16    /// A leaf value changed.
17    Value,
18
19    /// Struct fields changed.
20    Struct {
21        /// Name of the struct
22        name: String,
23        /// Changed fields with their names and diffs
24        changed_fields: Vec<(String, Diff)>,
25    },
26
27    /// Sequence elements changed.
28    Seq {
29        /// Indices of added elements
30        added: Vec<usize>,
31        /// Indices of removed elements
32        removed: Vec<usize>,
33        /// Modified elements with their diffs
34        modified: Vec<(usize, Diff)>,
35    },
36
37    /// Enum variant changed.
38    Enum {
39        /// Name of the enum
40        name: String,
41        /// Old variant name
42        old_variant: String,
43        /// New variant name
44        new_variant: String,
45        /// If same variant, the inner diff
46        inner: Option<Box<Diff>>,
47    },
48
49    /// Map entries changed.
50    Map {
51        /// Keys of added entries
52        added_keys: Vec<String>,
53        /// Keys of removed entries
54        removed_keys: Vec<String>,
55        /// Modified entries with their diffs
56        modified: Vec<(String, Diff)>,
57    },
58
59    /// Node types don't match.
60    TypeMismatch,
61}
62
63impl DiffDetail {
64    /// Get all changed paths with their change type.
65    pub fn changed_paths(&self) -> Vec<(FieldPath, ChangeType)> {
66        let mut paths = Vec::new();
67        self.collect_paths(FieldPath::new(), &mut paths);
68        paths
69    }
70
71    fn collect_paths(&self, current: FieldPath, paths: &mut Vec<(FieldPath, ChangeType)>) {
72        match self {
73            DiffDetail::Value => {
74                paths.push((current, ChangeType::Modified));
75            }
76            DiffDetail::Struct { changed_fields, .. } => {
77                for (field_name, diff) in changed_fields {
78                    let mut path = current.clone();
79                    path.push(PathSegment::Field(field_name.clone()));
80                    if let Diff::Different(detail) = diff {
81                        detail.collect_paths(path, paths);
82                    }
83                }
84            }
85            DiffDetail::Seq {
86                added,
87                removed,
88                modified,
89            } => {
90                for &idx in added {
91                    let mut path = current.clone();
92                    path.push(PathSegment::Index(idx));
93                    paths.push((path, ChangeType::Added));
94                }
95                for &idx in removed {
96                    let mut path = current.clone();
97                    path.push(PathSegment::Index(idx));
98                    paths.push((path, ChangeType::Removed));
99                }
100                for (idx, diff) in modified {
101                    let mut path = current.clone();
102                    path.push(PathSegment::Index(*idx));
103                    if let Diff::Different(detail) = diff {
104                        detail.collect_paths(path, paths);
105                    }
106                }
107            }
108            DiffDetail::Enum {
109                old_variant,
110                new_variant,
111                inner,
112                ..
113            } => {
114                if old_variant != new_variant {
115                    paths.push((current.clone(), ChangeType::VariantChanged));
116                }
117                if let Some(inner_diff) = inner {
118                    if let Diff::Different(detail) = inner_diff.as_ref() {
119                        let mut path = current;
120                        path.push(PathSegment::Variant(new_variant.clone()));
121                        detail.collect_paths(path, paths);
122                    }
123                }
124            }
125            DiffDetail::Map {
126                added_keys,
127                removed_keys,
128                modified,
129            } => {
130                for key in added_keys {
131                    let mut path = current.clone();
132                    path.push(PathSegment::MapKey(key.clone()));
133                    paths.push((path, ChangeType::Added));
134                }
135                for key in removed_keys {
136                    let mut path = current.clone();
137                    path.push(PathSegment::MapKey(key.clone()));
138                    paths.push((path, ChangeType::Removed));
139                }
140                for (key, diff) in modified {
141                    let mut path = current.clone();
142                    path.push(PathSegment::MapKey(key.clone()));
143                    if let Diff::Different(detail) = diff {
144                        detail.collect_paths(path, paths);
145                    }
146                }
147            }
148            DiffDetail::TypeMismatch => {
149                paths.push((current, ChangeType::TypeChanged));
150            }
151        }
152    }
153}
154
155/// Type of change detected.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum ChangeType {
158    Modified,
159    Added,
160    Removed,
161    VariantChanged,
162    TypeChanged,
163}
164
165/// Compare two Merkle trees and return their differences.
166pub fn merkle_diff(old: &Merkle, new: &Merkle) -> Diff {
167    // Fast path: identical digests
168    if old.digest == new.digest {
169        return Diff::Same;
170    }
171
172    match (&old.node, &new.node) {
173        // Leaf nodes
174        (Node::Leaf(_), Node::Leaf(_)) => Diff::Different(DiffDetail::Value),
175        (Node::Unit, Node::Unit) => Diff::Different(DiffDetail::Value),
176        (Node::None, Node::None) => Diff::Different(DiffDetail::Value),
177
178        // Struct nodes
179        (Node::Struct(old_s), Node::Struct(new_s)) => {
180            let changed_fields: Vec<_> = old_s
181                .fields
182                .iter()
183                .zip(&new_s.fields)
184                .filter_map(|((old_name, old_m), (new_name, new_m))| {
185                    debug_assert_eq!(old_name, new_name);
186                    let diff = merkle_diff(old_m, new_m);
187                    if diff != Diff::Same {
188                        Some((old_name.clone(), diff))
189                    } else {
190                        None
191                    }
192                })
193                .collect();
194
195            Diff::Different(DiffDetail::Struct {
196                name: old_s.name.clone(),
197                changed_fields,
198            })
199        }
200
201        // Sequence nodes
202        (Node::Seq(old_s), Node::Seq(new_s)) => {
203            let old_len = old_s.elements.len();
204            let new_len = new_s.elements.len();
205            let common_len = old_len.min(new_len);
206
207            let modified: Vec<_> = old_s.elements[..common_len]
208                .iter()
209                .zip(&new_s.elements[..common_len])
210                .enumerate()
211                .filter_map(|(i, (o, n))| {
212                    let diff = merkle_diff(o, n);
213                    if diff != Diff::Same {
214                        Some((i, diff))
215                    } else {
216                        None
217                    }
218                })
219                .collect();
220
221            let added: Vec<_> = (old_len..new_len).collect();
222            let removed: Vec<_> = (new_len..old_len).collect();
223
224            Diff::Different(DiffDetail::Seq {
225                added,
226                removed,
227                modified,
228            })
229        }
230
231        // Enum nodes
232        (Node::Enum(old_e), Node::Enum(new_e)) => {
233            if old_e.variant != new_e.variant {
234                Diff::Different(DiffDetail::Enum {
235                    name: old_e.name.clone(),
236                    old_variant: old_e.variant.clone(),
237                    new_variant: new_e.variant.clone(),
238                    inner: None,
239                })
240            } else {
241                let inner = match (&old_e.data, &new_e.data) {
242                    (Some(o), Some(n)) => {
243                        let diff = merkle_diff(o, n);
244                        if diff != Diff::Same {
245                            Some(Box::new(diff))
246                        } else {
247                            None
248                        }
249                    }
250                    _ => None,
251                };
252
253                if inner.is_some() {
254                    Diff::Different(DiffDetail::Enum {
255                        name: old_e.name.clone(),
256                        old_variant: old_e.variant.clone(),
257                        new_variant: new_e.variant.clone(),
258                        inner,
259                    })
260                } else {
261                    Diff::Different(DiffDetail::Value)
262                }
263            }
264        }
265
266        // Map nodes
267        (Node::Map(old_m), Node::Map(new_m)) => {
268            let old_keys: std::collections::HashSet<_> =
269                old_m.entries.iter().map(|(k, _)| k).collect();
270            let new_keys: std::collections::HashSet<_> =
271                new_m.entries.iter().map(|(k, _)| k).collect();
272
273            let added_keys: Vec<_> = new_keys
274                .difference(&old_keys)
275                .map(|k| (*k).clone())
276                .collect();
277            let removed_keys: Vec<_> = old_keys
278                .difference(&new_keys)
279                .map(|k| (*k).clone())
280                .collect();
281
282            let old_map: std::collections::HashMap<_, _> =
283                old_m.entries.iter().map(|(k, v)| (k, v)).collect();
284            let new_map: std::collections::HashMap<_, _> =
285                new_m.entries.iter().map(|(k, v)| (k, v)).collect();
286
287            let modified: Vec<_> = old_keys
288                .intersection(&new_keys)
289                .filter_map(|k| {
290                    let old_v = old_map.get(*k).unwrap();
291                    let new_v = new_map.get(*k).unwrap();
292                    let diff = merkle_diff(old_v, new_v);
293                    if diff != Diff::Same {
294                        Some(((*k).clone(), diff))
295                    } else {
296                        None
297                    }
298                })
299                .collect();
300
301            Diff::Different(DiffDetail::Map {
302                added_keys,
303                removed_keys,
304                modified,
305            })
306        }
307
308        // Type mismatch
309        _ => Diff::Different(DiffDetail::TypeMismatch),
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use crate::serializer::tree_hash;
317    use serde::Serialize;
318
319    #[test]
320    fn test_struct_diff_with_field_names() {
321        #[derive(Serialize)]
322        struct User {
323            name: String,
324            age: u32,
325            active: bool,
326        }
327
328        let u1 = User {
329            name: "Alice".into(),
330            age: 30,
331            active: true,
332        };
333        let u2 = User {
334            name: "Alice".into(),
335            age: 31,
336            active: true,
337        };
338
339        let m1 = tree_hash(&u1).unwrap();
340        let m2 = tree_hash(&u2).unwrap();
341
342        let diff = merkle_diff(&m1, &m2);
343
344        if let Diff::Different(DiffDetail::Struct { changed_fields, .. }) = diff {
345            assert_eq!(changed_fields.len(), 1);
346            assert_eq!(changed_fields[0].0, "age");
347        } else {
348            panic!("Expected Struct diff");
349        }
350    }
351
352    #[test]
353    fn test_nested_struct_paths() {
354        #[derive(Serialize)]
355        struct Address {
356            city: String,
357            zip: String,
358        }
359
360        #[derive(Serialize)]
361        struct User {
362            name: String,
363            address: Address,
364        }
365
366        let u1 = User {
367            name: "Alice".into(),
368            address: Address {
369                city: "NYC".into(),
370                zip: "10001".into(),
371            },
372        };
373        let u2 = User {
374            name: "Alice".into(),
375            address: Address {
376                city: "LA".into(),
377                zip: "10001".into(),
378            },
379        };
380
381        let m1 = tree_hash(&u1).unwrap();
382        let m2 = tree_hash(&u2).unwrap();
383
384        let diff = merkle_diff(&m1, &m2);
385
386        if let Diff::Different(detail) = diff {
387            let paths = detail.changed_paths();
388            assert_eq!(paths.len(), 1);
389            assert_eq!(paths[0].0.to_string(), "address.city");
390        } else {
391            panic!("Expected Different");
392        }
393    }
394
395    #[test]
396    fn test_vec_diff_paths() {
397        let v1: Vec<i32> = vec![1, 2, 3];
398        let v2: Vec<i32> = vec![1, 5, 3, 4];
399
400        let m1 = tree_hash(&v1).unwrap();
401        let m2 = tree_hash(&v2).unwrap();
402
403        let diff = merkle_diff(&m1, &m2);
404
405        if let Diff::Different(detail) = diff {
406            let paths = detail.changed_paths();
407            // [1] modified, [3] added
408            assert!(paths.iter().any(|(p, _)| p.to_string() == "[1]"));
409            assert!(paths
410                .iter()
411                .any(|(p, c)| p.to_string() == "[3]" && *c == ChangeType::Added));
412        } else {
413            panic!("Expected Different");
414        }
415    }
416
417    #[test]
418    fn test_enum_variant_change() {
419        #[derive(Serialize)]
420        enum Status {
421            Active,
422            Suspended { reason: String },
423        }
424
425        let s1 = Status::Active;
426        let s2 = Status::Suspended {
427            reason: "test".into(),
428        };
429
430        let m1 = tree_hash(&s1).unwrap();
431        let m2 = tree_hash(&s2).unwrap();
432
433        let diff = merkle_diff(&m1, &m2);
434
435        if let Diff::Different(DiffDetail::Enum {
436            old_variant,
437            new_variant,
438            ..
439        }) = diff
440        {
441            assert_eq!(old_variant, "Active");
442            assert_eq!(new_variant, "Suspended");
443        } else {
444            panic!("Expected Enum diff");
445        }
446    }
447}