Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion channeldb/payment_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
Roasbeef marked this conversation as resolved.
Outdated
// lets the last attempt to fail with a terminal write its
// failure to the PaymentControl without synchronizing with
// other attempts.
Expand Down
7 changes: 5 additions & 2 deletions routing/payment_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the internalErrorReason. To me errors come from other packages/services do seem to be "internal" when we are viewing it as the user of the routing. It's probably better (haven't thought through it) though we could wrap the error inside the internal error and returns it back to the caller.

}

return *reason
return reason
}

// reportFail is a helper closure that reports the failure to the
Expand Down
23 changes: 17 additions & 6 deletions routing/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routing

import (
"bytes"
goErrors "errors"
Comment thread
Roasbeef marked this conversation as resolved.
Outdated
"fmt"
"runtime"
"strings"
Expand Down Expand Up @@ -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)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the method should maybe just return *channeldb.FailureReason directly so it's clear that it can't return a "real" generic error.


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
}
Expand Down
84 changes: 52 additions & 32 deletions routing/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2921,44 +2921,64 @@ func TestSendToRouteStructuredError(t *testing.T) {
t.Fatalf("unable to create route: %v", err)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
channeldb.FailureReason error, since the newly added handleSendError
code path in the SendToRoute method will return the raw error, ratehr rather
than the shardError, which is of the expected type.


// 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)
Comment thread
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")
}
})
}
}

Expand Down