-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Intercept forward htlc #4018
Intercept forward htlc #4018
Conversation
I think they're different sides of the same coin? My understanding of this is that it's preemptive: receive forward, callout to external forwarding predicate and then return, as specified in #3953. |
@halseth thanks for bringing this to the discussion.
I think it will be an attempt to mix different things together. |
Interception should be done at the switch, not the link. Also extreme care must be taken to not block the entire forwarding fabric, as the switch manages its state synchronously atm. |
@roeierez I really like the look of the API here. I've also been working on something similar. It allows you to register virtual channels and control HTLCs there through gRPC. It adds a new The highly-WIP code is at b3b2f7f and an usage example at https://gist.github.com/champo/8e90ce1ef38f2dd546814ff7898988c3 A knonw issue is that it doesn't close circuits so it kinda stops working after a success settle. It's easy to fix but I haven't gotten around to it yet. |
df7f3de
to
a199479
Compare
I have moved the interception to the switch, added unit tests and the Router RPC implementation. |
uint64 htlc_id = 2; | ||
} | ||
|
||
message ForwardHtlcInterceptRequest { |
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.
It'd be great to include the sphinx packet in this message. It would allow a receiver to check amount and expiry as it would if the HTLC had been forwarded through a link. This is useful for reverse swaps.
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.
@champo thanks for the feedback.
Currently the fields amount_sat
expiry
are what you are looking for as they are the actual values that would have been used in the forwarded packet in the default switch behavior.
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.
A third-party receiver (say a mobile app) shouldn't trust the fields. They would want to check the information against the sphinx. It would also enable usage of new fields like payment secret which is only known to the receiver.
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.
@champo I agree. At first I exposed only the fields that are required for the use cases I mentioned in the PR description. I do think it makes sense to expose all the forwarding info (such as outgoing channel, tlv fields and raw sphinx packet) as other use cases may need this info in order to take a decision at interception time.
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.
It'd be great to include the sphinx packet in this message.
The raw packet itself can't be verified unless the receiver has the private key for the end node.
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.
@Roasbeef in our case, the end node and the interceptor communicate over HTTP to negotiate the settling of the HTLC. The end node can then decode the sphinx packet to reduce trust.
a199479
to
b1cdbc7
Compare
5d72a87
to
c416106
Compare
c416106
to
ef5113a
Compare
b95122a
to
f2faff9
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 🔱
Don't think we need the REST annotation since this type of RPC isn't supported atm. Should be ready to land after a rebase!
|
||
switch htlc := packet.htlc.(type) { | ||
case *lnwire.UpdateAddHTLC: | ||
// We are not interested in intercepting initated payments. |
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 might want to reverse this in the future, as it would allow for the interceptor to be used as a firewall for all outgoing send attempts.
@@ -211,6 +211,8 @@ http: | |||
# deprecated, no REST endpoint | |||
- selector: routerrpc.Router.TrackPayment | |||
# deprecated, no REST endpoint | |||
- selector: routerrpc.HtlcInterceptor |
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 don't think we need anything here since this is a bi directional streaming RPC, which we don't yet support. The current web sockets implementation only supports server-side streaming requests.
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 added it because it is required by the make rpc-check
As part of the preparation to the switch interceptor feature, this function is changed to return error instead of error channel that is closed automatically. Returning an error channel has become complex to maintain and implement when adding more asynchronous flows to the switch. The change doesn't affect the current behavior which logs the errors as before.
f2faff9
to
cfa2ee8
Compare
The build requires it atm:
Rebased. |
cfa2ee8
to
2b0bdab
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.
reviewed 2b0bdab1fb7c5a8b4602b6e65a6215d6fd4ceaa4
if err != nil { | ||
return err | ||
} | ||
err = interceptedForward.Settle(preimage) |
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.
return interceptedForward.Settle(preimage)
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.
Fixed.
// forwards and find them when manual resolution is later needed. | ||
func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error { | ||
// We ensure there is only one interceptor at a time. | ||
if !atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 0, 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.
I think it is still useful to make sure that any user of interceptable switch uses it correctly. But non-blocking
In this commit we implement a wrapper arround the switch, called InterceptableSwitch. This kind of wrapper behaves like a proxy which intercepts forwarded packets and allows an external interceptor to signal if it is interested to hold this forward and resolve it manually later or let the switch execute its default behavior. This infrastructure allows the RPC layer to expose interceptor registration API to the user and by that enable the implementation of custom routing behavior.
2b0bdab
to
5501da3
Compare
In this commit we add the ability to intercept forwarded htlc packets straight from the RPC layer. The RPC layer handles a bidrectional stream that comminucates to the client the intercepted packets and handles its response by coordinating with the interceptable switch.
5501da3
to
7b56268
Compare
Forwarded HTLC requests are sent to the client and the client responds with | ||
a boolean that tells LND if this htlc should be intercepted. | ||
In case of interception, the htlc can be either settled, cancelled or | ||
resumed later by using the ResolveHoldForward endpoint. |
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.
Hi @roeierez. This description text looks wrong. I don't find any ResolveHoldForward
endpoint.
AFAICT how to resume the incoming payment is by sending with action
set to RESUME
in this gRPC stream.
Perhaps I'm missing something?
Also by reading the protobuf, it doesn't look lke the client is supposed to respond with a boolean if the HTLC should be intercepted or not (rather it is decided by the incoming_circuit_key
?)
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.
@hsjoberg You are right. Unfortunately this description was left from a previous iteration and is indeed wrong.
The interface was changes and the client should return a ForwardHtlcInterceptResponse with action that maps to one of the follows:
SETTLE: to settle the htlc (a preimage is expected as well).
FAIL: to fail the htlc
RESUME: to resume with default behavior.
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.
@roeierez Thanks, that makes sense.
Cheers!
@roeierez Correct me if I'm wrong about this, but AFAICT as it stands right now, this API is dangerous if an amountless invoice is used (and action is EDIT: Below is based on misunderstandings from my side. |
@hsjoberg Not following. Settling an htlc backwards requires a preimage which is passed to the interceptor by the next node in the path either using regular lightning mechanism or some custom swap logic. In any case interceptor can make sure it has the required funds before settling. Don't see any risk. |
@roeierez In the case of an undetermined amount ("amountless invoice"), the interceptor node must somehow know how much it should reveal the preimage for to settle the payment. AFAICT basically what feature bits 14/15 is for. |
@hsjoberg The interceptor doesn't know the total amount of a payment regardless of amount-less invoice or not. Invoice is between the payer and the payee. Also the way the interceptor gets the pre-image to settle backwards is totally implementation dependent so in that sense it can be risky or not. |
But that could just be trivially communicated with the interceptor somehow in the case of a normal invoice with amount. @roeierez Sorry, I will try to explain my concern better. I'll take it from the beginning: Let's say A wants to pay C 1 BTC through M: Instead of forwarding the real payment, M could make a new payment with 1 sat using the same hash in an attempt lure C to reveal the preimage. This issue has been demonstrated in the past and fixed with the 14/15 payment secret and/or enforcing TLV/MPP/keysend. My high-level understanding is that a nonce is introduced that the payer is sending to the payee, which can be used to find out tampering. Now let's say we're using Pay to Open, and interceptor settling the payment and opening channel to C (either paying through push amount or another invoice, shouldn't matter AFAICT). The malicious party M could attempt to lure I and thus also C by forwarding 1 sat instead of the real payment. |
Then what prevents the payee to communicate the amount he expects in the zero invoice case as well?
If this is the case how the interceptor has the pre-image? It seems that you described a flow where the interceptor has the pre-image beforehand and this is not how we (at Breez) implementing Pay to Open. We do forward the packet to the destination and only once the destination reveals the pre-image the interceptor settles backwards using regular lightning mechanism. |
The payee doesn't know what amount to expect, hence amountless invoice.
The wallet simply hands the preimage to the LSP and the LSP uses the
I explained briefly in the OP that the concern is about the
I'm not talking about Breez's implementation per se, just resuming the forward should be completely fine. The interceptor requests a new invoice from the wallet to pay through and then settles the hold forward in this scenario(?). Thus the original sender's TLV payload would not reach the receiver.
Yes, if the interceptor just resumed the forwarding and uses a newly created channel to the payee, the problem I've described does not apply as 14 payment secret (or any other method) can be enforced. |
This PR introduces the ability to intercept htlc forward events that are handled at the htlcswitch package and to override the switch default behavior. Basically, similar to HODL invoices, this PR enables routing nodes to HODL HTLCs and to run custom validations or processes before forwarding (or cancelling) the HTLC.
More specifically for every HTLC that is intended to be forwarded, the implementor can choose to hold the forward and signal the switch to wait for a later resolution. Such resolution can be one of the following:
Having more control over the forward behavior enables new important use cases to be implemented.
We are specifically looking to support the following use cases:
Pay to Open
This is about being able to pay someone with no channel or with a low capacity channel by opening a channel during the payment.
Let's say Alice (that has a channel) wants to pay Bob (that doesn't have a channel).
Notify to Pay
Let's say Alice wants to pay Bob, a mobile user, which is connected to a node via private channel.
Implementation flow & Usage
Resume
)I am attaching a sample file to show a sample usage that filter packets according to amount
cmd_intercept_forward.txt