Merge pull request #5479 from Roasbeef/fix-send-to-route-error-propagation

lnrpc/routerrrpc+routing: fix send to route error propagation
This commit is contained in:
Olaoluwa Osuntokun 2021-07-07 16:01:56 -07:00 committed by GitHub
commit 583dee9916
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 45 deletions

View File

@ -503,7 +503,7 @@ func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
return err
}
// We mark the payent as failed as long as it is known. This
// We mark the payment as failed as long as it is known. This
// lets the last attempt to fail with a terminal write its
// failure to the PaymentControl without synchronizing with
// other attempts.

View File

@ -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
}
return *reason
return reason
}
// reportFail is a helper closure that reports the failure to the

View File

@ -2,6 +2,7 @@ package routing
import (
"bytes"
goErrors "errors"
"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)
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
}

View File

@ -2921,44 +2921,64 @@ func TestSendToRouteStructuredError(t *testing.T) {
t.Fatalf("unable to create route: %v", err)
}
// 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)
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
// 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)
require.True(
t, ok, "expected forwarding error, got: %T", err,
)
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")
}
})
// 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)
fErr, ok := err.(*htlcswitch.ForwardingError)
if !ok {
t.Fatalf("expected forwarding error")
}
if _, ok := fErr.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
t.Fatalf("expected fee insufficient error")
}
// 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")
}
}