@@ -329,10 +329,29 @@ impl OMNameResolver {
329329 }
330330 }
331331
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+
332350 /// Informs the [`OMNameResolver`] of the passage of time in the form of a new best Bitcoin
333351 /// block.
334352 ///
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.
336355 pub fn new_best_block ( & self , height : u32 , time : u32 ) {
337356 self . latest_block_time . store ( time as usize , Ordering :: Release ) ;
338357 self . latest_block_height . store ( height as usize , Ordering :: Release ) ;
@@ -343,6 +362,30 @@ impl OMNameResolver {
343362 } ) ;
344363 }
345364
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+
346389 /// Begins the process of resolving a BIP 353 Human Readable Name.
347390 ///
348391 /// Returns a [`DNSSECQuery`] onion message and a [`DNSResolverContext`] which should be sent
@@ -435,16 +478,26 @@ impl OMNameResolver {
435478 let validated_rrs =
436479 parsed_rrs. as_ref ( ) . and_then ( |rrs| verify_rr_stream ( rrs) . map_err ( |_| & ( ) ) ) ;
437480 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 ( ) ;
445488 }
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+ }
448501 }
449502 let resolved_rrs = validated_rrs. resolve_name ( & entry. key ( ) ) ;
450503 if resolved_rrs. is_empty ( ) {
@@ -482,7 +535,7 @@ impl OMNameResolver {
482535
483536#[ cfg( test) ]
484537mod tests {
485- use super :: HumanReadableName ;
538+ use super :: * ;
486539
487540 #[ test]
488541 fn test_hrn_display_format ( ) {
@@ -499,4 +552,43 @@ mod tests {
499552 "HumanReadableName display format mismatch"
500553 ) ;
501554 }
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+ }
502594}
0 commit comments