Closed ellemouton closed 2 weeks ago
[!IMPORTANT]
Review skipped
Auto reviews are limited to specific labels.
Labels to auto review (1)
* llm-reviewPlease check the settings in the CodeRabbit UI or the
.coderabbit.yaml
file in this repository. To trigger a single review, invoke the@coderabbitai review
command.You can disable this status message by setting the
reviews.review_status
tofalse
in the CodeRabbit configuration file.
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?
@bitromortac: review reminder @ellemouton, remember to re-request review from reviewers when ready
Adding a write-up here in the hopes that it will provide some clarity to reviewers of what my current understanding of things is: please let me know if i'm missing something 🙏 cc @bitromortac & @ziggie1984
Recipient (D
) communicates via invoice the min_final_expiry_delta
that they would accept. Meaning that if the current block height is x
then the want the minimum difference between the CLTV value of the incoming HTLC to be min_final_expiry_delta
. In otherwords, it is the buffer they want to be able to claim the HTLC if their peer goes on-chain. If the incoming payment has a CLTV value that would result in a safety buffer, then the recipient will reject the payment. The sender will want the payment to resolve as quickly as possible and wont want their funds on their outgoing channel to be tied up for too long if something goes wrong, so they are insentivised to use the smallest possible CLTV for the path. So they will try to use a value as close to (but not less than) x
(current block height) + min_final_expiry_delta
. However, the sender also wants the payment to succeed and so will want to avoid the scenario where they initiate the payment at block x
but then a block or two are mined before the recipient gets it and so the recipient will fail the payment. So to avoid this, the sender will add a few padding blocks (in LND, a padding of 3 is used.
The Sender (A
) finds a route to the recipient: A -> B -> C -> D. The sender constructs the parameters of the route starting at the final hop, C-> D. The sender sets the final outgoing_cltv_value
to:
C->D: outgoing_cltv_value
= x
(current block height) + b
(block buffer) + min_final_cltv_expiry_delta
= cltv_1
For the other hops:
B->C `outgoing_cltv_value` = `cltv_1` + C's advertised `cltv_expiry_delta` = `cltv_2`
A-> `outgoing_cltv_value` = `cltv_2` + B's advertised `cltv_expiry_delta`
The invoice (Bolt11) also contains an expiry
field in seconds and an invoice creation field (unix timestamp). These are completely separate from the CLTV expiry. There is no way for a routing node of the payment to know that it is routing a payment that will fail due to the recipient marking it as expired. So it could very well be that the actual payment parameters are valid long past the expiration of the invoice.
Note that in this flow the sender, A, is responsible for deciding to add a block buffer to the provided min_cltv_expiry_delta
so as to make sure that the payment still can work if a block or two are mined while the htlc chain is being set up.
For route blinding, the recipient is given a way to be able to enforce an expiry on the blinded part of the route. This is nice because the payment can fail earlier (since each hop can check if the expiry has passed) and it gives some protection to the blinded hops so that after the path has expired, they can change their routing parameters. This also allows us to tie invoice expiry a bit closer to the actual path expiry. We have 2 options here afaict:
1) re-use the existing expiry
field, assume a 10-min block time, also look at the invoice creation time and use these to determine the approximate block at which the path expires.
2) add a new max_cltv
field to the invoice which very explicitly communicates this.
Re having payment_constraints
for recipient:
If we go with 1) above, then I think it makes sense to add the payment constraint with the absolute max_cltv_expiry
in the recipient's encrypted data since then they dont need to store this separately in order to do the check when the payment comes in. I also like this cause then, just like all other hops on the blinded path, we repeat the same check against the "absolute source of truth". Otherwise we either need to persist this absolute truth somewhere else or we need to use the invoice "expiry" to work out a "rough" expiry. I think it is ok for the sender to work out a rough expiry but not so much the reciever.
If we go with 2) above, then maybe there is then not a reason to include the payment constraints for the recipient
the OG reason I incluced the payment_constraints
for the recipient was cause of this example.
The flow is then as follows:
1) Recipient, D
has:
x
min_final_cltv_expiry_delta
which Dave requires for the last CLTV value.max_cltv_expiry
min_final_cltv_expiry_delta
. 2) D
chooses a blinded path to himself: B -> C -> D. Let B's CLTV delta be b_delta
and C's be c_delta
.
3) D
calculates the accumulated blinded path CLTV delta as follows: total_cltv_delta
= min_final_cltv_expiry_delta
+ block_buffer
(optional) + b_delta
+ c_delta
(according to the example in the proposals doc but not explicitly stated in BOLT 4. See this issue for discussion on this
4) D
also constructs encrypted packets for each node on the blinded path. He uses this blob to communicate a max_cltv
of min_final_cltv_expiry_delta
+ c_delta
+ max_cltv_expiry
+ block_buffer
to C
(according to the example) and a max_cltv
of min_final_cltv_expiry_delta
+ c_delta
+ b_delta
+ max_cltv_expiry
+ block_buffer
to B
. In D
's packet to himself, he may include min_final_cltv_expiry_delta
+ block_buffer
+ max_cltv_expiry
so that he doesnt need to explicitly remember it.
5) D
sends the sender, A
:
5.1) the blinded path (B
-> B(C)
-> B(D)
)
5.2) the encrypted data to deliver to each hop and total_cltv_delta
.
5.3) Note that D
does not send min_final_cltv_delta
to A
(as is done in the normal payment) and A
is instead expected to know that this value is now included in the total_cltv_delta
.
5.4) D
does, however, also send A
the max_expiry_delta
so that A
has an idea of when they need to use the route by.
min_final_cltv_expiry_delta
from the invoice. min_final_cltv_expiry_delta
is included in accumulated route CLTV deltamin_final_cltv_exiry
wont automatically be accounted for. We need to go extract it & apply it to final_hop params.1) this issue 2) bLIP proposal
NOTE: has been edited (01/07/2024)
Thanks a lot for the writeup and thorough analysis @ellemouton!
some small corrections for iv):
D also constructs encrypted packets for each node on the blinded path. He uses this blob to communicate a
max_cltv_expiry
ofx + min_final_cltv_expiry_delta + c_delta + max_cltv_expiry
to C (according to the example) and amax_cltv_expiry
ofx + min_final_cltv_expiry_delta + c_delta + b_delta + max_cltv_expiry
to B. In D's packet to himself, he may includemax_cltv_expiry
so that he doesnt need to explicitly remember it.
Concerning the points:
min_final_cltv_expiry_delta
,max_cltv_expiry
, see https://github.com/lightningnetwork/lnd/pull/8735#discussion_r1654380105?expiry
= now + total_expiry_delta
* 9 min, to make the invoice expire before the path. As the path expiry is just a probing protection, I think it makes sense to not add an extra field. I would also argue against per-blinded-path expiries, since the receiver can make them uniform for all paths and in the end it is a single invoice that's about to be paid?CLTVs, CLTV-deltas and invoice expiry in a payment
Thank you for the explanation, I think I now understood the whole ctlv expiry for blinded paths.
But all those changes will only affect the follow up PRs regarding the CLTV Delta (I think PR 2)
Would we wait for all of the PRs to have ACKs to merge this?
Personally, I think we should just merge since this is not practically usable till the second PR is merged anyways. So we can always throw in small changes at the start of that PR if we realise we need to change something
To make review a bit more manageable, I've split out some of the groundwork needed for the main route blinding receives PR. This PR thus has no functional changes.
Tracking Issue: https://github.com/lightningnetwork/lnd/issues/6690
PR Overview:
1) a couple of refactors & interface expansions so that we have easy access to the
BlindedPayment
associated with an edge in path finding later on. 2) Remove the need to communicate the "TLV Option" in a blinded path feature bit vector. This bit can be assumed for any nodes in a blinded path. 3) Add fields toBlindedRouteData
that will be required for receives. Namely, PathID and Padding. 4) Bolt11 blinded payment path encodingEDIT: bLIP proposal here: https://github.com/lightning/blips/pull/39