-
Notifications
You must be signed in to change notification settings - Fork 2.3k
lnrpc/routerrrpc+routing: fix send to route error propagation #5479
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -782,10 +782,13 @@ func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo, | |
| if err := p.router.cfg.Control.Fail( | ||
| p.identifier, *reason); err != nil { | ||
|
|
||
| return err | ||
| log.Errorf("unable to report failure to control "+ | ||
| "tower: %v", err) | ||
|
|
||
| return &internalErrorReason | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to ReportPaymentFail below, we just count this as an internal failure reason. This lets the method always return *channeldb.FailureReason. The downside is that we lose this error context from the PoV, of the caller.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer the |
||
| } | ||
|
|
||
| return *reason | ||
| return reason | ||
| } | ||
|
|
||
| // reportFail is a helper closure that reports the failure to the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package routing | |
|
|
||
| import ( | ||
| "bytes" | ||
| goErrors "errors" | ||
|
Roasbeef marked this conversation as resolved.
Outdated
|
||
| "fmt" | ||
| "runtime" | ||
| "strings" | ||
|
|
@@ -2163,13 +2164,23 @@ func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route) ( | |
| // mark the payment failed with the control tower immediately. Process | ||
| // the error to check if it maps into a terminal error code, if not use | ||
| // a generic NO_ROUTE error. | ||
| if err := sh.handleSendError(attempt, shardError); err != nil { | ||
| return nil, err | ||
| } | ||
| var failureReason *channeldb.FailureReason | ||
| err = sh.handleSendError(attempt, shardError) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the method should maybe just return |
||
|
|
||
| err = r.cfg.Control.Fail( | ||
| paymentIdentifier, channeldb.FailureReasonNoRoute, | ||
| ) | ||
| switch { | ||
| // If we weren't able to extract a proper failure reason (which can | ||
| // happen if the second chance logic is triggered), then we'll use the | ||
| // normal no route error. | ||
| case err == nil: | ||
| err = r.cfg.Control.Fail( | ||
| paymentIdentifier, channeldb.FailureReasonNoRoute, | ||
| ) | ||
|
|
||
| // If this is a failure reason, then we'll apply the failure directly | ||
| // to the control tower, and return the normal response to the caller. | ||
| case goErrors.As(err, &failureReason): | ||
| err = r.cfg.Control.Fail(paymentIdentifier, *failureReason) | ||
| } | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2921,44 +2921,64 @@ func TestSendToRouteStructuredError(t *testing.T) { | |
| t.Fatalf("unable to create route: %v", err) | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: there's a typo in the commit message, As is, this test will fail as a recent refactoring causes us to return a |
||
|
|
||
| // We'll modify the SendToSwitch method so that it simulates a failed | ||
| // payment with an error originating from the first hop of the route. | ||
| // The unsigned channel update is attached to the failure message. | ||
| ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult( | ||
| func(firstHop lnwire.ShortChannelID) ([32]byte, error) { | ||
| return [32]byte{}, htlcswitch.NewForwardingError( | ||
| &lnwire.FailFeeInsufficient{ | ||
| Update: lnwire.ChannelUpdate{}, | ||
| }, 1, | ||
| finalHopIndex := len(hops) | ||
|
Roasbeef marked this conversation as resolved.
Outdated
|
||
| testCases := map[int]lnwire.FailureMessage{ | ||
| finalHopIndex: lnwire.NewFailIncorrectDetails(payAmt, 100), | ||
| 1: &lnwire.FailFeeInsufficient{ | ||
| Update: lnwire.ChannelUpdate{}, | ||
| }, | ||
| } | ||
|
|
||
| for failIndex, errorType := range testCases { | ||
| failIndex := failIndex | ||
| errorType := errorType | ||
|
|
||
| t.Run(fmt.Sprintf("%T", errorType), func(t *testing.T) { | ||
| // We'll modify the SendToSwitch method so that it | ||
| // simulates a failed payment with an error originating | ||
| // from the final hop in the route. | ||
| ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcherOld).setPaymentResult( | ||
| func(firstHop lnwire.ShortChannelID) ([32]byte, error) { | ||
| return [32]byte{}, htlcswitch.NewForwardingError( | ||
| errorType, failIndex, | ||
| ) | ||
| }, | ||
| ) | ||
| }) | ||
|
|
||
| // The payment parameter is mostly redundant in SendToRoute. Can be left | ||
| // empty for this test. | ||
| var payment lntypes.Hash | ||
| // The payment parameter is mostly redundant in | ||
| // SendToRoute. Can be left empty for this test. | ||
| var payment lntypes.Hash | ||
|
|
||
| // Send off the payment request to the router. The specified route | ||
| // should be attempted and the channel update should be received by | ||
| // router and ignored because it is missing a valid signature. | ||
| _, err = ctx.router.SendToRoute(payment, rt) | ||
| // Send off the payment request to the router. The | ||
| // specified route should be attempted and the channel | ||
| // update should be received by router and ignored | ||
| // because it is missing a valid | ||
| // signature. | ||
| _, err = ctx.router.SendToRoute(payment, rt) | ||
|
|
||
| fErr, ok := err.(*htlcswitch.ForwardingError) | ||
| if !ok { | ||
| t.Fatalf("expected forwarding error") | ||
| } | ||
| fErr, ok := err.(*htlcswitch.ForwardingError) | ||
| require.True( | ||
| t, ok, "expected forwarding error, got: %T", err, | ||
| ) | ||
|
|
||
| if _, ok := fErr.WireMessage().(*lnwire.FailFeeInsufficient); !ok { | ||
| t.Fatalf("expected fee insufficient error") | ||
| } | ||
| require.IsType( | ||
| t, errorType, fErr.WireMessage(), | ||
| "expected type %T got %T", errorType, | ||
| fErr.WireMessage(), | ||
| ) | ||
|
|
||
| // Check that the correct values were used when initiating the payment. | ||
| select { | ||
| case initVal := <-init: | ||
| if initVal.c.Value != payAmt { | ||
| t.Fatalf("expected %v, got %v", payAmt, initVal.c.Value) | ||
| } | ||
| case <-time.After(100 * time.Millisecond): | ||
| t.Fatalf("initPayment not called") | ||
| // Check that the correct values were used when | ||
| // initiating the payment. | ||
| select { | ||
| case initVal := <-init: | ||
| if initVal.c.Value != payAmt { | ||
| t.Fatalf("expected %v, got %v", payAmt, | ||
| initVal.c.Value) | ||
| } | ||
| case <-time.After(100 * time.Millisecond): | ||
| t.Fatalf("initPayment not called") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.