-
Notifications
You must be signed in to change notification settings - Fork 367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support paying directly to Human Readable Names using bLIP 32 #3283
Support paying directly to Human Readable Names using bLIP 32 #3283
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3283 +/- ##
==========================================
+ Coverage 89.65% 89.67% +0.02%
==========================================
Files 128 129 +1
Lines 104821 105419 +598
Branches 104821 105419 +598
==========================================
+ Hits 93980 94538 +558
- Misses 8130 8138 +8
- Partials 2711 2743 +32 ☔ View full report in Codecov by Sentry. |
b42d6e0
to
d4691a1
Compare
d4691a1
to
9b2ef2d
Compare
93efbec
to
b2cbd26
Compare
b2cbd26
to
dab9c91
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed I had this comment pending without submitting it, sorry.
54789fc
to
ae5d584
Compare
Added a commit to set the feature bit added in #3346. |
ae5d584
to
8ea3cd4
Compare
8ea3cd4
to
6c7b888
Compare
Rebased and addressed CI issues. |
I'm good with squashing the fixups at this point. |
6c7b888
to
c1dfc8c
Compare
Squashed the fixups without further changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit aside, code looks good. I think the most critical portion is in the dnssec_prover
crate anyway, which has already been reviewed.
c1dfc8c
to
5024aa9
Compare
(11, AwaitingOffer) => { | ||
(0, expiration, required), | ||
(2, retry_strategy, required), | ||
(4, max_total_routing_fee_msat, option), | ||
(6, amount_msats, required), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just trying to learn here. I noted this in other places too. There is a specific reason that we use the number with a step of 2? is it probably for upgrade and downgrade?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For new types not really, no. Even/odd means future versions that don't support these fields will fail/ignore the fields, but for new fields it doesn't matter too much since new versions will trivially support them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I left some question/light-suggestion long the way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a first pass, some questions/nits.
match payment.get() { | ||
PendingOutboundPayment::Abandoned { payment_hash, reason, .. } => { | ||
if payment.get().remaining_parts() == 0 { | ||
pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: given that rustfmt
will come around here eventually, might already want to pull out the event into a single variable while we're here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only the indentation here changed (git show -b
is happy) :)
lightning/src/offers/invoice.rs
Outdated
@@ -1613,6 +1613,7 @@ mod tests { | |||
payer_id: Some(&payer_pubkey()), | |||
payer_note: None, | |||
paths: None, | |||
source_human_readable_name: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused: given the context described in the commit message, shouldn't this rather be called target_
or destination_readable_name
? Or maybe I'm misunderstanding what source
is referring to? Isn't this describing which HRN we're trying to pay to?
Or maybe just bip_353_name
, given that lightning/bolts#1180 calls the field invreq_bip_353_name
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm, I was saying source as in "the place I got the offer I'm paying is..." The setter method on the invoicereq is sourced_from_human_readable_name
so I lined the getter up with that. The internal field I'm just calling the same as the public API for convenience. We could rename it as "destination", I suppose, but it does feel strange cause the "destination" is the BOLT 12.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, maybe target
then, or receiver
? (lol, it's always the same naming discussion around discerning immediate and 'final' destinations, always tricky).
Not sure if I'm alone in this but I read source_
as the exact opposite of what it's supposed to mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just from_human_readable_name
? I don't want to imply that this is the target's unique HRN, nor even that the target is aware that this is their HRN.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or derived_from_hrn
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either from_human_readable_name
or derived_from_hrn
would be an improvement from my perspective, also like hrn_offer_derived_from
you proposed elsewhere.
I'll leave it up to you 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went with offer_from_hrn
as it captures the bool-ness of the field, doesn't imply that the offer was somehow "derived" (vs just contained in the hrn) and focuses on the offer, not the struct that the method is being called on.
lightning/src/ln/channelmanager.rs
Outdated
let (onion_message, context) = | ||
self.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?; | ||
let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?; | ||
let expiration = StaleExpiration::TimerTicks(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 2
here? Why not also 1
or 3
or 5
? Also, nit: could you move these to const
s?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2
is just the "minimum" value for any tick-based expiration. 1 doesn't work cause the next tick might be in 1ms, so then we'd fail always. 2 is the next number. I don't think this needs to be configurable/changeable, its just "minimum possible value".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 doesn't work cause the next tick might be in 1ms, so then we'd fail always.
Wait, why do we have this in pay_for_offer
then?:
let expiration = StaleExpiration::TimerTicks(1);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it keeps it for one more tick after 0....honestly its always one tick so I'm gonna remove the parameter and just move the logic to outbound_payment.rs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, it is actually used, anyway, fixed to 1.
const MAX_PENDING_RESPONSES: usize = 1024; | ||
struct OMResolverState { | ||
resolver: SocketAddr, | ||
pending_replies: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use channels for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean like MPSC channels? Not quite sure I understand the suggestion here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry, 'channels' might have been a bit ambiguous here... I was thinking about tokio::sync::oneshot
mostly as they seem the right tool for the job and are also know to work nicely in async contexts/stacks. They'd would also be nice as message passing wouldn't risk introducing any deadlocks when accessing the the message handler (an issue I ran into previously with LiquidityManager
, for example. That said, I guess to go that route properly we'd need to make DNSResolverMessageHandler
async in one form or another, which probably isn't feasible right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean this basically is a one-shot channel, just not one that supports await
ing on it....Given we want to return all the messages back at once when we get a release_pending_messages
call using channels per-query would result in a lot of poll
ing which would be much worse than just a global vec.
5024aa9
to
260fe98
Compare
260fe98
to
cc054cb
Compare
Addressed feedback and rebased. |
5be55b2
to
f5c215e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to squash fixups
payment.remove(); | ||
} | ||
}, | ||
PendingOutboundPayment::AwaitingInvoice { .. } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this: why can't we have mark_abandoned
transition these states to Abandoned
, too?
Seems a bit strange to first not actually mark them abandoned and then here match on the state even though you'd think they'd always be Abandoned
post-mark_abandoned
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use the abandoned
state to keep track of payments which we don't intend to retry but which have some HTLCs pending (implying we cannot yet say Event::PaymentFailed
). We remove the payment once all pending HTLCs have been failed.
lightning/src/offers/invoice.rs
Outdated
@@ -1613,6 +1613,7 @@ mod tests { | |||
payer_id: Some(&payer_pubkey()), | |||
payer_note: None, | |||
paths: None, | |||
source_human_readable_name: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, maybe target
then, or receiver
? (lol, it's always the same naming discussion around discerning immediate and 'final' destinations, always tricky).
Not sure if I'm alone in this but I read source_
as the exact opposite of what it's supposed to mean.
f5c215e
to
31e16f8
Compare
Squashed without further changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, mod the pending name change for source_human_readable_name
.
(See comment below)
/// underlying handler. This is useful when this resolver is handling incoming resolution | ||
/// requests but some other handler is making proof requests of remote nodes and wants to get | ||
/// results. | ||
pub fn with_runtime( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, sorry for bringing this up just now, but thinking about it again it seems that this approach unfortunately won't work for LDK Node as is since it would require that OMDomainResolver
is setup after ChannelManager
but before MessageHandler
that we'd pass to PeerManager
.
However, at the time all this is initialized in LDK Node we don't have the runtime available, which we only setup on Node::start
. I think this could be resolved in two ways: either we'd go the FutureSpawner
approach as mentioned above, which would allow LDK Node to implement as needed based on another wrapper, or we could make this an Arc<RwLock<Option<Runtime>>>
and allowing to set the runtime some time after initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dnssec-resolver
resolving logic relies on tokio, so I don't think we should switch to a generic FutureSpawner
as it gives the (wrong) impression that you can use a different runtime. I'll just store the runtime handle in a mutex...even though that's really gross :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course I assume ldk-node won't actually use this in normal mobile setups, those obviously shouldn't be doing resolutions for others...ldk-node in a server setup tho probably maybe should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, surely it wouldn’t be feasible/necessary for a mobile node, but for any 24/7 LDK Node deployment. So would be great if we found a way to make it work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the latest push, I think it should work for that.
31e16f8
to
c7cd9b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, feel free to squash.
c7cd9b6
to
ce3b143
Compare
Domain names implicitly have a trailing `.`, which we require in bLIP 32 but generally shouldn't be exposing to the user in `HumanReadableName`s (after all, they're human-readable). Here we make sure the trailing `.` is dropped in `HumanReadableName`s before we re-add them when building the bLIP 32 messages.
When we resolve a Human Readable Name to a BOLT 12 `offer`, we may end up resolving to a wildcard DNS name covering all possible `user` parts. In that case, if we just blindly pay the `offer`, the recipient would have no way to tell which `user` we paid. Instead, BOLT 12 defines a field to include the HRN resolved in the `invoice_request`, which we implement here. We also take this opportunity to remove constant parameters from the `outbound_payment.rs` interface to `channelmanager.rs`
When we receive a payment to an offer we issued resolved with a human readable name, it may have been resolved using a wildcard DNS entry which we want to map to a specific recipient account locally. To do this, we need the human readable name from the `InvoiceRequest` in the `PaymentClaim{able,ed}`, which we pipe through here using `InvoiceRequestFields`.
Now that we have the ability to resolve BIP 353 Human Readable Names directly and have tracking for outbound payments waiting on an offer resolution, we can implement full BIP 353 support in `ChannelManager`. Users will need one or more known nodes which offer DNS resolution service over onion messages using bLIP 32, which they pass to `ChannelManager::pay_for_offer_from_human_readable_name`, as well as the `HumanReadableName` itself. From there, `ChannelManager` asks the DNS resolver to provide a DNSSEC proof, which it verifies, parses into an `Offer`, and then pays. For those who wish to support on-chain fallbacks, sadly, this will not work, and they'll still have to use `OMNameResolver` directly in order to use their existing `bitcoin:` URI parsing.
Now that `ChannelManager` supports using bLIP 32 to resolve BIP 353 Human Readable Names we should encourage users to use that feature by making the "default" (in various type aliases) to use `ChannelManager` as the `DNSResolverMessageHandler`.
When a lightning node wishes to send payments to a BIP 353 human readable name (using BOLT 12), it first has to resolve that name to a DNS TXT record. bLIP 32 defines a way to do so over onion messages, and this completes our implementation thereof by adding the server side. It operates by simply accepting new messages and spawning tokio tasks to do DNS lookups using the `dnsse_prover` crate. It also contains full end-to-end tests of the BIP 353 -> BOLT 12 -> payment logic using the new server code to do the resolution. Note that because we now have a workspace crate which sets the "lightning/dnssec" feature in its `dev-dependencies`, a naive `cargo test` will test the "dnssec" feature.
`OMDomainResolver` actually does support building DNSSECProofs, so should be setting the `dns_resolution` `NodeFeature` flag.
Squashed without changes then had to rebase to address a conflict:
|
ce3b143
to
cbdb8cb
Compare
Based on #3179 this fully implements BIP 353 sends using bLIP 32 DNS resolution with a wonderfully simple API:
Still needs a few trivial things piped out through to events (eg DNSSEC proofs as "proof of payment"), but its basically done!