-
Notifications
You must be signed in to change notification settings - Fork 112
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
loopout: configurable payment timeout for off-chain payments #743
Conversation
7428ef8
to
588145b
Compare
588145b
to
5af5697
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.
Very nice feature! I have a question regarding the cancellation of the payment context in addition to the paymentTimeout
.
Since we are thinking about properly exiting lnd
's payment loop in this PR lightningnetwork/lnd#8734 it might make sense to also make the context of awaitSendPayment
bound to the timeout.
/* | ||
The timeout in seconds to use for off-chain payments. Note that the swap | ||
payment is attempted multiple times where each attempt will set this value | ||
as the timeout for the payment. |
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.
In the high/low case, isn't the timeout value split across the attempts? If so, should we mention that 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.
Oh, I see, we are adjusting the no of retry attempts in payInvoiceAsync
if this setup exceeds the total config timeout.
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.
Yeah it'll try to fill the total allocated time, but each attempt will timeout after the payment_timeout
(if set).
@@ -443,6 +443,7 @@ func loopOutToInsertArgs(hash lntypes.Hash, | |||
PrepayInvoice: loopOut.PrepayInvoice, | |||
MaxPrepayRoutingFee: int64(loopOut.MaxPrepayRoutingFee), | |||
PublicationDeadline: loopOut.SwapPublicationDeadline.UTC(), | |||
PaymentTimeout: int32(loopOut.PaymentTimeout.Seconds()), |
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.
what happens for older entries? I guess the timeout is 0
then?
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.
Old entries won't have this set, and for those we'll assume that the payment timeout will be equal to the total divided by the max configured retries (like before). Thanks for mentioning though as I left this specific case out for resumed swaps (now added back).
@@ -647,8 +650,8 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string, | |||
var result paymentResult | |||
|
|||
status, err := s.payInvoiceAsync( |
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.
If we follow s.payInvoiceAsync
and the paymentTimeout
it ends up in awaitSendPayment
. The context here is passed to s.lnd.Router.SendPayment(ctx, *req)
.
Question: Does it make sense to cancel this context once the paymentTimeout
is reached, given that we think about making this context cancelable to properly shutdown lnd
's payment loop? The related PR is here: lightningnetwork/lnd#8734
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.
Yeah I think this could be a good idea, but given that not all users switch to the latest LND release, we need to still rely on the request's timeout. For now I'll keep it as is and we can add it in a separate PR once we bump the minimum LND version.
5af5697
to
b635394
Compare
b635394
to
ff74ad2
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 🎉
Left few small comments.
loopd/swapclient_server.go
Outdated
return nil, fmt.Errorf("payment timeout exceeds maximum "+ | ||
"allowed timeout of %v", s.config.TotalPaymentTimeout) |
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 propose to include paymentTimeout
into the error message:
return nil, fmt.Errorf("payment timeout %v exceeds maximum " ...
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.
done
@@ -101,6 +101,13 @@ func (s *swapClientServer) LoopOut(ctx context.Context, | |||
|
|||
log.Infof("Loop out request received") | |||
|
|||
paymentTimeout := time.Duration(in.PaymentTimeout) * time.Second | |||
// Make sure we don't exceed the total allowed payment timeout. | |||
if paymentTimeout > s.config.TotalPaymentTimeout { |
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 propose to add a comment that in.PaymentTimeout
is uint32, so it is >= 0 (no need to check that separately).
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.
done
if paymentTimeout*time.Duration(maxRetries) > | ||
totalPaymentTimeout { | ||
|
||
maxRetries = int(totalPaymentTimeout / paymentTimeout) |
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 propose to add an info or warning 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.
done
if paymentTimeout*time.Duration(maxRetries) > | ||
totalPaymentTimeout { | ||
|
||
maxRetries = int(totalPaymentTimeout / paymentTimeout) |
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.
If totalPaymentTimeout is not divisible by paymentTimeout, the remainder of totalPaymentTimeout will be removed. Does it make sense for the last attempt to use a shorter timeout instead?
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 it's needed since both the individual timeout and the total timeout is configurable.
cmd/loop/loopout.go
Outdated
paymentTimeout := 0 | ||
if ctx.IsSet("payment_timeout") { | ||
paymentTimeout = int(ctx.Duration("payment_timeout").Seconds()) | ||
if paymentTimeout < 0 { |
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.
If user passes --payment_timeout=0s
, it should also fail, I think. So this check should be if paymentTimeout <= 0
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.
done
cmd/loop/loopout.go
Outdated
@@ -235,6 +243,15 @@ func loopOut(ctx *cli.Context) error { | |||
} | |||
} | |||
|
|||
paymentTimeout := 0 | |||
if ctx.IsSet("payment_timeout") { | |||
paymentTimeout = int(ctx.Duration("payment_timeout").Seconds()) |
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 propose to add a check that the value is divisible by a second. The current implementation treats --payment_timeout=99.999s
as --payment_timeout=99s
silently. I think it should crash instead, complaining that sub-second accuracy is not supported.
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.
done
cmd/loop/loopout.go
Outdated
@@ -235,6 +243,15 @@ func loopOut(ctx *cli.Context) error { | |||
} | |||
} | |||
|
|||
paymentTimeout := 0 | |||
if ctx.IsSet("payment_timeout") { | |||
paymentTimeout = int(ctx.Duration("payment_timeout").Seconds()) |
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 propose to check that paymentTimeout < math.MaxUint32
just in case, to make sure that it does not overflow in the conversion to uint32
below. It should be always satisfied, because time.Duration does not support such large values.
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.
done
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 💾
ff74ad2
to
01c017d
Compare
With this client side RPC option the client will be able to set a payment timeout for individual off-chain payments. This applies both to the prepayment and the swap payment. With the swap payment, we may try to pay the swap invoice multiple times, but each attempt will use this configurable timeout.
Pull Request Checklist
release_notes.md
if your PR contains major features, breaking changes or bugfixes