@@ -253,24 +253,131 @@ pub fn ics23_spec() -> ics23::ProofSpec {
253
253
#[ cfg( test) ]
254
254
mod tests {
255
255
use alloc:: format;
256
- use ics23:: HostFunctionsManager ;
256
+ use ics23:: { commitment_proof :: Proof , HostFunctionsManager , NonExistenceProof } ;
257
257
use proptest:: prelude:: * ;
258
+ use proptest:: strategy:: Strategy ;
258
259
use sha2:: Sha256 ;
259
260
260
261
use super :: * ;
261
262
use crate :: { mock:: MockTreeStore , KeyHash , TransparentHasher , SPARSE_MERKLE_PLACEHOLDER_HASH } ;
262
263
263
- #[ test]
264
- #[ should_panic]
265
- fn test_jmt_ics23_nonexistence_single_empty_key ( ) {
266
- test_jmt_ics23_nonexistence_with_keys ( vec ! [ vec![ ] ] . into_iter ( ) ) ;
267
- }
268
-
269
264
proptest ! {
265
+ #![ proptest_config( ProptestConfig {
266
+ cases: 1000 , .. ProptestConfig :: default ( )
267
+ } ) ]
268
+
270
269
#[ test]
271
- fn test_jmt_ics23_nonexistence( keys: Vec <Vec <u8 >>) {
272
- test_jmt_ics23_nonexistence_with_keys( keys. into_iter( ) . filter( |k| k. len( ) != 0 ) ) ;
273
- }
270
+ /// Assert that the Ics23 bonding path calculations are correct.
271
+ /// To achieve this, the uses a strategy that consists in:
272
+ /// 1. generating a sorted vector of key preimages
273
+ /// 2. instantiating a JMT over a `TransparentHasher`
274
+ ///
275
+ /// The last point allow us to easily test that for a given key
276
+ /// that is *in* the JMT, we can generate two non-existent keys
277
+ /// that are "neighbor" to `k`: (k-1, k+1).
278
+ ///
279
+ /// To recap, we generate a vector of sorted key <k_1, ... k_n>;
280
+ /// then, we iterate over each existing key `k_i` and compute a
281
+ /// tuple of neighbors (`k_i - 1`, `k_i + 1`) which are *not*
282
+ /// in the tree.
283
+ /// Equipped with those nonexisting neighbors, we check for exclusion
284
+ /// in the tree, and specifically assert that the generated proof contains:
285
+ /// 1. the initial key we requested (i.e. `k_i + 1` or `k_i - 1`)
286
+ /// 2. the correct left neighbor (i.e. `k_{i-1}`, or `k_{i+1}`, or none`)
287
+ /// 2. the correct right neighbor (i.e. `k_{i-1}`, or `k_{i+1}`, or none`)
288
+ /// across configurations e.g. bounding path for a leftmost or rightmost subtree.
289
+ /// More context available in #104.
290
+ fn test_ics23_bounding_path_simple( key_seeds in key_strategy( ) ) {
291
+ let mut preimages: Vec <String > = key_seeds. into_iter( ) . filter( |k| * k!=0 ) . map( |k| format!( "{k:032x}" ) ) . collect( ) ;
292
+ preimages. sort( ) ;
293
+ preimages. dedup( ) ;
294
+
295
+ assert!( preimages. len( ) > 0 ) ;
296
+
297
+ let store = MockTreeStore :: default ( ) ;
298
+ let tree = JellyfishMerkleTree :: <_, TransparentHasher >:: new( & store) ;
299
+
300
+ // Our key preimages and key hashes are identical, but we still need to populate
301
+ // the mock store so that ics23 internal queries can resolve.
302
+ for preimage in preimages. iter( ) {
303
+ store. put_key_preimage( KeyHash :: with:: <TransparentHasher >( preimage. clone( ) ) , preimage. clone( ) . as_bytes( ) . to_vec( ) . as_ref( ) ) ;
304
+ }
305
+
306
+ let ( _, write_batch) = tree. put_value_set(
307
+ preimages. iter( ) . enumerate( ) . map( |( i, k) | ( KeyHash :: with:: <TransparentHasher >( k. as_bytes( ) ) , Some ( i. to_be_bytes( ) . to_vec( ) ) ) ) ,
308
+ 1
309
+ ) . unwrap( ) ;
310
+
311
+ store. write_tree_update_batch( write_batch) . expect( "can write to mock storage" ) ;
312
+
313
+ let len_preimages = preimages. len( ) ;
314
+
315
+ for ( idx, existing_key) in preimages. iter( ) . enumerate( ) {
316
+ // For each existing key, we generate two alternative keys that are not
317
+ // in the tree. One that is one bit "ahead" and one that is one bit after.
318
+ // e.g. 0x5 -> 0x4 and 0x6
319
+ let ( smaller_key, bigger_key) = generate_adjacent_keys( existing_key) ;
320
+
321
+ let ( v, proof) = tree. get_with_ics23_proof( smaller_key. as_bytes( ) . to_vec( ) , 1 ) . expect( "can query tree" ) ;
322
+ assert!( v. is_none( ) , "the key should not exist!" ) ;
323
+ let proof = proof. proof. expect( "a proof is present" ) ;
324
+ if let Proof :: Nonexist ( NonExistenceProof { key, left, right } ) = proof {
325
+ // Basic check that we are getting back the key that we queried.
326
+ assert_eq!( key, smaller_key. as_bytes( ) . to_vec( ) ) ;
327
+
328
+ // The expected predecessor to the nonexistent key `k_i - 1` is `k_{i-1}`, unless `i=0`.
329
+ let expected_left_neighbor = if idx == 0 { None } else { preimages. get( idx-1 ) } ;
330
+ // The expected successor to the nonexistent key `k_i - 1` is `k_{i+1}`.
331
+ let expected_right_neighbor = Some ( existing_key) ;
332
+
333
+ let reported_left_neighbor = left. clone( ) . map( |existence_proof| String :: from_utf8_lossy( & existence_proof. key) . into_owned( ) ) ;
334
+ let reported_right_neighbor = right. clone( ) . map( |existence_proof| String :: from_utf8_lossy( & existence_proof. key) . into_owned( ) ) ;
335
+
336
+ assert_eq!( expected_left_neighbor. cloned( ) , reported_left_neighbor) ;
337
+ assert_eq!( expected_right_neighbor. cloned( ) , reported_right_neighbor) ;
338
+ } else {
339
+ unreachable!( "we have assessed that the value is `None`" )
340
+ }
341
+
342
+ let ( v, proof) = tree. get_with_ics23_proof( bigger_key. as_bytes( ) . to_vec( ) , 1 ) . expect( "can query tree" ) ;
343
+ assert!( v. is_none( ) , "the key should not exist!" ) ;
344
+ let proof = proof. proof. expect( "a proof is present" ) ;
345
+ if let Proof :: Nonexist ( NonExistenceProof { key, left, right } ) = proof {
346
+ // Basic check that we are getting back the key that we queried.
347
+ assert_eq!( key, bigger_key. as_bytes( ) . to_vec( ) ) ;
348
+ let reported_left_neighbor = left. clone( ) . map( |existence_proof| String :: from_utf8_lossy( & existence_proof. key) . into_owned( ) ) ;
349
+ let reported_right_neighbor = right. clone( ) . map( |existence_proof| String :: from_utf8_lossy( & existence_proof. key) . into_owned( ) ) ;
350
+
351
+ // The expected predecessor to the nonexistent key `k_i + 1` is `k_{i}`.
352
+ let expected_left_neighbor = Some ( existing_key) ;
353
+ // The expected successor to the nonexistent key `k_i + 1` is `k_{i+1}`.
354
+ let expected_right_neighbor = if idx == len_preimages - 1 { None } else { preimages. get( idx+1 ) } ;
355
+
356
+ assert_eq!( expected_left_neighbor. cloned( ) , reported_left_neighbor) ;
357
+ assert_eq!( expected_right_neighbor. cloned( ) , reported_right_neighbor) ;
358
+ } else {
359
+ unreachable!( "we have assessed that the value is `None`" )
360
+ }
361
+ }
362
+ }
363
+
364
+ #[ test]
365
+ fn test_jmt_ics23_nonexistence( keys: Vec <Vec <u8 >>) {
366
+ test_jmt_ics23_nonexistence_with_keys( keys. into_iter( ) . filter( |k| k. len( ) != 0 ) ) ;
367
+ }
368
+ }
369
+
370
+ fn key_strategy ( ) -> impl Strategy < Value = Vec < u128 > > {
371
+ proptest:: collection:: btree_set ( u64:: MAX as u128 ..=u128:: MAX , 200 )
372
+ . prop_map ( |set| set. into_iter ( ) . collect ( ) )
373
+ }
374
+ fn generate_adjacent_keys ( hex : & String ) -> ( String , String ) {
375
+ let value = u128:: from_str_radix ( hex. as_str ( ) , 16 ) . expect ( "valid hexstring" ) ;
376
+ let prev = value - 1 ;
377
+ let next = value + 1 ;
378
+ let p = format ! ( "{prev:032x}" ) ;
379
+ let n = format ! ( "{next:032x}" ) ;
380
+ ( p, n)
274
381
}
275
382
276
383
fn test_jmt_ics23_nonexistence_with_keys ( keys : impl Iterator < Item = Vec < u8 > > ) {
@@ -387,6 +494,12 @@ mod tests {
387
494
) ) ;
388
495
}
389
496
497
+ #[ test]
498
+ #[ should_panic]
499
+ fn test_jmt_ics23_nonexistence_single_empty_key ( ) {
500
+ test_jmt_ics23_nonexistence_with_keys ( vec ! [ vec![ ] ] . into_iter ( ) ) ;
501
+ }
502
+
390
503
#[ test]
391
504
fn test_jmt_ics23_existence ( ) {
392
505
let db = MockTreeStore :: default ( ) ;
0 commit comments