lightning/onion_message/
dns_resolution.rs

1// This file is Copyright its original authors, visible in version control
2// history.
3//
4// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7// You may not use this file except in accordance with one or both of these
8// licenses.
9
10//! This module defines message handling for DNSSEC proof fetching using [bLIP 32].
11//!
12//! It contains [`DNSResolverMessage`]s as well as a [`DNSResolverMessageHandler`] trait to handle
13//! such messages using an [`OnionMessenger`].
14//!
15//! With the `dnssec` feature enabled, it also contains `OMNameResolver`, which does all the work
16//! required to resolve BIP 353 [`HumanReadableName`]s using [bLIP 32] - sending onion messages to
17//! a DNS resolver, validating the proofs, and ultimately surfacing validated data back to the
18//! caller.
19//!
20//! [bLIP 32]: https://github.com/lightning/blips/blob/master/blip-0032.md
21//! [`OnionMessenger`]: super::messenger::OnionMessenger
22
23#[cfg(feature = "dnssec")]
24use core::str::FromStr;
25#[cfg(feature = "dnssec")]
26use core::sync::atomic::{AtomicUsize, Ordering};
27
28#[cfg(feature = "dnssec")]
29use dnssec_prover::rr::RR;
30#[cfg(feature = "dnssec")]
31use dnssec_prover::ser::parse_rr_stream;
32#[cfg(feature = "dnssec")]
33use dnssec_prover::validation::verify_rr_stream;
34
35use dnssec_prover::rr::Name;
36
37use lightning_types::features::NodeFeatures;
38
39use core::fmt;
40
41use crate::blinded_path::message::DNSResolverContext;
42use crate::io;
43#[cfg(feature = "dnssec")]
44use crate::ln::channelmanager::PaymentId;
45use crate::ln::msgs::DecodeError;
46#[cfg(feature = "dnssec")]
47use crate::offers::offer::Offer;
48use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
49use crate::onion_message::packet::OnionMessageContents;
50use crate::prelude::*;
51#[cfg(feature = "dnssec")]
52use crate::sign::EntropySource;
53#[cfg(feature = "dnssec")]
54use crate::sync::Mutex;
55use crate::util::ser::{Hostname, Readable, ReadableArgs, Writeable, Writer};
56
57/// A handler for an [`OnionMessage`] containing a DNS(SEC) query or a DNSSEC proof
58///
59/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
60pub trait DNSResolverMessageHandler {
61	/// Handle a [`DNSSECQuery`] message.
62	///
63	/// If we provide DNS resolution services to third parties, we should respond with a
64	/// [`DNSSECProof`] message.
65	fn handle_dnssec_query(
66		&self, message: DNSSECQuery, responder: Option<Responder>,
67	) -> Option<(DNSResolverMessage, ResponseInstruction)>;
68
69	/// Handle a [`DNSSECProof`] message (in response to a [`DNSSECQuery`] we presumably sent).
70	///
71	/// The provided [`DNSResolverContext`] was authenticated by the [`OnionMessenger`] as coming from
72	/// a blinded path that we created.
73	///
74	/// With this, we should be able to validate the DNS record we requested.
75	///
76	/// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger
77	fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext);
78
79	/// Gets the node feature flags which this handler itself supports. Useful for setting the
80	/// `dns_resolver` flag if this handler supports returning [`DNSSECProof`] messages in response
81	/// to [`DNSSECQuery`] messages.
82	fn provided_node_features(&self) -> NodeFeatures {
83		NodeFeatures::empty()
84	}
85
86	/// Release any [`DNSResolverMessage`]s that need to be sent.
87	fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
88		vec![]
89	}
90}
91
92#[derive(Clone, Debug, Hash, PartialEq, Eq)]
93/// An enum containing the possible onion messages which are used uses to request and receive
94/// DNSSEC proofs.
95pub enum DNSResolverMessage {
96	/// A query requesting a DNSSEC proof
97	DNSSECQuery(DNSSECQuery),
98	/// A response containing a DNSSEC proof
99	DNSSECProof(DNSSECProof),
100}
101
102const DNSSEC_QUERY_TYPE: u64 = 65536;
103const DNSSEC_PROOF_TYPE: u64 = 65538;
104
105#[derive(Clone, Debug, Hash, PartialEq, Eq)]
106/// A message which is sent to a DNSSEC prover requesting a DNSSEC proof for the given name.
107pub struct DNSSECQuery(pub Name);
108
109#[derive(Clone, Debug, Hash, PartialEq, Eq)]
110/// A message which is sent in response to [`DNSSECQuery`] containing a DNSSEC proof.
111pub struct DNSSECProof {
112	/// The name which the query was for. The proof may not contain a DNS RR for exactly this name
113	/// if it contains a wildcard RR which contains this name instead.
114	pub name: Name,
115	/// An [RFC 9102 DNSSEC AuthenticationChain] providing a DNSSEC proof.
116	///
117	/// [RFC 9102 DNSSEC AuthenticationChain]: https://www.rfc-editor.org/rfc/rfc9102.html#name-dnssec-authentication-chain
118	pub proof: Vec<u8>,
119}
120
121impl DNSResolverMessage {
122	/// Returns whether `tlv_type` corresponds to a TLV record for DNS Resolvers.
123	pub fn is_known_type(tlv_type: u64) -> bool {
124		match tlv_type {
125			DNSSEC_QUERY_TYPE | DNSSEC_PROOF_TYPE => true,
126			_ => false,
127		}
128	}
129}
130
131impl Writeable for DNSResolverMessage {
132	fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
133		match self {
134			Self::DNSSECQuery(DNSSECQuery(q)) => {
135				(q.as_str().len() as u8).write(w)?;
136				w.write_all(&q.as_str().as_bytes())
137			},
138			Self::DNSSECProof(DNSSECProof { name, proof }) => {
139				(name.as_str().len() as u8).write(w)?;
140				w.write_all(&name.as_str().as_bytes())?;
141				proof.write(w)
142			},
143		}
144	}
145}
146
147impl ReadableArgs<u64> for DNSResolverMessage {
148	fn read<R: io::Read>(r: &mut R, message_type: u64) -> Result<Self, DecodeError> {
149		match message_type {
150			DNSSEC_QUERY_TYPE => {
151				let s = Hostname::read(r)?;
152				let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
153				Ok(DNSResolverMessage::DNSSECQuery(DNSSECQuery(name)))
154			},
155			DNSSEC_PROOF_TYPE => {
156				let s = Hostname::read(r)?;
157				let name = s.try_into().map_err(|_| DecodeError::InvalidValue)?;
158				let proof = Readable::read(r)?;
159				Ok(DNSResolverMessage::DNSSECProof(DNSSECProof { name, proof }))
160			},
161			_ => Err(DecodeError::InvalidValue),
162		}
163	}
164}
165
166impl OnionMessageContents for DNSResolverMessage {
167	#[cfg(c_bindings)]
168	fn msg_type(&self) -> String {
169		match self {
170			DNSResolverMessage::DNSSECQuery(_) => "DNS(SEC) Query".to_string(),
171			DNSResolverMessage::DNSSECProof(_) => "DNSSEC Proof".to_string(),
172		}
173	}
174	#[cfg(not(c_bindings))]
175	fn msg_type(&self) -> &'static str {
176		match self {
177			DNSResolverMessage::DNSSECQuery(_) => "DNS(SEC) Query",
178			DNSResolverMessage::DNSSECProof(_) => "DNSSEC Proof",
179		}
180	}
181	fn tlv_type(&self) -> u64 {
182		match self {
183			DNSResolverMessage::DNSSECQuery(_) => DNSSEC_QUERY_TYPE,
184			DNSResolverMessage::DNSSECProof(_) => DNSSEC_PROOF_TYPE,
185		}
186	}
187}
188
189// Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
190const REQUIRED_EXTRA_LEN: usize = ".user._bitcoin-payment.".len() + 1;
191
192/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
193///
194/// The `user` and `domain` parts, together, cannot exceed 231 bytes in length, and both must be
195/// non-empty.
196///
197/// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
198/// and do punycode en-/de-coding yourself. This struct will always handle only plain ASCII `user`
199/// and `domain` parts.
200///
201/// This struct can also be used for LN-Address recipients.
202///
203/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
204#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
205pub struct HumanReadableName {
206	contents: [u8; 255 - REQUIRED_EXTRA_LEN],
207	user_len: u8,
208	domain_len: u8,
209}
210
211impl HumanReadableName {
212	/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
213	/// struct-level documentation for more on the requirements on each.
214	pub fn new(user: &str, mut domain: &str) -> Result<HumanReadableName, ()> {
215		// First normalize domain and remove the optional trailing `.`
216		if domain.ends_with('.') {
217			domain = &domain[..domain.len() - 1];
218		}
219		if user.len() + domain.len() + REQUIRED_EXTRA_LEN > 255 {
220			return Err(());
221		}
222		if user.is_empty() || domain.is_empty() {
223			return Err(());
224		}
225		if !Hostname::str_is_valid_hostname(&user) || !Hostname::str_is_valid_hostname(&domain) {
226			return Err(());
227		}
228		let mut contents = [0; 255 - REQUIRED_EXTRA_LEN];
229		contents[..user.len()].copy_from_slice(user.as_bytes());
230		contents[user.len()..user.len() + domain.len()].copy_from_slice(domain.as_bytes());
231		Ok(HumanReadableName {
232			contents,
233			user_len: user.len() as u8,
234			domain_len: domain.len() as u8,
235		})
236	}
237
238	/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
239	///
240	/// If `user` includes the standard BIP 353 ₿ prefix it is automatically removed as required by
241	/// BIP 353.
242	pub fn from_encoded(encoded: &str) -> Result<HumanReadableName, ()> {
243		if let Some((user, domain)) = encoded.strip_prefix('₿').unwrap_or(encoded).split_once("@")
244		{
245			Self::new(user, domain)
246		} else {
247			Err(())
248		}
249	}
250
251	/// Gets the `user` part of this Human Readable Name
252	pub fn user(&self) -> &str {
253		let bytes = &self.contents[..self.user_len as usize];
254		core::str::from_utf8(bytes).expect("Checked in constructor")
255	}
256
257	/// Gets the `domain` part of this Human Readable Name
258	pub fn domain(&self) -> &str {
259		let user_len = self.user_len as usize;
260		let bytes = &self.contents[user_len..user_len + self.domain_len as usize];
261		core::str::from_utf8(bytes).expect("Checked in constructor")
262	}
263}
264
265// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
266impl Writeable for HumanReadableName {
267	fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
268		(self.user().len() as u8).write(writer)?;
269		writer.write_all(&self.user().as_bytes())?;
270		(self.domain().len() as u8).write(writer)?;
271		writer.write_all(&self.domain().as_bytes())
272	}
273}
274
275impl Readable for HumanReadableName {
276	fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
277		let mut user_bytes = [0; 255];
278		let user_len: u8 = Readable::read(reader)?;
279		reader.read_exact(&mut user_bytes[..user_len as usize])?;
280		let user = match core::str::from_utf8(&user_bytes[..user_len as usize]) {
281			Ok(user) => user,
282			Err(_) => return Err(DecodeError::InvalidValue),
283		};
284
285		let mut domain_bytes = [0; 255];
286		let domain_len: u8 = Readable::read(reader)?;
287		reader.read_exact(&mut domain_bytes[..domain_len as usize])?;
288		let domain = match core::str::from_utf8(&domain_bytes[..domain_len as usize]) {
289			Ok(domain) => domain,
290			Err(_) => return Err(DecodeError::InvalidValue),
291		};
292
293		HumanReadableName::new(user, domain).map_err(|()| DecodeError::InvalidValue)
294	}
295}
296
297impl fmt::Display for HumanReadableName {
298	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299		write!(f, "₿{}@{}", self.user(), self.domain())
300	}
301}
302
303#[cfg(feature = "dnssec")]
304struct PendingResolution {
305	start_height: u32,
306	context: DNSResolverContext,
307	name: HumanReadableName,
308	payment_id: PaymentId,
309}
310
311/// A stateful resolver which maps BIP 353 Human Readable Names to URIs and BOLT12 [`Offer`]s.
312///
313/// It does not directly implement [`DNSResolverMessageHandler`] but implements all the core logic
314/// which is required in a client which intends to.
315///
316/// It relies on being made aware of the passage of time with regular calls to
317/// [`Self::new_best_block`] in order to time out existing queries. Queries time out after two
318/// blocks.
319#[cfg(feature = "dnssec")]
320pub struct OMNameResolver {
321	pending_resolves: Mutex<HashMap<Name, Vec<PendingResolution>>>,
322	latest_block_time: AtomicUsize,
323	latest_block_height: AtomicUsize,
324}
325
326#[cfg(feature = "dnssec")]
327impl OMNameResolver {
328	/// Builds a new [`OMNameResolver`].
329	pub fn new(latest_block_time: u32, latest_block_height: u32) -> Self {
330		Self {
331			pending_resolves: Mutex::new(new_hash_map()),
332			latest_block_time: AtomicUsize::new(latest_block_time as usize),
333			latest_block_height: AtomicUsize::new(latest_block_height as usize),
334		}
335	}
336
337	/// Builds a new [`OMNameResolver`] which will not validate the time limits on DNSSEC proofs
338	/// (for builds without the "std" feature and until [`Self::new_best_block`] is called).
339	///
340	/// If possible, you should prefer [`Self::new`] so that providing stale proofs is not
341	/// possible, however in no-std environments where there is some trust in the resolver used and
342	/// no time source is available, this may be acceptable.
343	///
344	/// Note that not calling [`Self::new_best_block`] will result in requests not timing out and
345	/// unresolved requests leaking memory. You must instead call
346	/// [`Self::expire_pending_resolution`] as unresolved requests expire.
347	pub fn new_without_no_std_expiry_validation() -> Self {
348		Self {
349			pending_resolves: Mutex::new(new_hash_map()),
350			latest_block_time: AtomicUsize::new(0),
351			latest_block_height: AtomicUsize::new(0),
352		}
353	}
354
355	/// Informs the [`OMNameResolver`] of the passage of time in the form of a new best Bitcoin
356	/// block.
357	///
358	/// This is used to prune stale requests (by block height) and keep track of the current time
359	/// to validate that DNSSEC proofs are current.
360	pub fn new_best_block(&self, height: u32, time: u32) {
361		self.latest_block_time.store(time as usize, Ordering::Release);
362		self.latest_block_height.store(height as usize, Ordering::Release);
363		let mut resolves = self.pending_resolves.lock().unwrap();
364		resolves.retain(|_, queries| {
365			queries.retain(|query| query.start_height >= height - 1);
366			!queries.is_empty()
367		});
368	}
369
370	/// Removes any pending resolutions for the given `name` and `payment_id`.
371	///
372	/// Any future calls to [`Self::handle_dnssec_proof_for_offer`] or
373	/// [`Self::handle_dnssec_proof_for_uri`] will no longer return a result for the given
374	/// resolution.
375	pub fn expire_pending_resolution(&self, name: &HumanReadableName, payment_id: PaymentId) {
376		let dns_name =
377			Name::try_from(format!("{}.user._bitcoin-payment.{}.", name.user(), name.domain()));
378		debug_assert!(
379			dns_name.is_ok(),
380			"The HumanReadableName constructor shouldn't allow names which are too long"
381		);
382		if let Ok(name) = dns_name {
383			let mut pending_resolves = self.pending_resolves.lock().unwrap();
384			if let hash_map::Entry::Occupied(mut entry) = pending_resolves.entry(name) {
385				let resolutions = entry.get_mut();
386				resolutions.retain(|resolution| resolution.payment_id != payment_id);
387				if resolutions.is_empty() {
388					entry.remove();
389				}
390			}
391		}
392	}
393
394	/// Begins the process of resolving a BIP 353 Human Readable Name.
395	///
396	/// Returns a [`DNSSECQuery`] onion message and a [`DNSResolverContext`] which should be sent
397	/// to a resolver (with the context used to generate the blinded response path) on success.
398	pub fn resolve_name<ES: EntropySource + ?Sized>(
399		&self, payment_id: PaymentId, name: HumanReadableName, entropy_source: &ES,
400	) -> Result<(DNSSECQuery, DNSResolverContext), ()> {
401		let dns_name =
402			Name::try_from(format!("{}.user._bitcoin-payment.{}.", name.user(), name.domain()));
403		debug_assert!(
404			dns_name.is_ok(),
405			"The HumanReadableName constructor shouldn't allow names which are too long"
406		);
407		let mut context = DNSResolverContext { nonce: [0; 16] };
408		context.nonce.copy_from_slice(&entropy_source.get_secure_random_bytes()[..16]);
409		if let Ok(dns_name) = dns_name {
410			let start_height = self.latest_block_height.load(Ordering::Acquire) as u32;
411			let mut pending_resolves = self.pending_resolves.lock().unwrap();
412			let context_ret = context.clone();
413			let resolution = PendingResolution { start_height, context, name, payment_id };
414			pending_resolves.entry(dns_name.clone()).or_insert_with(Vec::new).push(resolution);
415			Ok((DNSSECQuery(dns_name), context_ret))
416		} else {
417			Err(())
418		}
419	}
420
421	/// Handles a [`DNSSECProof`] message, attempting to verify it and match it against a pending
422	/// query.
423	///
424	/// If verification succeeds, the resulting bitcoin: URI is parsed to find a contained
425	/// [`Offer`].
426	///
427	/// Note that a single proof for a wildcard DNS entry may complete several requests for
428	/// different [`HumanReadableName`]s.
429	///
430	/// If an [`Offer`] is found, it, as well as the [`PaymentId`] and original `name` passed to
431	/// [`Self::resolve_name`] are returned.
432	pub fn handle_dnssec_proof_for_offer(
433		&self, msg: DNSSECProof, context: DNSResolverContext,
434	) -> Option<(Vec<(HumanReadableName, PaymentId)>, Offer)> {
435		let (completed_requests, uri) = self.handle_dnssec_proof_for_uri(msg, context)?;
436		if let Some((_onchain, params)) = uri.split_once("?") {
437			for param in params.split("&") {
438				let (k, v) = if let Some(split) = param.split_once("=") {
439					split
440				} else {
441					continue;
442				};
443				if k.eq_ignore_ascii_case("lno") {
444					if let Ok(offer) = Offer::from_str(v) {
445						return Some((completed_requests, offer));
446					}
447					return None;
448				}
449			}
450		}
451		None
452	}
453
454	/// Handles a [`DNSSECProof`] message, attempting to verify it and match it against any pending
455	/// queries.
456	///
457	/// If verification succeeds, all matching [`PaymentId`] and [`HumanReadableName`]s passed to
458	/// [`Self::resolve_name`], as well as the resolved bitcoin: URI are returned.
459	///
460	/// Note that a single proof for a wildcard DNS entry may complete several requests for
461	/// different [`HumanReadableName`]s.
462	///
463	/// This method is useful for those who handle bitcoin: URIs already, handling more than just
464	/// BOLT12 [`Offer`]s.
465	pub fn handle_dnssec_proof_for_uri(
466		&self, msg: DNSSECProof, context: DNSResolverContext,
467	) -> Option<(Vec<(HumanReadableName, PaymentId)>, String)> {
468		let DNSSECProof { name: answer_name, proof } = msg;
469		let mut pending_resolves = self.pending_resolves.lock().unwrap();
470		if let hash_map::Entry::Occupied(entry) = pending_resolves.entry(answer_name) {
471			if !entry.get().iter().any(|query| query.context == context) {
472				// If we don't have any pending queries with the context included in the blinded
473				// path (implying someone sent us this response not using the blinded path we gave
474				// when making the query), return immediately to avoid the extra time for the proof
475				// validation giving away that we were the node that made the query.
476				//
477				// If there was at least one query with the same context, we go ahead and complete
478				// all queries for the same name, as there's no point in waiting for another proof
479				// for the same name.
480				return None;
481			}
482			let parsed_rrs = parse_rr_stream(&proof);
483			let validated_rrs =
484				parsed_rrs.as_ref().and_then(|rrs| verify_rr_stream(rrs).map_err(|_| &()));
485			if let Ok(validated_rrs) = validated_rrs {
486				#[allow(unused_assignments, unused_mut)]
487				let mut time = self.latest_block_time.load(Ordering::Acquire) as u64;
488				#[cfg(feature = "std")]
489				{
490					use std::time::{SystemTime, UNIX_EPOCH};
491					let now = SystemTime::now().duration_since(UNIX_EPOCH);
492					time = now.expect("Time must be > 1970").as_secs();
493				}
494				if time != 0 {
495					// Block times may be up to two hours in the future and some time into the past
496					// (we assume no more than two hours, though the actual limits are rather
497					// complicated).
498					// Thus, we have to let the proof times be rather fuzzy.
499					let max_time_offset = if cfg!(feature = "std") { 0 } else { 60 * 2 };
500					if validated_rrs.valid_from > time + max_time_offset {
501						return None;
502					}
503					if validated_rrs.expires < time - max_time_offset {
504						return None;
505					}
506				}
507				let resolved_rrs = validated_rrs.resolve_name(&entry.key());
508				if resolved_rrs.is_empty() {
509					return None;
510				}
511
512				let (_, requests) = entry.remove_entry();
513
514				const URI_PREFIX: &str = "bitcoin:";
515				let mut candidate_records = resolved_rrs
516					.iter()
517					.filter_map(
518						|rr| if let RR::Txt(txt) = rr { Some(txt.data.as_vec()) } else { None },
519					)
520					.filter_map(|data| String::from_utf8(data).ok())
521					.filter(|data_string| data_string.len() > URI_PREFIX.len())
522					.filter(|data_string| {
523						data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
524					});
525				// Check that there is exactly one TXT record that begins with
526				// bitcoin: as required by BIP 353 (and is valid UTF-8).
527				match (candidate_records.next(), candidate_records.next()) {
528					(Some(txt), None) => {
529						let completed_requests =
530							requests.into_iter().map(|r| (r.name, r.payment_id)).collect();
531						return Some((completed_requests, txt));
532					},
533					_ => {},
534				}
535			}
536		}
537		None
538	}
539}
540
541#[cfg(test)]
542mod tests {
543	use super::*;
544
545	#[test]
546	fn test_hrn_display_format() {
547		let user = "user";
548		let domain = "example.com";
549		let hrn = HumanReadableName::new(user, domain)
550			.expect("Failed to create HumanReadableName for user");
551
552		// Assert that the formatted string matches the expected output
553		let expected_display = format!("₿{}@{}", user, domain);
554		assert_eq!(
555			format!("{}", hrn),
556			expected_display,
557			"HumanReadableName display format mismatch"
558		);
559	}
560
561	#[test]
562	#[cfg(feature = "dnssec")]
563	fn test_expiry() {
564		let keys = crate::sign::KeysManager::new(&[33; 32], 0, 0, true);
565		let resolver = OMNameResolver::new(42, 42);
566		let name = HumanReadableName::new("user", "example.com").unwrap();
567
568		// Queue up a resolution
569		resolver.resolve_name(PaymentId([0; 32]), name.clone(), &keys).unwrap();
570		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
571		// and check that it expires after two blocks
572		resolver.new_best_block(44, 42);
573		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 0);
574
575		// Queue up another resolution
576		resolver.resolve_name(PaymentId([1; 32]), name.clone(), &keys).unwrap();
577		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
578		// it won't expire after one block
579		resolver.new_best_block(45, 42);
580		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
581		assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 1);
582		// and queue up a second and third resolution of the same name
583		resolver.resolve_name(PaymentId([2; 32]), name.clone(), &keys).unwrap();
584		resolver.resolve_name(PaymentId([3; 32]), name.clone(), &keys).unwrap();
585		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
586		assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 3);
587		// after another block the first will expire, but the second and third won't
588		resolver.new_best_block(46, 42);
589		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
590		assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 2);
591		// Check manual expiry
592		resolver.expire_pending_resolution(&name, PaymentId([3; 32]));
593		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 1);
594		assert_eq!(resolver.pending_resolves.lock().unwrap().iter().next().unwrap().1.len(), 1);
595		// after one more block all the requests will have expired
596		resolver.new_best_block(47, 42);
597		assert_eq!(resolver.pending_resolves.lock().unwrap().len(), 0);
598	}
599}