@@ -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+
251435func TestConsistencySucceedsUpToTreeSize (t * testing.T ) {
252436 const maxSize = uint64 (100 )
253437 for s1 := uint64 (1 ); s1 < maxSize ; s1 ++ {
0 commit comments