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