Skip to content

Commit e6f7a2d

Browse files
authored
Merge pull request #8734 from hieblmi/cancel-estimateroutefee
routing: cancelable payment loop
2 parents 68494fd + 4568dfc commit e6f7a2d

File tree

9 files changed

+317
-146
lines changed

9 files changed

+317
-146
lines changed

cmd/lncli/cmd_payments.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ var (
139139
Usage: "(blinded paths) the total cltv delay for the " +
140140
"blinded portion of the route",
141141
}
142+
143+
cancelableFlag = cli.BoolFlag{
144+
Name: "cancelable",
145+
Usage: "if set to true, the payment loop can be interrupted " +
146+
"by manually canceling the payment context, even " +
147+
"before the payment timeout is reached. Note that " +
148+
"the payment may still succeed after cancellation, " +
149+
"as in-flight attempts can still settle afterwards. " +
150+
"Canceling will only prevent further attempts from " +
151+
"being sent",
152+
}
142153
)
143154

144155
// paymentFlags returns common flags for sendpayment and payinvoice.
@@ -166,6 +177,7 @@ func paymentFlags() []cli.Flag {
166177
"after the timeout has elapsed",
167178
Value: paymentTimeout,
168179
},
180+
cancelableFlag,
169181
cltvLimitFlag,
170182
lastHopFlag,
171183
cli.Int64SliceFlag{
@@ -329,6 +341,7 @@ func sendPayment(ctx *cli.Context) error {
329341
Amt: ctx.Int64("amt"),
330342
DestCustomRecords: make(map[uint64][]byte),
331343
Amp: ctx.Bool(ampFlag.Name),
344+
Cancelable: ctx.Bool(cancelableFlag.Name),
332345
}
333346

334347
// We'll attempt to parse a payment address as well, given that
@@ -387,6 +400,7 @@ func sendPayment(ctx *cli.Context) error {
387400
Amt: amount,
388401
DestCustomRecords: make(map[uint64][]byte),
389402
Amp: ctx.Bool(ampFlag.Name),
403+
Cancelable: ctx.Bool(cancelableFlag.Name),
390404
}
391405

392406
var rHash []byte
@@ -888,6 +902,7 @@ func payInvoice(ctx *cli.Context) error {
888902
Amt: ctx.Int64("amt"),
889903
DestCustomRecords: make(map[uint64][]byte),
890904
Amp: ctx.Bool(ampFlag.Name),
905+
Cancelable: ctx.Bool(cancelableFlag.Name),
891906
}
892907

893908
return sendPaymentRequest(ctx, req)

docs/release-notes/release-notes-0.18.1.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,25 @@
2222
* `closedchannels` now [successfully reports](https://github.com/lightningnetwork/lnd/pull/8800)
2323
settled balances even if the delivery address is set to an address that
2424
LND does not control.
25+
26+
* [SendPaymentV2](https://github.com/lightningnetwork/lnd/pull/8734) now cancels
27+
the background payment loop if the user cancels the stream context.
2528

2629
# New Features
2730
## Functional Enhancements
2831
## RPC Additions
32+
33+
* The [SendPaymentRequest](https://github.com/lightningnetwork/lnd/pull/8734)
34+
message receives a new flag `cancelable` which indicates if the payment loop
35+
is cancelable. The cancellation can either occur manually by cancelling the
36+
send payment stream context, or automatically at the end of the timeout period
37+
if the user provided `timeout_seconds`.
38+
2939
## lncli Additions
3040

3141
* [Added](https://github.com/lightningnetwork/lnd/pull/8491) the `cltv_expiry`
3242
argument to `addinvoice` and `addholdinvoice`, allowing users to set the
33-
`min_final_cltv_expiry_delta`
43+
`min_final_cltv_expiry_delta`.
3444

3545
* The [`lncli wallet estimatefeerate`](https://github.com/lightningnetwork/lnd/pull/8730)
3646
command returns the fee rate estimate for on-chain transactions in sat/kw and
@@ -72,3 +82,4 @@
7282

7383
* Andras Banki-Horvath
7484
* Bufo
85+
* Slyghtning

lnrpc/routerrpc/router.pb.go

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/routerrpc/router.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,15 @@ message SendPaymentRequest {
330330
only, to 1 to optimize for reliability only or a value inbetween for a mix.
331331
*/
332332
double time_pref = 23;
333+
334+
/*
335+
If set, the payment loop can be interrupted by manually canceling the
336+
payment context, even before the payment timeout is reached. Note that the
337+
payment may still succeed after cancellation, as in-flight attempts can
338+
still settle afterwards. Canceling will only prevent further attempts from
339+
being sent.
340+
*/
341+
bool cancelable = 24;
333342
}
334343

335344
message TrackPaymentRequest {

lnrpc/routerrpc/router.swagger.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,10 @@
18041804
"type": "number",
18051805
"format": "double",
18061806
"description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix."
1807+
},
1808+
"cancelable": {
1809+
"type": "boolean",
1810+
"description": "If set, the payment loop can be interrupted by manually canceling the\npayment context, even before the payment timeout is reached. Note that the\npayment may still succeed after cancellation, as in-flight attempts can\nstill settle afterwards. Canceling will only prevent further attempts from\nbeing sent."
18071811
}
18081812
}
18091813
},

lnrpc/routerrpc/router_server.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,14 @@ var (
149149
DefaultRouterMacFilename = "router.macaroon"
150150
)
151151

152-
// ServerShell a is shell struct holding a reference to the actual sub-server.
152+
// ServerShell is a shell struct holding a reference to the actual sub-server.
153153
// It is used to register the gRPC sub-server with the root server before we
154154
// have the necessary dependencies to populate the actual sub-server.
155155
type ServerShell struct {
156156
RouterServer
157157
}
158158

159-
// Server is a stand alone sub RPC server which exposes functionality that
159+
// Server is a stand-alone sub RPC server which exposes functionality that
160160
// allows clients to route arbitrary payment through the Lightning Network.
161161
type Server struct {
162162
started int32 // To be used atomically.
@@ -181,7 +181,7 @@ var _ RouterServer = (*Server)(nil)
181181
// that contains all external dependencies. If the target macaroon exists, and
182182
// we're unable to create it, then an error will be returned. We also return
183183
// the set of permissions that we require as a server. At the time of writing
184-
// of this documentation, this is the same macaroon as as the admin macaroon.
184+
// of this documentation, this is the same macaroon as the admin macaroon.
185185
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
186186
// If the path of the router macaroon wasn't generated, then we'll
187187
// assume that it's found at the default network directory.
@@ -360,13 +360,25 @@ func (s *Server) SendPaymentV2(req *SendPaymentRequest,
360360
return err
361361
}
362362

363+
// The payment context is influenced by two user-provided parameters,
364+
// the cancelable flag and the payment attempt timeout.
365+
// If the payment is cancelable, we will use the stream context as the
366+
// payment context. That way, if the user ends the stream, the payment
367+
// loop will be canceled.
368+
// The second context parameter is the timeout. If the user provides a
369+
// timeout, we will additionally wrap the context in a deadline. If the
370+
// user provided 'cancelable' and ends the stream before the timeout is
371+
// reached the payment will be canceled.
372+
ctx := context.Background()
373+
if req.Cancelable {
374+
ctx = stream.Context()
375+
}
376+
363377
// Send the payment asynchronously.
364-
s.cfg.Router.SendPaymentAsync(payment, paySession, shardTracker)
378+
s.cfg.Router.SendPaymentAsync(ctx, payment, paySession, shardTracker)
365379

366380
// Track the payment and return.
367-
return s.trackPayment(
368-
sub, payHash, stream, req.NoInflightUpdates,
369-
)
381+
return s.trackPayment(sub, payHash, stream, req.NoInflightUpdates)
370382
}
371383

372384
// EstimateRouteFee allows callers to obtain an expected value w.r.t how much it
@@ -986,9 +998,8 @@ func (s *Server) SetMissionControlConfig(ctx context.Context,
986998
AprioriHopProbability: float64(
987999
req.Config.HopProbability,
9881000
),
989-
AprioriWeight: float64(req.Config.Weight),
990-
CapacityFraction: float64(
991-
routing.DefaultCapacityFraction),
1001+
AprioriWeight: float64(req.Config.Weight),
1002+
CapacityFraction: routing.DefaultCapacityFraction, //nolint:lll
9921003
}
9931004
}
9941005

@@ -1032,8 +1043,8 @@ func (s *Server) SetMissionControlConfig(ctx context.Context,
10321043

10331044
// QueryMissionControl exposes the internal mission control state to callers. It
10341045
// is a development feature.
1035-
func (s *Server) QueryMissionControl(ctx context.Context,
1036-
req *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
1046+
func (s *Server) QueryMissionControl(_ context.Context,
1047+
_ *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
10371048

10381049
snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot()
10391050

@@ -1080,7 +1091,7 @@ func toRPCPairData(data *routing.TimedPairResult) *PairData {
10801091

10811092
// XImportMissionControl imports the state provided to our internal mission
10821093
// control. Only entries that are fresher than our existing state will be used.
1083-
func (s *Server) XImportMissionControl(ctx context.Context,
1094+
func (s *Server) XImportMissionControl(_ context.Context,
10841095
req *XImportMissionControlRequest) (*XImportMissionControlResponse,
10851096
error) {
10861097

@@ -1273,8 +1284,9 @@ func (s *Server) subscribePayment(identifier lntypes.Hash) (
12731284
sub, err := router.Tower.SubscribePayment(identifier)
12741285

12751286
switch {
1276-
case err == channeldb.ErrPaymentNotInitiated:
1287+
case errors.Is(err, channeldb.ErrPaymentNotInitiated):
12771288
return nil, status.Error(codes.NotFound, err.Error())
1289+
12781290
case err != nil:
12791291
return nil, err
12801292
}
@@ -1385,7 +1397,7 @@ func (s *Server) trackPaymentStream(context context.Context,
13851397
}
13861398

13871399
// BuildRoute builds a route from a list of hop addresses.
1388-
func (s *Server) BuildRoute(ctx context.Context,
1400+
func (s *Server) BuildRoute(_ context.Context,
13891401
req *BuildRouteRequest) (*BuildRouteResponse, error) {
13901402

13911403
// Unmarshall hop list.
@@ -1446,7 +1458,7 @@ func (s *Server) BuildRoute(ctx context.Context,
14461458

14471459
// SubscribeHtlcEvents creates a uni-directional stream from the server to
14481460
// the client which delivers a stream of htlc events.
1449-
func (s *Server) SubscribeHtlcEvents(req *SubscribeHtlcEventsRequest,
1461+
func (s *Server) SubscribeHtlcEvents(_ *SubscribeHtlcEventsRequest,
14501462
stream Router_SubscribeHtlcEventsServer) error {
14511463

14521464
htlcClient, err := s.cfg.RouterBackend.SubscribeHtlcEvents()
@@ -1495,7 +1507,7 @@ func (s *Server) SubscribeHtlcEvents(req *SubscribeHtlcEventsRequest,
14951507

14961508
// HtlcInterceptor is a bidirectional stream for streaming interception
14971509
// requests to the caller.
1498-
// Upon connection it does the following:
1510+
// Upon connection, it does the following:
14991511
// 1. Check if there is already a live stream, if yes it rejects the request.
15001512
// 2. Registered a ForwardInterceptor
15011513
// 3. Delivers to the caller every √√ and detect his answer.
@@ -1525,7 +1537,7 @@ func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) {
15251537
}
15261538

15271539
// UpdateChanStatus allows channel state to be set manually.
1528-
func (s *Server) UpdateChanStatus(ctx context.Context,
1540+
func (s *Server) UpdateChanStatus(_ context.Context,
15291541
req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) {
15301542

15311543
outPoint, err := extractOutPoint(req)

0 commit comments

Comments
 (0)