@@ -329,10 +329,29 @@ impl OMNameResolver {
329
329
}
330
330
}
331
331
332
+ /// Builds a new [`OMNameResolver`] which will not validate the time limits on DNSSEC proofs
333
+ /// (for builds without the "std" feature and until [`Self::new_best_block`] is called).
334
+ ///
335
+ /// If possible, you should prefer [`Self::new`] so that providing stale proofs is not
336
+ /// possible, however in no-std environments where there is some trust in the resolver used and
337
+ /// no time source is available, this may be acceptable.
338
+ ///
339
+ /// Note that not calling [`Self::new_best_block`] will result in requests not timing out and
340
+ /// unresolved requests leaking memory. You must instead call
341
+ /// [`Self::expire_pending_resolution`] as unresolved requests expire.
342
+ pub fn new_without_no_std_expiry_validation ( ) -> Self {
343
+ Self {
344
+ pending_resolves : Mutex :: new ( new_hash_map ( ) ) ,
345
+ latest_block_time : AtomicUsize :: new ( 0 ) ,
346
+ latest_block_height : AtomicUsize :: new ( 0 ) ,
347
+ }
348
+ }
349
+
332
350
/// Informs the [`OMNameResolver`] of the passage of time in the form of a new best Bitcoin
333
351
/// block.
334
352
///
335
- /// This will call back to resolve some pending queries which have timed out.
353
+ /// This is used to prune stale requests (by block height) and keep track of the current time
354
+ /// to validate that DNSSEC proofs are current.
336
355
pub fn new_best_block ( & self , height : u32 , time : u32 ) {
337
356
self . latest_block_time . store ( time as usize , Ordering :: Release ) ;
338
357
self . latest_block_height . store ( height as usize , Ordering :: Release ) ;
@@ -343,6 +362,30 @@ impl OMNameResolver {
343
362
} ) ;
344
363
}
345
364
365
+ /// Removes any pending resolutions for the given `name` and `payment_id`.
366
+ ///
367
+ /// Any future calls to [`Self::handle_dnssec_proof_for_offer`] or
368
+ /// [`Self::handle_dnssec_proof_for_uri`] will no longer return a result for the given
369
+ /// resolution.
370
+ pub fn expire_pending_resolution ( & self , name : & HumanReadableName , payment_id : PaymentId ) {
371
+ let dns_name =
372
+ Name :: try_from ( format ! ( "{}.user._bitcoin-payment.{}." , name. user( ) , name. domain( ) ) ) ;
373
+ debug_assert ! (
374
+ dns_name. is_ok( ) ,
375
+ "The HumanReadableName constructor shouldn't allow names which are too long"
376
+ ) ;
377
+ if let Ok ( name) = dns_name {
378
+ let mut pending_resolves = self . pending_resolves . lock ( ) . unwrap ( ) ;
379
+ if let hash_map:: Entry :: Occupied ( mut entry) = pending_resolves. entry ( name) {
380
+ let resolutions = entry. get_mut ( ) ;
381
+ resolutions. retain ( |resolution| resolution. payment_id != payment_id) ;
382
+ if resolutions. is_empty ( ) {
383
+ entry. remove ( ) ;
384
+ }
385
+ }
386
+ }
387
+ }
388
+
346
389
/// Begins the process of resolving a BIP 353 Human Readable Name.
347
390
///
348
391
/// Returns a [`DNSSECQuery`] onion message and a [`DNSResolverContext`] which should be sent
@@ -435,16 +478,26 @@ impl OMNameResolver {
435
478
let validated_rrs =
436
479
parsed_rrs. as_ref ( ) . and_then ( |rrs| verify_rr_stream ( rrs) . map_err ( |_| & ( ) ) ) ;
437
480
if let Ok ( validated_rrs) = validated_rrs {
438
- let block_time = self . latest_block_time . load ( Ordering :: Acquire ) as u64 ;
439
- // Block times may be up to two hours in the future and some time into the past
440
- // (we assume no more than two hours, though the actual limits are rather
441
- // complicated).
442
- // Thus, we have to let the proof times be rather fuzzy.
443
- if validated_rrs . valid_from > block_time + 60 * 2 {
444
- return None ;
481
+ # [ allow ( unused_assignments , unused_mut ) ]
482
+ let mut time = self . latest_block_time . load ( Ordering :: Acquire ) as u64 ;
483
+ # [ cfg ( feature = "std" ) ]
484
+ {
485
+ use std :: time :: { SystemTime , UNIX_EPOCH } ;
486
+ let now = SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) ;
487
+ time = now . expect ( "Time must be > 1970" ) . as_secs ( ) ;
445
488
}
446
- if validated_rrs. expires < block_time - 60 * 2 {
447
- return None ;
489
+ if time != 0 {
490
+ // Block times may be up to two hours in the future and some time into the past
491
+ // (we assume no more than two hours, though the actual limits are rather
492
+ // complicated).
493
+ // Thus, we have to let the proof times be rather fuzzy.
494
+ let max_time_offset = if cfg ! ( feature = "std" ) { 0 } else { 60 * 2 } ;
495
+ if validated_rrs. valid_from > time + max_time_offset {
496
+ return None ;
497
+ }
498
+ if validated_rrs. expires < time - max_time_offset {
499
+ return None ;
500
+ }
448
501
}
449
502
let resolved_rrs = validated_rrs. resolve_name ( & entry. key ( ) ) ;
450
503
if resolved_rrs. is_empty ( ) {
@@ -482,7 +535,7 @@ impl OMNameResolver {
482
535
483
536
#[ cfg( test) ]
484
537
mod tests {
485
- use super :: HumanReadableName ;
538
+ use super :: * ;
486
539
487
540
#[ test]
488
541
fn test_hrn_display_format ( ) {
@@ -499,4 +552,43 @@ mod tests {
499
552
"HumanReadableName display format mismatch"
500
553
) ;
501
554
}
555
+
556
+ #[ test]
557
+ #[ cfg( feature = "dnssec" ) ]
558
+ fn test_expiry ( ) {
559
+ let keys = crate :: sign:: KeysManager :: new ( & [ 33 ; 32 ] , 0 , 0 ) ;
560
+ let resolver = OMNameResolver :: new ( 42 , 42 ) ;
561
+ let name = HumanReadableName :: new ( "user" , "example.com" ) . unwrap ( ) ;
562
+
563
+ // Queue up a resolution
564
+ resolver. resolve_name ( PaymentId ( [ 0 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
565
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
566
+ // and check that it expires after two blocks
567
+ resolver. new_best_block ( 44 , 42 ) ;
568
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 0 ) ;
569
+
570
+ // Queue up another resolution
571
+ resolver. resolve_name ( PaymentId ( [ 1 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
572
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
573
+ // it won't expire after one block
574
+ resolver. new_best_block ( 45 , 42 ) ;
575
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
576
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 1 ) ;
577
+ // and queue up a second and third resolution of the same name
578
+ resolver. resolve_name ( PaymentId ( [ 2 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
579
+ resolver. resolve_name ( PaymentId ( [ 3 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
580
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
581
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 3 ) ;
582
+ // after another block the first will expire, but the second and third won't
583
+ resolver. new_best_block ( 46 , 42 ) ;
584
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
585
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 2 ) ;
586
+ // Check manual expiry
587
+ resolver. expire_pending_resolution ( & name, PaymentId ( [ 3 ; 32 ] ) ) ;
588
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
589
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 1 ) ;
590
+ // after one more block all the requests will have expired
591
+ resolver. new_best_block ( 47 , 42 ) ;
592
+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 0 ) ;
593
+ }
502
594
}
0 commit comments