Skip to content

Commit 257ebad

Browse files
authored
Merge pull request #3900 from TheBlueMatt/2025-06-bad-docs
Correct docs and marginally expand `OMNameResolver`
2 parents 22c3433 + 1855184 commit 257ebad

File tree

1 file changed

+103
-11
lines changed

1 file changed

+103
-11
lines changed

lightning/src/onion_message/dns_resolution.rs

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
484537
mod 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

Comments
 (0)