Skip to content

Commit eb0fa9a

Browse files
committed
SubtreeConsistency
test
1 parent 2097c8d commit eb0fa9a

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

proof/proof.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,45 @@ func Consistency(size1, size2 uint64) (Nodes, error) {
109109
return p, nil
110110
}
111111

112+
// SubtreeConsistency returns the information on how to fetch and construct a
113+
// consistency proof between a subtree covering [start, end) and log Merkle
114+
// tree of a given size. It requires 0 <= start < end <= size.
115+
func SubtreeConsistency(start, end, size uint64) (Nodes, error) {
116+
if start >= end {
117+
return Nodes{}, fmt.Errorf("start %d larger or equal than end %d", start, end)
118+
}
119+
if end > size {
120+
return Nodes{}, fmt.Errorf("subtree end %d strictly larger than tree size %d", end, size)
121+
}
122+
if shift := bits.Len64(end - start - 1); shift >= 64 {
123+
if start != 0 {
124+
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start)", start)
125+
}
126+
} else if bc := uint64(1) << shift; start%bc != 0 {
127+
return Nodes{}, fmt.Errorf("start %d not a multiple of bit_ceil(end - start) = %d", start, bc)
128+
}
129+
if start == 0 && end == size {
130+
return Nodes{IDs: []compact.NodeID{}}, nil
131+
}
132+
133+
// Find the root of the subtree.
134+
level := uint(bits.TrailingZeros64(end - start))
135+
index := (end - 1) >> level
136+
// The consistency proof consists of this node (except if the subtree is full
137+
// and starts at 0, in which case adding this node would be redundant because the client
138+
// is assumed to know it from a checkpoint), and nodes of the inclusion proof
139+
// into this node in the tree of the given size.
140+
p := nodes(index, level, size)
141+
142+
// Handle the case when end is a power of 2.
143+
// TODO: do we also need to do taht for a full subtree?
144+
// if first node is the root of the subtree.
145+
if index == 0 {
146+
return p.skipFirst(), nil
147+
}
148+
return p, nil
149+
}
150+
112151
// nodes returns the node IDs necessary to prove that the (level, index) node
113152
// is included in the Merkle tree of the given size.
114153
func nodes(index uint64, level uint, size uint64) Nodes {

proof/proof_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
414583
func 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+
455643
func TestEphem(t *testing.T) {
456644
id := compact.NewNodeID
457645
for _, tc := range []struct {

0 commit comments

Comments
 (0)