@@ -412,6 +412,191 @@ func TestConsistency(t *testing.T) {
412412 }
413413}
414414
415+ // TestSubtreeConsistency contains consistency proof tests. For reference, consider
416+ // the following example:
417+ //
418+ // aaaaa <== Level 4
419+ // / \
420+ // ... ...
421+ // / \
422+ // / \
423+ // / \
424+ // aaaa bbbb <== Level 3
425+ // / \ / \
426+ // / \ / \
427+ // / \ / \
428+ // / \ / \
429+ // / \ / \
430+ // aaa bbb ccc ddd <== Level 2
431+ // / \ / \ / \ / \
432+ // / \ / \ / \ / \
433+ // / \ / \ / \ / \
434+ // aa bb cc dd ee ff gg hh ii <== Level 1
435+ // / \ / \ / \ / \ / \ / \ / \ / \ / \
436+ // a b c d e f g h i j k l m n o p q r <== Level 0
437+ // | | | | | | | | | | | | | | | | | |
438+ // d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d12 d14 d16
439+ // | | | |
440+ // d11 d13 d15 d17
441+ //
442+ // The consistency proof between tree size 5 and 7 consists of nodes e, f, g,
443+ // and aaa. The node g is taken instead of its missing parent.
444+ func TestSubtreeConsistency (t * testing.T ) {
445+ id := compact .NewNodeID
446+ nodes := func (ids ... compact.NodeID ) Nodes {
447+ return Nodes {IDs : ids }
448+ }
449+ rehash := func (begin , end int , ids ... compact.NodeID ) Nodes {
450+ return Nodes {IDs : ids , begin : begin , end : end }
451+ }
452+ for _ , tc := range []struct {
453+ start uint64
454+ end uint64
455+ size uint64
456+ want Nodes
457+ wantErr bool
458+ }{
459+ // Errors.
460+ {start : 0 , end : 0 , size : 0 , wantErr : true }, // start = end = 0
461+ {start : 1 , end : 1 , size : 1 , wantErr : true }, // start = end
462+ {start : 2 , end : 1 , size : 0 , wantErr : true }, // start > end
463+ {start : 0 , end : 5 , size : 0 , wantErr : true }, // end > size
464+ {start : 0 , end : 9 , size : 8 , wantErr : true }, // end > size
465+ {start : 3 , end : 5 , size : 3 , wantErr : true }, // start not multiple of bit_ceil(end-start)
466+ {start : 1 , end : 1 << 63 + 2 , size : 1 << 63 + 2 , wantErr : true }, // start not multiple of bit_ceil(len) with big tree
467+
468+ // Small trees.
469+ // start = 0
470+ {start : 0 , end : 1 , size : 2 , want : nodes (id (0 , 1 ))}, // b
471+ {start : 0 , end : 1 , size : 4 , want : nodes (id (0 , 1 ), id (1 , 1 ))}, // b bb
472+ {start : 0 , end : 1 , size : 6 , want : rehash (2 , 3 , id (0 , 1 ), id (1 , 1 ), id (1 , 2 ))}, // b bb cc
473+ {start : 0 , end : 2 , size : 3 , want : rehash (0 , 1 , id (0 , 2 ))}, // c
474+ {start : 0 , end : 2 , size : 8 , want : nodes (id (1 , 1 ), id (2 , 1 ))}, // bb bbb
475+ {start : 0 , end : 3 , size : 7 , want : rehash (3 , 5 , // bbb=hash(cc,g)
476+ id (0 , 2 ), id (0 , 3 ), id (1 , 0 ), id (0 , 6 ), id (1 , 2 ))}, // c d aa g cc
477+ {start : 0 , end : 4 , size : 7 , want : rehash (0 , 2 , // bbb=hash(cc,g)
478+ id (0 , 6 ), id (1 , 2 ))}, // g cc
479+ {start : 0 , end : 5 , size : 7 , want : rehash (2 , 3 ,
480+ id (0 , 4 ), id (0 , 5 ), id (0 , 6 ), id (2 , 0 ))}, // e f g aaa
481+ {start : 0 , end : 6 , size : 7 , want : rehash (1 , 2 ,
482+ id (1 , 2 ), id (0 , 6 ), id (2 , 0 ))}, // cc g aaa
483+ {start : 0 , end : 6 , size : 8 , want : nodes (
484+ id (1 , 2 ), id (1 , 3 ), id (2 , 0 ))}, // cc h aaa
485+ {start : 0 , end : 7 , size : 8 , want : nodes (
486+ id (0 , 6 ), id (0 , 7 ), id (1 , 2 ), id (2 , 0 ))}, // g h cc aaa
487+ // start > 0
488+ {start : 1 , end : 2 , size : 3 , want : rehash (1 , 2 , id (0 , 0 ), id (0 , 2 ))}, // a c
489+ {start : 1 , end : 2 , size : 5 , want : rehash (2 , 3 , id (0 , 0 ), id (1 , 1 ), id (0 , 4 ))}, // a bb e
490+ {start : 2 , end : 4 , size : 5 , want : rehash (1 , 2 , id (1 , 0 ), id (0 , 4 ))}, // aa e
491+ {start : 1 , end : 2 , size : 7 , want : rehash (2 , 4 , id (0 , 0 ), id (1 , 1 ), id (0 , 6 ), id (1 , 2 ))}, // a bb g cc
492+ {start : 2 , end : 4 , size : 10 , want : rehash (2 , 3 , id (1 , 0 ), id (2 , 1 ), id (1 , 4 ))}, // aa bbb ee
493+ {start : 4 , end : 6 , size : 10 , want : rehash (2 , 3 , id (1 , 3 ), id (2 , 0 ), id (1 , 4 ))}, // dd aaa ee
494+ {start : 4 , end : 7 , size : 11 , want : rehash (4 , 6 , // ccc=hash(ee,k)
495+ id (0 , 6 ), id (0 , 7 ), id (1 , 2 ), id (2 , 0 ), id (0 , 10 ), id (1 , 4 ))}, // g h cc aaa k ee
496+ {start : 4 , end : 8 , size : 11 , want : rehash (1 , 3 , // ccc=hash(ee,k)
497+ id (2 , 0 ), id (0 , 10 ), id (1 , 4 ))}, // aaa k ee
498+ {start : 8 , end : 13 , size : 15 , want : rehash (2 , 3 ,
499+ id (0 , 12 ), id (0 , 13 ), id (0 , 14 ), id (2 , 2 ), id (3 , 0 ))}, // m n o ccc aaaa
500+ {start : 8 , end : 14 , size : 15 , want : rehash (1 , 2 , // hh=hash(o)
501+ id (1 , 6 ), id (0 , 14 ), id (2 , 2 ), id (3 , 0 ))}, // gg, o, ccc, aaaa
502+ {start : 8 , end : 14 , size : 16 , want : nodes (
503+ id (1 , 6 ), id (1 , 7 ), id (2 , 2 ), id (3 , 0 ))}, // gg hh ccc aaaa
504+ {start : 8 , end : 15 , size : 16 , want : nodes (
505+ id (0 , 14 ), id (0 , 15 ), id (1 , 6 ), id (2 , 2 ), id (3 , 0 ))}, // o p gg ccc aaaa
506+ // end = size
507+ {start : 1 , end : 2 , size : 2 , want : nodes (id (0 , 0 ))}, // a
508+ {start : 3 , end : 4 , size : 4 , want : nodes (id (0 , 2 ), id (1 , 0 ))}, // c aa
509+ {start : 5 , end : 6 , size : 6 , want : nodes (id (0 , 4 ), id (2 , 0 ))}, // e aaa
510+ {start : 2 , end : 3 , size : 3 , want : nodes (id (1 , 0 ))}, // aa
511+ {start : 6 , end : 8 , size : 8 , want : nodes (id (1 , 2 ), id (2 , 0 ))}, // cc aaa
512+ {start : 4 , end : 7 , size : 7 , want : nodes (id (2 , 0 ))}, // aaa
513+ {start : 6 , end : 7 , size : 7 , want : nodes (id (1 , 2 ), id (2 , 0 ))}, // cc aaa
514+ {start : 4 , end : 8 , size : 8 , want : nodes (id (2 , 0 ))}, // aaa
515+ {start : 7 , end : 8 , size : 8 , want : nodes (id (0 , 6 ), id (1 , 2 ), id (2 , 0 ))}, // g h cc aaa
516+
517+ // Same tree size.
518+ {start : 0 , end : 1 , size : 1 , want : Nodes {IDs : []compact.NodeID {}}},
519+ {start : 0 , end : 2 , size : 2 , want : Nodes {IDs : []compact.NodeID {}}},
520+ {start : 0 , end : 3 , size : 3 , want : Nodes {IDs : []compact.NodeID {}}},
521+ {start : 0 , end : 4 , size : 4 , want : Nodes {IDs : []compact.NodeID {}}},
522+ {start : 0 , end : 5 , size : 5 , want : Nodes {IDs : []compact.NodeID {}}},
523+ {start : 0 , end : 7 , size : 7 , want : Nodes {IDs : []compact.NodeID {}}},
524+ {start : 0 , end : 8 , size : 8 , want : Nodes {IDs : []compact.NodeID {}}},
525+
526+ // Smaller trees within a bigger stored tree.
527+ // start = 0
528+ {start : 0 , end : 2 , size : 4 , want : nodes (id (1 , 1 ))}, // bb
529+ {start : 0 , end : 3 , size : 5 , want : rehash (3 , 4 ,
530+ id (0 , 2 ), id (0 , 3 ), id (1 , 0 ), id (0 , 4 ))}, // c d aa e
531+ {start : 0 , end : 3 , size : 6 , want : rehash (3 , 4 ,
532+ id (0 , 2 ), id (0 , 3 ), id (1 , 0 ), id (1 , 2 ))}, // c d aa cc
533+ {start : 0 , end : 4 , size : 6 , want : rehash (0 , 1 , id (1 , 2 ))}, // cc
534+ {start : 0 , end : 1 , size : 7 , want : rehash (2 , 4 , // bbb=hash(cc,g)
535+ id (0 , 1 ), id (1 , 1 ), id (0 , 6 ), id (1 , 2 ))}, // b bb g cc
536+ // start > 0
537+ {start : 2 , end : 4 , size : 6 , want : rehash (1 , 2 , // bbb=hash(cc)
538+ id (1 , 0 ), id (1 , 2 ))}, // aa, cc
539+ {start : 4 , end : 7 , size : 9 , want : rehash (4 , 5 , // bbbb=hash(i)
540+ id (0 , 6 ), id (0 , 7 ), id (1 , 2 ), id (2 , 0 ), id (0 , 8 ))}, // g h cc aaa i
541+ {start : 4 , end : 7 , size : 10 , want : rehash (4 , 5 , // bbbb=hash(ee)
542+ id (0 , 6 ), id (0 , 7 ), id (1 , 2 ), id (2 , 0 ), id (1 , 4 ))}, // g h cc aaa ee
543+ {start : 4 , end : 8 , size : 10 , want : rehash (1 , 2 , //ccc=hash(ee)
544+ id (2 , 0 ), id (1 , 4 ))}, // aa ee
545+ {start : 2 , end : 3 , size : 9 , want : rehash (3 , 4 , // bbbb=hash(i)
546+ id (0 , 3 ), id (1 , 0 ), id (2 , 1 ), id (0 , 8 ))}, // d aa bbb i
547+ // end = size
548+ {start : 4 , end : 6 , size : 6 , want : nodes (id (2 , 0 ))}, // aaa
549+ {start : 8 , end : 9 , size : 9 , want : nodes (id (3 , 0 ))}, // aaaa
550+ {start : 8 , end : 10 , size : 10 , want : nodes (id (3 , 0 ))}, // aaaa
551+ {start : 8 , end : 12 , size : 12 , want : nodes (id (3 , 0 ))}, // aaaa
552+
553+ // Some rehashes in the middle of the returned list.
554+ {start : 0 , end : 10 , size : 15 , want : rehash (2 , 4 ,
555+ id (1 , 4 ), id (1 , 5 ), id (0 , 14 ), id (1 , 6 ), id (3 , 0 ))},
556+ {start : 16 , end : 26 , size : 31 , want : rehash (2 , 4 ,
557+ id (1 , 12 ), id (1 , 13 ), id (0 , 30 ), id (1 , 14 ), id (3 , 2 ), id (4 , 0 ))},
558+ {start : 0 , end : 24 , size : 31 , want : rehash (1 , 4 ,
559+ id (3 , 2 ),
560+ id (0 , 30 ), id (1 , 14 ), id (2 , 6 ),
561+ id (4 , 0 ),
562+ )},
563+ {start : 32 , end : 56 , size : 63 , want : rehash (1 , 4 ,
564+ id (3 , 6 ),
565+ id (0 , 62 ), id (1 , 30 ), id (2 , 14 ),
566+ id (4 , 2 ),
567+ id (5 , 0 ),
568+ )},
569+ {start : 0 , end : 81 , size : 95 , want : rehash (4 , 7 ,
570+ id (0 , 80 ), id (0 , 81 ), id (1 , 41 ), id (2 , 21 ),
571+ id (0 , 94 ), id (1 , 46 ), id (2 , 22 ),
572+ id (4 , 4 ), id (6 , 0 ),
573+ )},
574+ {start : 128 , end : 209 , size : 223 , want : rehash (4 , 7 ,
575+ id (0 , 208 ), id (0 , 209 ), id (1 , 105 ), id (2 , 53 ),
576+ id (0 , 222 ), id (1 , 110 ), id (2 , 54 ),
577+ id (4 , 12 ), id (6 , 2 ),
578+ id (7 , 0 ),
579+ )},
580+ } {
581+ t .Run (fmt .Sprintf ("%d:%d:%d" , tc .start , tc .end , tc .size ), func (t * testing.T ) {
582+ proof , err := SubtreeConsistency (tc .start , tc .end , tc .size )
583+ if tc .wantErr {
584+ if err == nil {
585+ t .Fatal ("accepted bad params" )
586+ }
587+ return
588+ } else if err != nil {
589+ t .Fatalf ("Consistency: %v" , err )
590+ }
591+ // Ignore the ephemeral node, it is tested separately.
592+ proof .ephem = compact.NodeID {}
593+ if diff := cmp .Diff (tc .want , proof , cmp .AllowUnexported (Nodes {})); diff != "" {
594+ t .Errorf ("paths mismatch:\n %v" , diff )
595+ }
596+ })
597+ }
598+ }
599+
415600func TestInclusionSucceedsUpToTreeSize (t * testing.T ) {
416601 const maxSize = uint64 (555 )
417602 for ts := uint64 (1 ); ts <= maxSize ; ts ++ {
@@ -450,6 +635,22 @@ func TestConsistencySucceedsUpToTreeSize(t *testing.T) {
450635 }
451636}
452637
638+ func TestSubtreeConsistencySucceedsUpToTreeSize (t * testing.T ) {
639+ const maxSize = uint64 (100 )
640+ for s := uint64 (1 ); s <= maxSize ; s ++ {
641+ for sbe := uint64 (1 ); sbe <= s ; sbe ++ {
642+ for sbs := range sbe {
643+ if err := isSubtreeValid (sbs , sbe ); err != nil {
644+ continue
645+ }
646+ if _ , err := SubtreeConsistency (sbs , sbe , s ); err != nil {
647+ t .Errorf ("SubtreeConsistency(sbs:%d, sbe:%d, s:%d) = %v" , sbs , sbe , s , err )
648+ }
649+ }
650+ }
651+ }
652+ }
653+
453654func TestEphem (t * testing.T ) {
454655 id := compact .NewNodeID
455656 for _ , tc := range []struct {
0 commit comments