Skip to content

Commit 98fab82

Browse files
committed
SubtreeInclusionProof Tests
1 parent 55bed6b commit 98fab82

2 files changed

Lines changed: 185 additions & 1 deletion

File tree

proof/proof.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func SubtreeInclusion(index, start, end uint64) (Nodes, error) {
6969
// Shift the subtree to the left such that it starts at 0.
7070
p := nodes(index-start, 0, end-start)
7171

72-
// Shift all nodes back to the right.
72+
// Shift nodes back to the right matching the original subtree position.
7373
for n := range p.IDs {
7474
p.IDs[n].Index += start >> p.IDs[n].Level
7575
}

proof/proof_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,177 @@ func TestInclusion(t *testing.T) {
130130
}
131131
}
132132

133+
// TestSubtreeInclusion contains subtree inclusion proof tests. For reference, consider the
134+
// following example of a tree from RFC 6962:
135+
//
136+
// aaaaa <== Level 4
137+
// / \
138+
// ... ...
139+
// / \
140+
// / \
141+
// / \
142+
// aaaa bbbb <== Level 3
143+
// / \ / \
144+
// / \ / \
145+
// / \ / \
146+
// / \ / \
147+
// / \ / \
148+
// aaa bbb ccc ddd <== Level 2
149+
// / \ / \ / \ / \
150+
// / \ / \ / \ / \
151+
// / \ / \ / \ / \
152+
// aa bb cc dd ee ff gg hh ii <== Level 1
153+
// / \ / \ / \ / \ / \ / \ / \ / \ / \
154+
// a b c d e f g h i j k l m n o p q r <== Level 0
155+
// | | | | | | | | | | | | | | | | | |
156+
// d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d12 d14 d16
157+
// | | | |
158+
// d11 d13 d15 d17
159+
//
160+
// Our storage node layers are always populated from the bottom up, hence the
161+
// gaps above ii.
162+
func TestSubtreeInclusion(t *testing.T) {
163+
id := compact.NewNodeID
164+
nodes := func(ids ...compact.NodeID) Nodes {
165+
return Nodes{IDs: ids}
166+
}
167+
rehash := func(begin, end int, ids ...compact.NodeID) Nodes {
168+
return Nodes{IDs: ids, begin: begin, end: end}
169+
}
170+
for _, tc := range []struct {
171+
index uint64 // Leaf index in the requested tree.
172+
start uint64 // The smallest index of the subtree.
173+
end uint64 // The largest index of the subtree + 1.
174+
want Nodes
175+
wantErr bool
176+
}{
177+
// Errors.
178+
{start: 0, end: 0, index: 0, wantErr: true}, // everything at 0
179+
{start: 1, end: 1, index: 0, wantErr: true}, // start = end
180+
{start: 2, end: 1, index: 0, wantErr: true}, // start > end
181+
{start: 1, end: 2, index: 0, wantErr: true}, // index out of bounds left
182+
{start: 0, end: 2, index: 3, wantErr: true}, // index out of bounds right
183+
{start: 3, end: 5, index: 3, wantErr: true}, // start not multiple of bit_ceil(len)
184+
185+
// Small trees.
186+
{start: 0, end: 1, index: 0, want: Nodes{IDs: []compact.NodeID{}}},
187+
{start: 0, end: 2, index: 0, want: nodes(id(0, 1))}, // b
188+
{start: 0, end: 2, index: 1, want: nodes(id(0, 0))}, // a
189+
{start: 0, end: 3, index: 1, want: rehash(1, 2, id(0, 0), id(0, 2))}, // a c
190+
191+
// Small subtrees.
192+
// Small tree shifted by bit_ceil(len).
193+
{start: 1, end: 2, index: 1, want: Nodes{IDs: []compact.NodeID{}}},
194+
{start: 2, end: 3, index: 2, want: Nodes{IDs: []compact.NodeID{}}},
195+
{start: 7, end: 8, index: 7, want: Nodes{IDs: []compact.NodeID{}}},
196+
{start: 2, end: 4, index: 2, want: nodes(id(0, 3))}, // d
197+
{start: 2, end: 4, index: 3, want: nodes(id(0, 2))}, // c
198+
{start: 4, end: 7, index: 4, want: rehash(1, 2,
199+
id(0, 5), id(0, 6))}, // f, j
200+
{start: 4, end: 7, index: 6, want: nodes(id(1, 2))}, // i
201+
202+
// Tree of size 7.
203+
{start: 0, end: 7, index: 0, want: rehash(2, 4, // bbb=hash(cc,g)
204+
id(0, 1), id(1, 1), id(0, 6), id(1, 2))}, // b bb g cc
205+
{start: 0, end: 7, index: 1, want: rehash(2, 4, // bbb=hash(cc,g)
206+
id(0, 0), id(1, 1), id(0, 6), id(1, 2))}, // a bb g cc
207+
{start: 0, end: 7, index: 2, want: rehash(2, 4, // bbb=hash(cc,g)
208+
id(0, 3), id(1, 0), id(0, 6), id(1, 2))}, // d aa g cc
209+
{start: 0, end: 7, index: 3, want: rehash(2, 4, // bbb=hash(cc,g)
210+
id(0, 2), id(1, 0), id(0, 6), id(1, 2))}, // c aa g cc
211+
{start: 0, end: 7, index: 4, want: rehash(1, 2,
212+
id(0, 5), id(0, 6), id(2, 0))}, // f g aaa
213+
{start: 0, end: 7, index: 5, want: rehash(1, 2,
214+
id(0, 4), id(0, 6), id(2, 0))}, // e g aaa
215+
{start: 0, end: 7, index: 6, want: nodes(id(1, 2), id(2, 0))}, // i k
216+
217+
// Subtree of size 7.
218+
// Tree of size 7 shifted by bit_ceil(len).
219+
{start: 8, end: 15, index: 8, want: rehash(2, 4, // ddd=hash(gg,o)
220+
id(0, 9), id(1, 5), id(0, 14), id(1, 6))}, // j ff o gg
221+
{start: 8, end: 15, index: 9, want: rehash(2, 4, // ddd=hash(gg,o)
222+
id(0, 8), id(1, 5), id(0, 14), id(1, 6))}, // j ff o gg
223+
{start: 8, end: 15, index: 10, want: rehash(2, 4, // ddd=hash(gg,o)
224+
id(0, 11), id(1, 4), id(0, 14), id(1, 6))}, // l ee o gg
225+
{start: 8, end: 15, index: 11, want: rehash(2, 4, // ddd=hash(gg, o)
226+
id(0, 10), id(1, 4), id(0, 14), id(1, 6))}, // k ee o gg
227+
{start: 8, end: 15, index: 12, want: rehash(1, 2,
228+
id(0, 13), id(0, 14), id(2, 2))}, // n o ccc
229+
{start: 8, end: 15, index: 13, want: rehash(1, 2,
230+
id(0, 12), id(0, 14), id(2, 2))}, // m o ccc
231+
{start: 8, end: 15, index: 14, want: nodes(id(1, 6), id(2, 2))}, // gg ccc
232+
233+
// Smaller trees within a bigger stored tree.
234+
// start = 0
235+
{start: 0, end: 4, index: 2, want: nodes(id(0, 3), id(1, 0))}, // d aa
236+
{start: 0, end: 5, index: 3, want: rehash(2, 3, id(0, 2), id(1, 0), id(0, 4))}, // c aa e
237+
{start: 0, end: 6, index: 3, want: rehash(2, 3, id(0, 2), id(1, 0), id(1, 2))}, // c aa i
238+
{start: 0, end: 6, index: 4, want: nodes(id(0, 5), id(2, 0))}, // f aaa
239+
{start: 0, end: 7, index: 1, want: rehash(2, 4, // bbb=hash(cc,g)
240+
id(0, 0), id(1, 1), id(0, 6), id(1, 2))}, // a bb g cc
241+
{start: 0, end: 7, index: 3, want: rehash(2, 4, // bbb=hash(cc,g)
242+
id(0, 2), id(1, 0), id(0, 6), id(1, 2))}, // c aa g cc
243+
// Shifted by bit_ceil(len).
244+
{start: 4, end: 8, index: 6, want: nodes(id(0, 7), id(1, 2))}, // h cc
245+
{start: 8, end: 13, index: 11, want: rehash(2, 3, id(0, 10), id(1, 4), id(0, 12))}, // k ee m
246+
{start: 8, end: 14, index: 11, want: rehash(2, 3, id(0, 10), id(1, 4), id(1, 6))}, // k, ee, gg
247+
{start: 8, end: 14, index: 12, want: nodes(id(0, 13), id(2, 2))}, // n ccc
248+
{start: 8, end: 15, index: 9, want: rehash(2, 4, // ddd=hash(gg,o)
249+
id(0, 8), id(1, 5), id(0, 14), id(1, 6))}, // i ff o gg
250+
{start: 8, end: 15, index: 11, want: rehash(2, 4, // bbb=hash(cc,g)
251+
id(0, 10), id(1, 4), id(0, 14), id(1, 6))}, // k ff q gg
252+
253+
// Some rehashes in the middle of the returned list.
254+
{start: 0, end: 15, index: 10, want: rehash(2, 4,
255+
id(0, 11), id(1, 4),
256+
id(0, 14), id(1, 6),
257+
id(3, 0),
258+
)},
259+
{start: 16, end: 31, index: 26, want: rehash(2, 4,
260+
id(0, 27), id(1, 12),
261+
id(0, 30), id(1, 14),
262+
id(3, 2),
263+
)},
264+
{start: 0, end: 31, index: 24, want: rehash(2, 4,
265+
id(0, 25), id(1, 13),
266+
id(0, 30), id(1, 14),
267+
id(3, 2), id(4, 0),
268+
)},
269+
{start: 32, end: 63, index: 56, want: rehash(2, 4,
270+
id(0, 57), id(1, 29),
271+
id(0, 62), id(1, 30),
272+
id(3, 6), id(4, 2),
273+
)},
274+
{start: 0, end: 95, index: 81, want: rehash(3, 6,
275+
id(0, 80), id(1, 41), id(2, 21),
276+
id(0, 94), id(1, 46), id(2, 22),
277+
id(4, 4), id(6, 0),
278+
)},
279+
{start: 128, end: 223, index: 209, want: rehash(3, 6,
280+
id(0, 208), id(1, 105), id(2, 53),
281+
id(0, 222), id(1, 110), id(2, 54),
282+
id(4, 12), id(6, 2),
283+
)},
284+
} {
285+
t.Run(fmt.Sprintf("%d:%d:%d", tc.start, tc.end, tc.index), func(t *testing.T) {
286+
proof, err := SubtreeInclusion(tc.index, tc.start, tc.end)
287+
if tc.wantErr {
288+
if err == nil {
289+
t.Fatal("accepted bad params")
290+
}
291+
return
292+
} else if err != nil {
293+
t.Fatalf("Inclusion: %v", err)
294+
}
295+
// Ignore the ephemeral node, it is tested separately.
296+
proof.ephem = compact.NodeID{}
297+
if diff := cmp.Diff(tc.want, proof, cmp.AllowUnexported(Nodes{})); diff != "" {
298+
t.Errorf("paths mismatch:\n%v", diff)
299+
}
300+
})
301+
}
302+
}
303+
133304
// TestConsistency contains consistency proof tests. For reference, consider
134305
// the following example:
135306
//
@@ -248,6 +419,19 @@ func TestInclusionSucceedsUpToTreeSize(t *testing.T) {
248419
}
249420
}
250421

422+
func TestInclusionSubtreeSucceedsUpToTreeSize(t *testing.T) {
423+
const maxSize = uint64(555)
424+
for sbe := uint64(1); sbe <= maxSize; sbe++ {
425+
for sbs := uint64(1); sbs < sbe; sbs++ {
426+
for i := sbe; i < sbe; i++ {
427+
if _, err := SubtreeInclusion(i, sbs, sbe); err != nil {
428+
t.Errorf("SubtreeInclusion(ts:%d, i:%d) = %v", sbe, i, err)
429+
}
430+
}
431+
}
432+
}
433+
}
434+
251435
func TestConsistencySucceedsUpToTreeSize(t *testing.T) {
252436
const maxSize = uint64(100)
253437
for s1 := uint64(1); s1 < maxSize; s1++ {

0 commit comments

Comments
 (0)