Skip to main content

calimero_node_primitives/sync/
subtree.rs

1//! SubtreePrefetch sync types (CIP Appendix B - Protocol Selection Matrix).
2//!
3//! Types for subtree prefetch-based synchronization, optimized for deep trees
4//! with clustered changes.
5//!
6//! # When to Use
7//!
8//! - `max_depth > 3` (deep trees)
9//! - `divergence < 20%`
10//! - Changes are clustered in subtrees
11//!
12//! # Trade-offs
13//!
14//! | Aspect        | HashComparison     | SubtreePrefetch      |
15//! |---------------|--------------------|-----------------------|
16//! | Round trips   | O(depth)           | O(1) per subtree      |
17//! | Data transfer | Minimal            | May over-fetch        |
18//! | Best for      | Scattered changes  | Clustered changes     |
19//!
20//! # Validation
21//!
22//! All types have `is_valid()` methods that should be called after deserializing
23//! from untrusted sources to prevent resource exhaustion attacks.
24
25use borsh::{BorshDeserialize, BorshSerialize};
26
27use super::hash_comparison::TreeLeafData;
28
29// =============================================================================
30// Constants
31// =============================================================================
32
33/// Default maximum depth for subtree prefetch.
34///
35/// Limits how deep into a subtree we fetch to avoid over-fetching.
36pub const DEFAULT_SUBTREE_MAX_DEPTH: usize = 5;
37
38/// Maximum allowed depth for subtree traversal requests.
39///
40/// This limit prevents resource exhaustion from malicious peers requesting
41/// extremely deep traversals. Aligned with `hash_comparison::MAX_TREE_DEPTH`.
42pub const MAX_SUBTREE_DEPTH: usize = 64;
43
44/// Maximum number of subtree roots in a single request.
45///
46/// Limits the size of `SubtreePrefetchRequest::subtree_roots` to prevent
47/// DoS attacks from malicious peers sending oversized requests.
48pub const MAX_SUBTREES_PER_REQUEST: usize = 100;
49
50/// Maximum number of entities per subtree.
51///
52/// Limits the size of `SubtreeData::entities` to prevent memory exhaustion
53/// from malicious peers sending oversized subtree responses.
54pub const MAX_ENTITIES_PER_SUBTREE: usize = 10_000;
55
56/// Maximum total entities across all subtrees in a response.
57///
58/// Even if each subtree is within its individual limit, the total could still
59/// cause memory exhaustion. This bounds the entire response.
60/// `MAX_SUBTREES_PER_REQUEST * MAX_ENTITIES_PER_SUBTREE` would be 1M entities,
61/// so we set a more reasonable limit.
62pub const MAX_TOTAL_ENTITIES: usize = 100_000;
63
64// =============================================================================
65// Heuristic Thresholds
66// =============================================================================
67
68/// Minimum tree depth to consider SubtreePrefetch over HashComparison.
69///
70/// Trees shallower than this are better served by HashComparison.
71pub const DEEP_TREE_THRESHOLD: usize = 3;
72
73/// Maximum divergence ratio for SubtreePrefetch to be efficient.
74///
75/// Above this ratio, other strategies (full sync) may be better.
76pub const MAX_DIVERGENCE_RATIO: f64 = 0.20;
77
78/// Maximum number of differing subtrees for changes to be "clustered".
79///
80/// If changes span more subtrees than this, HashComparison is likely better.
81pub const MAX_CLUSTERED_SUBTREES: usize = 5;
82
83// =============================================================================
84// SubtreePrefetch Request/Response
85// =============================================================================
86
87/// Request for subtree prefetch-based sync.
88///
89/// Fetches entire subtrees when divergence is detected in deep trees.
90/// More efficient than HashComparison when changes are clustered.
91///
92/// Use when:
93/// - tree depth > `DEEP_TREE_THRESHOLD` (3)
94/// - divergence < `MAX_DIVERGENCE_RATIO` (20%)
95/// - Changes are clustered in <= `MAX_CLUSTERED_SUBTREES` (5) subtrees
96#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
97pub struct SubtreePrefetchRequest {
98    /// Root hashes of subtrees to fetch.
99    ///
100    /// Limited to MAX_SUBTREES_PER_REQUEST entries. Use `is_valid()` to check
101    /// bounds after deserialization from untrusted sources.
102    pub subtree_roots: Vec<[u8; 32]>,
103
104    /// Maximum depth to traverse within each subtree (None = use MAX_SUBTREE_DEPTH).
105    ///
106    /// This field is private to enforce validation. Use:
107    /// - `depth()` accessor to get the validated, bounded depth
108    /// - `with_depth()` constructor to set a custom depth limit
109    /// - `unlimited_depth()` constructor to use MAX_SUBTREE_DEPTH
110    max_depth: Option<usize>,
111}
112
113impl SubtreePrefetchRequest {
114    /// Create a new subtree prefetch request.
115    #[must_use]
116    pub fn new(subtree_roots: Vec<[u8; 32]>) -> Self {
117        Self {
118            subtree_roots,
119            max_depth: Some(DEFAULT_SUBTREE_MAX_DEPTH),
120        }
121    }
122
123    /// Create a request with custom depth limit.
124    ///
125    /// Depth is clamped to MAX_SUBTREE_DEPTH to prevent resource exhaustion.
126    #[must_use]
127    pub fn with_depth(subtree_roots: Vec<[u8; 32]>, max_depth: usize) -> Self {
128        Self {
129            subtree_roots,
130            // Clamp to MAX_SUBTREE_DEPTH to prevent resource exhaustion
131            max_depth: Some(max_depth.min(MAX_SUBTREE_DEPTH)),
132        }
133    }
134
135    /// Create a request with maximum allowed depth (MAX_SUBTREE_DEPTH).
136    ///
137    /// Use this when you want to fetch as deep as safely possible.
138    /// The `depth()` accessor will return `MAX_SUBTREE_DEPTH`.
139    #[must_use]
140    pub fn unlimited_depth(subtree_roots: Vec<[u8; 32]>) -> Self {
141        Self {
142            subtree_roots,
143            max_depth: None,
144        }
145    }
146
147    /// Get the validated depth limit.
148    ///
149    /// Always returns a bounded value:
150    /// - Clamps to MAX_SUBTREE_DEPTH if the raw value exceeds it
151    /// - Returns MAX_SUBTREE_DEPTH if `max_depth` is `None`
152    ///
153    /// This ensures consumers always get a safe, bounded depth value
154    /// regardless of how the request was constructed or deserialized.
155    #[must_use]
156    pub fn depth(&self) -> usize {
157        self.max_depth
158            .map(|d| d.min(MAX_SUBTREE_DEPTH))
159            .unwrap_or(MAX_SUBTREE_DEPTH)
160    }
161
162    /// Check if this request was created with unlimited depth.
163    ///
164    /// Returns `true` if created via `unlimited_depth()` constructor.
165    /// Note: `depth()` will still return `MAX_SUBTREE_DEPTH` for safety.
166    #[must_use]
167    pub fn is_unlimited(&self) -> bool {
168        self.max_depth.is_none()
169    }
170
171    /// Number of subtrees requested.
172    #[must_use]
173    pub fn subtree_count(&self) -> usize {
174        self.subtree_roots.len()
175    }
176
177    /// Check if this is an empty request (no subtrees).
178    #[must_use]
179    pub fn is_empty(&self) -> bool {
180        self.subtree_roots.is_empty()
181    }
182
183    /// Check if request is within valid bounds.
184    ///
185    /// Call this after deserializing from untrusted sources to prevent
186    /// resource exhaustion attacks. Validates:
187    /// - `subtree_roots` count <= MAX_SUBTREES_PER_REQUEST
188    /// - `max_depth` (if Some) <= MAX_SUBTREE_DEPTH
189    #[must_use]
190    pub fn is_valid(&self) -> bool {
191        // Check subtree roots count
192        if self.subtree_roots.len() > MAX_SUBTREES_PER_REQUEST {
193            return false;
194        }
195
196        // Check max_depth bounds (None is valid, it means use MAX_SUBTREE_DEPTH)
197        if let Some(depth) = self.max_depth {
198            if depth > MAX_SUBTREE_DEPTH {
199                return false;
200            }
201        }
202
203        true
204    }
205}
206
207/// Response containing prefetched subtrees.
208#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
209pub struct SubtreePrefetchResponse {
210    /// Fetched subtrees.
211    ///
212    /// Limited to MAX_SUBTREES_PER_REQUEST entries. Use `is_valid()` to check
213    /// bounds after deserialization from untrusted sources.
214    pub subtrees: Vec<SubtreeData>,
215
216    /// Subtree roots that were not found.
217    ///
218    /// Limited to MAX_SUBTREES_PER_REQUEST entries. Use `is_valid()` to check
219    /// bounds after deserialization from untrusted sources.
220    pub not_found: Vec<[u8; 32]>,
221}
222
223impl SubtreePrefetchResponse {
224    /// Create a new response.
225    #[must_use]
226    pub fn new(subtrees: Vec<SubtreeData>, not_found: Vec<[u8; 32]>) -> Self {
227        Self {
228            subtrees,
229            not_found,
230        }
231    }
232
233    /// Create a response with no missing subtrees.
234    #[must_use]
235    pub fn complete(subtrees: Vec<SubtreeData>) -> Self {
236        Self {
237            subtrees,
238            not_found: vec![],
239        }
240    }
241
242    /// Create an empty/not-found response.
243    #[must_use]
244    pub fn not_found(roots: Vec<[u8; 32]>) -> Self {
245        Self {
246            subtrees: vec![],
247            not_found: roots,
248        }
249    }
250
251    /// Check if all requested subtrees were found.
252    #[must_use]
253    pub fn is_complete(&self) -> bool {
254        self.not_found.is_empty()
255    }
256
257    /// Check if response is empty (no subtrees and none not found).
258    #[must_use]
259    pub fn is_empty(&self) -> bool {
260        self.subtrees.is_empty() && self.not_found.is_empty()
261    }
262
263    /// Total number of entities across all subtrees.
264    ///
265    /// Uses saturating arithmetic to prevent overflow from malicious input.
266    #[must_use]
267    pub fn total_entity_count(&self) -> usize {
268        self.subtrees
269            .iter()
270            .fold(0usize, |acc, s| acc.saturating_add(s.entity_count()))
271    }
272
273    /// Number of subtrees returned.
274    #[must_use]
275    pub fn subtree_count(&self) -> usize {
276        self.subtrees.len()
277    }
278
279    /// Check if response is within valid bounds.
280    ///
281    /// Call this after deserializing from untrusted sources to prevent
282    /// resource exhaustion attacks. Validates both response size, total
283    /// entity count, and all contained subtrees.
284    #[must_use]
285    pub fn is_valid(&self) -> bool {
286        // Check subtree count limits
287        if self.subtrees.len() > MAX_SUBTREES_PER_REQUEST {
288            return false;
289        }
290        if self.not_found.len() > MAX_SUBTREES_PER_REQUEST {
291            return false;
292        }
293
294        // Check total entity count (even if individual subtrees are valid)
295        if self.total_entity_count() > MAX_TOTAL_ENTITIES {
296            return false;
297        }
298
299        // Validate all subtrees
300        self.subtrees.iter().all(SubtreeData::is_valid)
301    }
302}
303
304// =============================================================================
305// SubtreeData
306// =============================================================================
307
308/// Data for a single subtree.
309///
310/// Contains all entities within the subtree for bulk CRDT merge.
311#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
312pub struct SubtreeData {
313    /// Root ID of this subtree.
314    pub root_id: [u8; 32],
315
316    /// Root hash of this subtree (for verification).
317    pub root_hash: [u8; 32],
318
319    /// All entities in this subtree (leaves only).
320    /// Includes full data and metadata for CRDT merge.
321    ///
322    /// Limited to MAX_ENTITIES_PER_SUBTREE entries. Use `is_valid()` to check
323    /// bounds after deserialization from untrusted sources.
324    pub entities: Vec<TreeLeafData>,
325
326    /// Depth of this subtree (how many levels were traversed).
327    pub depth: usize,
328
329    /// Whether the subtree was truncated due to depth limit.
330    pub truncated: bool,
331}
332
333impl SubtreeData {
334    /// Create subtree data.
335    #[must_use]
336    pub fn new(
337        root_id: [u8; 32],
338        root_hash: [u8; 32],
339        entities: Vec<TreeLeafData>,
340        depth: usize,
341    ) -> Self {
342        Self {
343            root_id,
344            root_hash,
345            entities,
346            depth,
347            truncated: false,
348        }
349    }
350
351    /// Create truncated subtree data (depth limit reached).
352    #[must_use]
353    pub fn truncated(
354        root_id: [u8; 32],
355        root_hash: [u8; 32],
356        entities: Vec<TreeLeafData>,
357        depth: usize,
358    ) -> Self {
359        Self {
360            root_id,
361            root_hash,
362            entities,
363            depth,
364            truncated: true,
365        }
366    }
367
368    /// Number of entities in this subtree.
369    #[must_use]
370    pub fn entity_count(&self) -> usize {
371        self.entities.len()
372    }
373
374    /// Check if subtree is empty.
375    #[must_use]
376    pub fn is_empty(&self) -> bool {
377        self.entities.is_empty()
378    }
379
380    /// Check if more data exists beyond depth limit.
381    #[must_use]
382    pub fn is_truncated(&self) -> bool {
383        self.truncated
384    }
385
386    /// Check if subtree data is within valid bounds.
387    ///
388    /// Call this after deserializing from untrusted sources to prevent
389    /// resource exhaustion attacks. Validates entity count, depth, and all
390    /// contained leaf data.
391    #[must_use]
392    pub fn is_valid(&self) -> bool {
393        // Check entity count limit
394        if self.entities.len() > MAX_ENTITIES_PER_SUBTREE {
395            return false;
396        }
397
398        // Check depth limit
399        if self.depth > MAX_SUBTREE_DEPTH {
400            return false;
401        }
402
403        // Validate all leaf data
404        self.entities.iter().all(TreeLeafData::is_valid)
405    }
406}
407
408// =============================================================================
409// Heuristic Function
410// =============================================================================
411
412/// Compare HashComparison vs SubtreePrefetch for a given scenario.
413///
414/// Returns true if SubtreePrefetch is likely more efficient based on:
415/// - Tree depth > `DEEP_TREE_THRESHOLD`
416/// - Divergence ratio < `MAX_DIVERGENCE_RATIO`
417/// - Differing subtrees <= `MAX_CLUSTERED_SUBTREES`
418#[must_use]
419pub fn should_use_subtree_prefetch(
420    tree_depth: usize,
421    divergence_ratio: f64,
422    estimated_differing_subtrees: usize,
423) -> bool {
424    // SubtreePrefetch is better when:
425    // - Tree is deep (> DEEP_TREE_THRESHOLD levels)
426    // - Divergence is moderate (< MAX_DIVERGENCE_RATIO)
427    // - Changes are clustered (few differing subtrees)
428
429    let deep_tree = tree_depth > DEEP_TREE_THRESHOLD;
430    let moderate_divergence = divergence_ratio < MAX_DIVERGENCE_RATIO;
431    let clustered_changes = estimated_differing_subtrees <= MAX_CLUSTERED_SUBTREES;
432
433    deep_tree && moderate_divergence && clustered_changes
434}
435
436// =============================================================================
437// Tests
438// =============================================================================
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use crate::sync::hash_comparison::{CrdtType, LeafMetadata, MAX_LEAF_VALUE_SIZE};
444
445    // =========================================================================
446    // Helper Functions
447    // =========================================================================
448
449    fn make_leaf(key: u8, value: Vec<u8>) -> TreeLeafData {
450        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [key; 32]);
451        TreeLeafData::new([key; 32], value, metadata)
452    }
453
454    fn make_subtree(root_id: u8, entities: Vec<TreeLeafData>, depth: usize) -> SubtreeData {
455        SubtreeData::new([root_id; 32], [root_id + 100; 32], entities, depth)
456    }
457
458    // =========================================================================
459    // SubtreePrefetchRequest Tests
460    // =========================================================================
461
462    #[test]
463    fn test_subtree_prefetch_request_new() {
464        let roots = vec![[1u8; 32], [2u8; 32]];
465        let request = SubtreePrefetchRequest::new(roots.clone());
466
467        assert_eq!(request.subtree_roots, roots);
468        assert_eq!(request.depth(), DEFAULT_SUBTREE_MAX_DEPTH);
469        assert!(!request.is_unlimited());
470        assert_eq!(request.subtree_count(), 2);
471        assert!(!request.is_empty());
472        assert!(request.is_valid());
473    }
474
475    #[test]
476    fn test_subtree_prefetch_request_empty() {
477        let request = SubtreePrefetchRequest::new(vec![]);
478
479        assert!(request.is_empty());
480        assert_eq!(request.subtree_count(), 0);
481        assert!(request.is_valid());
482    }
483
484    #[test]
485    fn test_subtree_prefetch_request_with_depth() {
486        let roots = vec![[1u8; 32]];
487        let request = SubtreePrefetchRequest::with_depth(roots, 10);
488
489        assert_eq!(request.depth(), 10);
490        assert!(!request.is_unlimited());
491        assert!(request.is_valid());
492    }
493
494    #[test]
495    fn test_subtree_prefetch_request_with_zero_depth() {
496        let roots = vec![[1u8; 32]];
497        let request = SubtreePrefetchRequest::with_depth(roots, 0);
498
499        assert_eq!(request.depth(), 0);
500        assert!(!request.is_unlimited());
501        assert!(request.is_valid());
502    }
503
504    #[test]
505    fn test_subtree_prefetch_request_depth_clamping() {
506        // Test that depth is clamped during construction
507        let request = SubtreePrefetchRequest::with_depth(vec![[1u8; 32]], MAX_SUBTREE_DEPTH);
508        assert_eq!(request.depth(), MAX_SUBTREE_DEPTH);
509        assert!(request.is_valid());
510
511        // Excessive depth gets clamped at construction
512        let excessive =
513            SubtreePrefetchRequest::with_depth(vec![[1u8; 32]], MAX_SUBTREE_DEPTH + 100);
514        assert_eq!(excessive.depth(), MAX_SUBTREE_DEPTH);
515        assert!(excessive.is_valid());
516    }
517
518    #[test]
519    fn test_subtree_prefetch_request_depth_accessor_always_bounded() {
520        // depth() always returns a bounded value
521        let request = SubtreePrefetchRequest::new(vec![[1u8; 32]]);
522        assert_eq!(request.depth(), DEFAULT_SUBTREE_MAX_DEPTH);
523
524        // unlimited_depth() returns MAX_SUBTREE_DEPTH via depth()
525        let unlimited = SubtreePrefetchRequest::unlimited_depth(vec![[1u8; 32]]);
526        assert_eq!(unlimited.depth(), MAX_SUBTREE_DEPTH);
527        assert!(unlimited.is_unlimited());
528    }
529
530    #[test]
531    fn test_subtree_prefetch_request_unlimited() {
532        let roots = vec![[1u8; 32]];
533        let request = SubtreePrefetchRequest::unlimited_depth(roots);
534
535        assert!(request.is_unlimited());
536        // depth() still returns a bounded value for safety
537        assert_eq!(request.depth(), MAX_SUBTREE_DEPTH);
538        assert!(request.is_valid());
539    }
540
541    #[test]
542    fn test_subtree_prefetch_request_roundtrip() {
543        let request = SubtreePrefetchRequest::with_depth(vec![[1u8; 32], [2u8; 32]], 7);
544
545        let encoded = borsh::to_vec(&request).expect("serialize");
546        let decoded: SubtreePrefetchRequest = borsh::from_slice(&encoded).expect("deserialize");
547
548        assert_eq!(request, decoded);
549    }
550
551    #[test]
552    fn test_subtree_prefetch_request_validation() {
553        // Valid request at limit
554        let roots: Vec<[u8; 32]> = (0..MAX_SUBTREES_PER_REQUEST)
555            .map(|i| [i as u8; 32])
556            .collect();
557        let at_limit = SubtreePrefetchRequest::new(roots);
558        assert!(at_limit.is_valid());
559
560        // Invalid request over limit
561        let roots: Vec<[u8; 32]> = (0..=MAX_SUBTREES_PER_REQUEST)
562            .map(|i| [i as u8; 32])
563            .collect();
564        let over_limit = SubtreePrefetchRequest::new(roots);
565        assert!(!over_limit.is_valid());
566    }
567
568    // =========================================================================
569    // SubtreeData Tests
570    // =========================================================================
571
572    #[test]
573    fn test_subtree_data_new() {
574        let leaf = make_leaf(1, vec![1, 2, 3]);
575        let subtree = SubtreeData::new([10; 32], [11; 32], vec![leaf], 3);
576
577        assert_eq!(subtree.root_id, [10; 32]);
578        assert_eq!(subtree.root_hash, [11; 32]);
579        assert_eq!(subtree.entity_count(), 1);
580        assert_eq!(subtree.depth, 3);
581        assert!(!subtree.is_truncated());
582        assert!(!subtree.is_empty());
583        assert!(subtree.is_valid());
584    }
585
586    #[test]
587    fn test_subtree_data_truncated() {
588        let leaf = make_leaf(2, vec![4, 5, 6]);
589        let subtree = SubtreeData::truncated([20; 32], [21; 32], vec![leaf], 5);
590
591        assert!(subtree.is_truncated());
592        assert_eq!(subtree.depth, 5);
593        assert!(subtree.is_valid());
594    }
595
596    #[test]
597    fn test_subtree_data_empty() {
598        let subtree = SubtreeData::new([30; 32], [31; 32], vec![], 1);
599
600        assert!(subtree.is_empty());
601        assert_eq!(subtree.entity_count(), 0);
602        assert!(subtree.is_valid());
603    }
604
605    #[test]
606    fn test_subtree_data_zero_depth() {
607        let leaf = make_leaf(1, vec![1, 2, 3]);
608        let subtree = SubtreeData::new([10; 32], [11; 32], vec![leaf], 0);
609
610        assert_eq!(subtree.depth, 0);
611        assert!(subtree.is_valid());
612    }
613
614    #[test]
615    fn test_subtree_data_multiple_entities() {
616        let leaves = vec![
617            make_leaf(1, vec![1, 2, 3]),
618            make_leaf(2, vec![4, 5, 6]),
619            make_leaf(3, vec![7, 8, 9]),
620        ];
621        let subtree = SubtreeData::new([10; 32], [11; 32], leaves, 3);
622
623        assert_eq!(subtree.entity_count(), 3);
624        assert!(!subtree.is_empty());
625        assert!(subtree.is_valid());
626    }
627
628    #[test]
629    fn test_subtree_data_roundtrip() {
630        let leaf = make_leaf(3, vec![7, 8, 9]);
631        let subtree = SubtreeData::truncated([40; 32], [41; 32], vec![leaf], 4);
632
633        let encoded = borsh::to_vec(&subtree).expect("serialize");
634        let decoded: SubtreeData = borsh::from_slice(&encoded).expect("deserialize");
635
636        assert_eq!(subtree, decoded);
637    }
638
639    #[test]
640    fn test_subtree_data_validation() {
641        // Valid subtree with valid leaf
642        let valid_leaf = make_leaf(1, vec![1, 2, 3]);
643        let valid = SubtreeData::new([1; 32], [2; 32], vec![valid_leaf], 2);
644        assert!(valid.is_valid());
645
646        // Invalid subtree with oversized leaf value
647        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
648        let invalid_leaf = TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
649        let invalid = SubtreeData::new([1; 32], [2; 32], vec![invalid_leaf], 2);
650        assert!(!invalid.is_valid());
651    }
652
653    // =========================================================================
654    // SubtreePrefetchResponse Tests
655    // =========================================================================
656
657    #[test]
658    fn test_subtree_prefetch_response_complete() {
659        let leaf = make_leaf(1, vec![1, 2, 3]);
660        let subtree = make_subtree(10, vec![leaf], 2);
661
662        let response = SubtreePrefetchResponse::complete(vec![subtree]);
663
664        assert!(response.is_complete());
665        assert!(!response.is_empty());
666        assert_eq!(response.subtree_count(), 1);
667        assert_eq!(response.total_entity_count(), 1);
668        assert!(response.is_valid());
669    }
670
671    #[test]
672    fn test_subtree_prefetch_response_not_found() {
673        let response = SubtreePrefetchResponse::not_found(vec![[1u8; 32], [2u8; 32]]);
674
675        assert!(!response.is_complete());
676        assert!(!response.is_empty());
677        assert_eq!(response.subtree_count(), 0);
678        assert_eq!(response.not_found.len(), 2);
679        assert!(response.is_valid());
680    }
681
682    #[test]
683    fn test_subtree_prefetch_response_empty() {
684        let response = SubtreePrefetchResponse::new(vec![], vec![]);
685
686        assert!(response.is_complete());
687        assert!(response.is_empty());
688        assert_eq!(response.subtree_count(), 0);
689        assert_eq!(response.total_entity_count(), 0);
690        assert!(response.is_valid());
691    }
692
693    #[test]
694    fn test_subtree_prefetch_response_partial() {
695        let leaf1 = make_leaf(1, vec![1, 2]);
696        let leaf2 = make_leaf(2, vec![3, 4]);
697
698        let subtree1 = make_subtree(10, vec![leaf1], 2);
699        let subtree2 = make_subtree(20, vec![leaf2], 3);
700
701        let response = SubtreePrefetchResponse::new(
702            vec![subtree1, subtree2],
703            vec![[30u8; 32]], // One not found
704        );
705
706        assert!(!response.is_complete());
707        assert!(!response.is_empty());
708        assert_eq!(response.subtree_count(), 2);
709        assert_eq!(response.total_entity_count(), 2);
710        assert!(response.is_valid());
711    }
712
713    #[test]
714    fn test_subtree_prefetch_response_with_empty_subtrees() {
715        // Some subtrees have entities, some don't
716        let leaf = make_leaf(1, vec![1, 2, 3]);
717        let populated = make_subtree(10, vec![leaf], 2);
718        let empty = make_subtree(20, vec![], 1);
719
720        let response = SubtreePrefetchResponse::complete(vec![populated, empty]);
721
722        assert!(response.is_complete());
723        assert_eq!(response.subtree_count(), 2);
724        assert_eq!(response.total_entity_count(), 1); // Only one entity across all subtrees
725        assert!(response.is_valid());
726    }
727
728    #[test]
729    fn test_subtree_prefetch_response_total_entity_count_multiple() {
730        let subtree1 = make_subtree(1, vec![make_leaf(1, vec![1]), make_leaf(2, vec![2])], 2);
731        let subtree2 = make_subtree(
732            2,
733            vec![
734                make_leaf(3, vec![3]),
735                make_leaf(4, vec![4]),
736                make_leaf(5, vec![5]),
737            ],
738            3,
739        );
740        let subtree3 = make_subtree(3, vec![], 1); // Empty
741
742        let response = SubtreePrefetchResponse::complete(vec![subtree1, subtree2, subtree3]);
743
744        assert_eq!(response.subtree_count(), 3);
745        assert_eq!(response.total_entity_count(), 5); // 2 + 3 + 0
746    }
747
748    #[test]
749    fn test_subtree_prefetch_response_roundtrip() {
750        let leaf = make_leaf(4, vec![10, 11, 12]);
751        let subtree = make_subtree(50, vec![leaf], 2);
752
753        let response = SubtreePrefetchResponse::new(vec![subtree], vec![[60u8; 32]]);
754
755        let encoded = borsh::to_vec(&response).expect("serialize");
756        let decoded: SubtreePrefetchResponse = borsh::from_slice(&encoded).expect("deserialize");
757
758        assert_eq!(response, decoded);
759    }
760
761    #[test]
762    fn test_subtree_prefetch_response_validation() {
763        // Valid response at subtree limit
764        let subtrees: Vec<SubtreeData> = (0..MAX_SUBTREES_PER_REQUEST)
765            .map(|i| make_subtree(i as u8, vec![], 1))
766            .collect();
767        let at_limit = SubtreePrefetchResponse::complete(subtrees);
768        assert!(at_limit.is_valid());
769
770        // Invalid response over subtree limit
771        let subtrees: Vec<SubtreeData> = (0..=MAX_SUBTREES_PER_REQUEST)
772            .map(|i| make_subtree(i as u8, vec![], 1))
773            .collect();
774        let over_limit = SubtreePrefetchResponse::complete(subtrees);
775        assert!(!over_limit.is_valid());
776
777        // Invalid response over not_found limit
778        let not_found: Vec<[u8; 32]> = (0..=MAX_SUBTREES_PER_REQUEST)
779            .map(|i| [i as u8; 32])
780            .collect();
781        let over_not_found = SubtreePrefetchResponse::not_found(not_found);
782        assert!(!over_not_found.is_valid());
783
784        // Invalid response with invalid subtree
785        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
786        let invalid_leaf = TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
787        let invalid_subtree = SubtreeData::new([1; 32], [2; 32], vec![invalid_leaf], 2);
788        let response_with_invalid = SubtreePrefetchResponse::complete(vec![invalid_subtree]);
789        assert!(!response_with_invalid.is_valid());
790    }
791
792    // =========================================================================
793    // Heuristic Function Tests
794    // =========================================================================
795
796    #[test]
797    fn test_heuristic_constants_are_sensible() {
798        // Verify the constants have sensible values
799        assert!(DEEP_TREE_THRESHOLD > 0);
800        assert!(DEEP_TREE_THRESHOLD < MAX_SUBTREE_DEPTH);
801        assert!(MAX_DIVERGENCE_RATIO > 0.0);
802        assert!(MAX_DIVERGENCE_RATIO < 1.0);
803        assert!(MAX_CLUSTERED_SUBTREES > 0);
804        assert!(MAX_CLUSTERED_SUBTREES <= MAX_SUBTREES_PER_REQUEST);
805    }
806
807    #[test]
808    fn test_should_use_subtree_prefetch_basic() {
809        // Deep tree, moderate divergence, clustered - YES
810        assert!(should_use_subtree_prefetch(5, 0.10, 3));
811
812        // Deep tree, high divergence - NO
813        assert!(!should_use_subtree_prefetch(5, 0.30, 3));
814
815        // Shallow tree - NO
816        assert!(!should_use_subtree_prefetch(2, 0.10, 3));
817
818        // Many differing subtrees (scattered) - NO
819        assert!(!should_use_subtree_prefetch(5, 0.10, 10));
820    }
821
822    #[test]
823    fn test_should_use_subtree_prefetch_boundary_conditions() {
824        // Exactly at depth threshold (depth > DEEP_TREE_THRESHOLD, not >=)
825        assert!(!should_use_subtree_prefetch(DEEP_TREE_THRESHOLD, 0.10, 3));
826        assert!(should_use_subtree_prefetch(
827            DEEP_TREE_THRESHOLD + 1,
828            0.10,
829            3
830        ));
831
832        // Exactly at divergence threshold (< MAX_DIVERGENCE_RATIO, not <=)
833        assert!(!should_use_subtree_prefetch(5, MAX_DIVERGENCE_RATIO, 3));
834        assert!(should_use_subtree_prefetch(
835            5,
836            MAX_DIVERGENCE_RATIO - 0.01,
837            3
838        ));
839        assert!(should_use_subtree_prefetch(
840            5,
841            MAX_DIVERGENCE_RATIO - 0.000001,
842            3
843        )); // just under
844
845        // Exactly at subtree threshold (<= MAX_CLUSTERED_SUBTREES)
846        assert!(should_use_subtree_prefetch(5, 0.10, MAX_CLUSTERED_SUBTREES));
847        assert!(!should_use_subtree_prefetch(
848            5,
849            0.10,
850            MAX_CLUSTERED_SUBTREES + 1
851        ));
852    }
853
854    #[test]
855    fn test_should_use_subtree_prefetch_edge_cases() {
856        // Zero values
857        assert!(!should_use_subtree_prefetch(0, 0.10, 3)); // zero depth
858        assert!(should_use_subtree_prefetch(5, 0.0, 3)); // zero divergence
859        assert!(should_use_subtree_prefetch(5, 0.10, 0)); // zero subtrees
860
861        // Very large values
862        assert!(should_use_subtree_prefetch(1000, 0.10, 3)); // very deep tree
863        assert!(!should_use_subtree_prefetch(5, 1.0, 3)); // 100% divergence
864        assert!(!should_use_subtree_prefetch(5, 10.0, 3)); // >100% divergence (edge case)
865        assert!(!should_use_subtree_prefetch(5, 0.10, 1000)); // many subtrees
866
867        // All conditions fail
868        assert!(!should_use_subtree_prefetch(2, 0.50, 10));
869
870        // All conditions pass with extreme values
871        assert!(should_use_subtree_prefetch(100, 0.001, 1));
872    }
873
874    #[test]
875    fn test_should_use_subtree_prefetch_typical_scenarios() {
876        // Scenario 1: Large, deep tree with clustered changes (ideal for subtree prefetch)
877        assert!(should_use_subtree_prefetch(10, 0.05, 2));
878
879        // Scenario 2: Shallow config tree (not suitable)
880        assert!(!should_use_subtree_prefetch(2, 0.05, 2));
881
882        // Scenario 3: Deep tree but scattered changes (HashComparison better)
883        assert!(!should_use_subtree_prefetch(10, 0.05, 20));
884
885        // Scenario 4: Deep tree but high divergence (full sync better)
886        assert!(!should_use_subtree_prefetch(10, 0.60, 2));
887
888        // Scenario 5: Medium tree with moderate changes (borderline)
889        assert!(should_use_subtree_prefetch(4, 0.15, 5));
890    }
891
892    // =========================================================================
893    // Validation Edge Case Tests
894    // =========================================================================
895
896    #[test]
897    fn test_subtree_data_validation_entity_limit() {
898        // At entity limit - should be valid
899        let leaves: Vec<TreeLeafData> = (0..MAX_ENTITIES_PER_SUBTREE)
900            .map(|i| make_leaf(i as u8, vec![i as u8]))
901            .collect();
902        let at_limit = SubtreeData::new([1; 32], [2; 32], leaves, 5);
903        assert!(at_limit.is_valid());
904
905        // Over entity limit - should be invalid
906        let leaves: Vec<TreeLeafData> = (0..=MAX_ENTITIES_PER_SUBTREE)
907            .map(|i| make_leaf(i as u8, vec![i as u8]))
908            .collect();
909        let over_limit = SubtreeData::new([1; 32], [2; 32], leaves, 5);
910        assert!(!over_limit.is_valid());
911    }
912
913    #[test]
914    fn test_subtree_data_validation_depth_limit() {
915        let leaf = make_leaf(1, vec![1, 2, 3]);
916
917        // At depth limit - should be valid
918        let at_limit = SubtreeData::new([1; 32], [2; 32], vec![leaf.clone()], MAX_SUBTREE_DEPTH);
919        assert!(at_limit.is_valid());
920
921        // Over depth limit - should be invalid
922        let over_limit = SubtreeData::new([1; 32], [2; 32], vec![leaf], MAX_SUBTREE_DEPTH + 1);
923        assert!(!over_limit.is_valid());
924    }
925
926    #[test]
927    fn test_subtree_response_validation_total_entity_limit() {
928        use super::MAX_TOTAL_ENTITIES;
929
930        // Create subtrees that individually are valid but together exceed total limit
931        // Each subtree has MAX_ENTITIES_PER_SUBTREE, and we create enough to exceed MAX_TOTAL_ENTITIES
932        let entities_per_subtree = 1000;
933        let num_subtrees = (MAX_TOTAL_ENTITIES / entities_per_subtree) + 1;
934
935        let subtrees: Vec<SubtreeData> = (0..num_subtrees)
936            .map(|i| {
937                let leaves: Vec<TreeLeafData> = (0..entities_per_subtree)
938                    .map(|j| make_leaf((i * 100 + j) as u8, vec![(i * 100 + j) as u8]))
939                    .collect();
940                SubtreeData::new([i as u8; 32], [(i + 100) as u8; 32], leaves, 5)
941            })
942            .collect();
943
944        let response = SubtreePrefetchResponse::complete(subtrees);
945        assert!(!response.is_valid()); // Should be invalid due to total entity count
946    }
947
948    // =========================================================================
949    // Security / Exploit Tests
950    // =========================================================================
951
952    #[test]
953    fn test_subtree_request_memory_exhaustion_prevention() {
954        // Request with maximum allowed roots should be valid
955        let roots: Vec<[u8; 32]> = (0..MAX_SUBTREES_PER_REQUEST)
956            .map(|i| [i as u8; 32])
957            .collect();
958        let valid = SubtreePrefetchRequest::new(roots);
959        assert!(valid.is_valid());
960
961        // Request exceeding limit should be invalid
962        let roots: Vec<[u8; 32]> = (0..=MAX_SUBTREES_PER_REQUEST)
963            .map(|i| [i as u8; 32])
964            .collect();
965        let invalid = SubtreePrefetchRequest::new(roots);
966        assert!(!invalid.is_valid());
967    }
968
969    #[test]
970    fn test_subtree_depth_exhaustion_prevention() {
971        // Attempt to request extremely deep traversal should be clamped at construction
972        let request = SubtreePrefetchRequest::with_depth(vec![[1u8; 32]], usize::MAX);
973        assert_eq!(request.depth(), MAX_SUBTREE_DEPTH);
974        assert!(request.is_valid());
975
976        // unlimited_depth() returns MAX_SUBTREE_DEPTH via depth() accessor
977        let unlimited = SubtreePrefetchRequest::unlimited_depth(vec![[1u8; 32]]);
978        assert_eq!(unlimited.depth(), MAX_SUBTREE_DEPTH);
979        assert!(unlimited.is_valid());
980    }
981
982    #[test]
983    fn test_subtree_request_max_depth_validation() {
984        // Valid request with depth at limit
985        let at_limit = SubtreePrefetchRequest::with_depth(vec![[1u8; 32]], MAX_SUBTREE_DEPTH);
986        assert!(at_limit.is_valid());
987
988        // Deserialized request with excessive max_depth should be caught by is_valid()
989        // We simulate this by deserializing a manually constructed request
990        let valid = SubtreePrefetchRequest::with_depth(vec![[1u8; 32]], 10);
991        let mut bytes = borsh::to_vec(&valid).expect("serialize");
992
993        // The max_depth field is after subtree_roots in the serialization
994        // For a single root (32 bytes + 4 bytes length prefix) = 36 bytes
995        // Then Option<usize> with Some variant = 1 byte tag + 8 bytes value
996        // Corrupt the value to be larger than MAX_SUBTREE_DEPTH
997        let depth_offset = 4 + 32 + 1; // vec length + root + option tag
998        bytes[depth_offset..depth_offset + 8]
999            .copy_from_slice(&(MAX_SUBTREE_DEPTH as u64 + 100).to_le_bytes());
1000
1001        let corrupted: SubtreePrefetchRequest = borsh::from_slice(&bytes).expect("deserialize");
1002        // depth() accessor still returns bounded value
1003        assert_eq!(corrupted.depth(), MAX_SUBTREE_DEPTH);
1004        // But is_valid() catches the raw invalid value
1005        assert!(!corrupted.is_valid());
1006    }
1007
1008    #[test]
1009    fn test_subtree_total_entity_count_overflow_prevention() {
1010        // Create a response with subtrees containing many entities
1011        // to test that total_entity_count uses saturating arithmetic
1012        let leaf = make_leaf(1, vec![1, 2, 3]);
1013        let subtree = SubtreeData::new([1; 32], [2; 32], vec![leaf], 2);
1014
1015        let response = SubtreePrefetchResponse::complete(vec![subtree]);
1016
1017        // Should not panic or overflow
1018        let count = response.total_entity_count();
1019        assert_eq!(count, 1);
1020    }
1021
1022    #[test]
1023    fn test_subtree_cross_validation_consistency() {
1024        // Verify that individual subtree validation is enforced in response validation
1025        let metadata = LeafMetadata::new(CrdtType::lww_register("test"), 100, [1; 32]);
1026        let oversized_leaf =
1027            TreeLeafData::new([1; 32], vec![0u8; MAX_LEAF_VALUE_SIZE + 1], metadata);
1028        let invalid_subtree = SubtreeData::new([1; 32], [2; 32], vec![oversized_leaf], 2);
1029
1030        // Invalid subtree by itself
1031        assert!(!invalid_subtree.is_valid());
1032
1033        // Response containing invalid subtree should also be invalid
1034        let response = SubtreePrefetchResponse::complete(vec![invalid_subtree]);
1035        assert!(!response.is_valid());
1036    }
1037
1038    #[test]
1039    fn test_subtree_special_values() {
1040        // All zeros
1041        let zeros_subtree = SubtreeData::new([0u8; 32], [0u8; 32], vec![], 0);
1042        assert!(zeros_subtree.is_valid());
1043
1044        // All ones
1045        let ones_subtree = SubtreeData::new([0xFF; 32], [0xFF; 32], vec![], MAX_SUBTREE_DEPTH);
1046        assert!(ones_subtree.is_valid());
1047
1048        // Request with all-zeros roots
1049        let request = SubtreePrefetchRequest::new(vec![[0u8; 32]]);
1050        assert!(request.is_valid());
1051
1052        // Request with all-ones roots
1053        let request = SubtreePrefetchRequest::new(vec![[0xFF; 32]]);
1054        assert!(request.is_valid());
1055    }
1056
1057    #[test]
1058    fn test_subtree_serialization_roundtrip_with_edge_values() {
1059        // Test roundtrip with boundary values
1060        let leaf = make_leaf(0xFF, vec![0xFF; 100]);
1061        let subtree = SubtreeData::truncated([0xFF; 32], [0u8; 32], vec![leaf], MAX_SUBTREE_DEPTH);
1062
1063        let encoded = borsh::to_vec(&subtree).expect("serialize");
1064        let decoded: SubtreeData = borsh::from_slice(&encoded).expect("deserialize");
1065
1066        assert_eq!(subtree, decoded);
1067        assert!(decoded.is_valid());
1068        assert!(decoded.is_truncated());
1069        assert_eq!(decoded.depth, MAX_SUBTREE_DEPTH);
1070    }
1071
1072    #[test]
1073    fn test_subtree_response_all_not_found() {
1074        // Response where everything was not found (no subtrees returned)
1075        let not_found: Vec<[u8; 32]> = (0..50).map(|i| [i as u8; 32]).collect();
1076        let response = SubtreePrefetchResponse::not_found(not_found);
1077
1078        assert!(!response.is_complete());
1079        assert!(!response.is_empty());
1080        assert_eq!(response.subtree_count(), 0);
1081        assert_eq!(response.total_entity_count(), 0);
1082        assert!(response.is_valid());
1083    }
1084
1085    #[test]
1086    fn test_subtree_empty_entities_is_valid() {
1087        // Subtree with no entities (internal node with no leaves yet)
1088        let empty_subtree = SubtreeData::new([1; 32], [2; 32], vec![], 5);
1089        assert!(empty_subtree.is_valid());
1090        assert!(empty_subtree.is_empty());
1091        assert_eq!(empty_subtree.entity_count(), 0);
1092    }
1093}