htlcswitch: use distinct error for unreadable failures
This commit is contained in:
9 files changed
+396
-165
No files matched your search
@@ -17,7 +17,7 @@ import (
|
||||
type errNoRoute struct {
|
||||
// lastError is the error encountered during the last payment attempt,
|
||||
// if at least one attempt has been made.
|
||||
lastError *htlcswitch.ForwardingError
|
||||
lastError error
|
||||
}
|
||||
|
||||
// Error returns a string representation of the error.
|
||||
@@ -37,7 +37,7 @@ type paymentLifecycle struct {
|
||||
finalCLTVDelta uint16
|
||||
attempt *channeldb.PaymentAttemptInfo
|
||||
circuit *sphinx.Circuit
|
||||
lastError *htlcswitch.ForwardingError
|
||||
lastError error
|
||||
}
|
||||
|
||||
// resumePayment resumes the paymentLifecycle from the current state.
|
||||
@@ -341,25 +341,16 @@ func (p *paymentLifecycle) sendPaymentAttempt(firstHop lnwire.ShortChannelID,
|
||||
// handleSendError inspects the given error from the Switch and determines
|
||||
// whether we should make another payment attempt.
|
||||
func (p *paymentLifecycle) handleSendError(sendErr error) error {
|
||||
var reason channeldb.FailureReason
|
||||
|
||||
// If an internal, non-forwarding error occurred, we can stop trying.
|
||||
fErr, ok := sendErr.(*htlcswitch.ForwardingError)
|
||||
if !ok {
|
||||
reason = channeldb.FailureReasonError
|
||||
} else {
|
||||
var final bool
|
||||
final, reason = p.router.processSendError(
|
||||
p.paySession, &p.attempt.Route, fErr,
|
||||
)
|
||||
final, reason := p.router.processSendError(
|
||||
p.paySession, &p.attempt.Route, sendErr,
|
||||
)
|
||||
if !final {
|
||||
// Save the forwarding error so it can be returned if
|
||||
// this turns out to be the last attempt.
|
||||
p.lastError = sendErr
|
||||
|
||||
if !final {
|
||||
// Save the forwarding error so it can be returned if
|
||||
// this turns out to be the last attempt.
|
||||
p.lastError = fErr
|
||||
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Payment %x failed: final_outcome=%v, raw_err=%v",
|
||||
|
||||
+18
-2
@@ -1812,8 +1812,24 @@ func (r *ChannelRouter) sendPayment(
|
||||
// to continue with an alternative route. This is indicated by the boolean
|
||||
// return value.
|
||||
func (r *ChannelRouter) processSendError(paySession PaymentSession,
|
||||
rt *route.Route, fErr *htlcswitch.ForwardingError) (
|
||||
bool, channeldb.FailureReason) {
|
||||
rt *route.Route, sendErr error) (bool, channeldb.FailureReason) {
|
||||
|
||||
// If the failure message could not be decrypted, attribute the failure
|
||||
// to our own outgoing channel.
|
||||
//
|
||||
// TODO(joostager): Penalize all channels in the route.
|
||||
if sendErr == htlcswitch.ErrUnreadableFailureMessage {
|
||||
sendErr = &htlcswitch.ForwardingError{
|
||||
FailureSourceIdx: 0,
|
||||
FailureMessage: lnwire.NewTemporaryChannelFailure(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// If an internal, non-forwarding error occurred, we can stop trying.
|
||||
fErr, ok := sendErr.(*htlcswitch.ForwardingError)
|
||||
if !ok {
|
||||
return true, channeldb.FailureReasonError
|
||||
}
|
||||
|
||||
var (
|
||||
failureSourceIdx = fErr.FailureSourceIdx
|
||||
|
||||
@@ -2425,6 +2425,95 @@ func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnknownErrorSource tests that if the source of an error is unknown, all
|
||||
// edges along the route will be pruned.
|
||||
func TestUnknownErrorSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Setup a network. It contains two paths to c: a->b->c and an
|
||||
// alternative a->d->c.
|
||||
chanCapSat := btcutil.Amount(100000)
|
||||
testChannels := []*testChannel{
|
||||
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
||||
}, 1),
|
||||
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
||||
}, 3),
|
||||
symmetricTestChannel("a", "d", chanCapSat, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
FeeBaseMsat: 100000,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
||||
}, 2),
|
||||
symmetricTestChannel("d", "c", chanCapSat, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
FeeRate: 400,
|
||||
FeeBaseMsat: 100000,
|
||||
MinHTLC: 1,
|
||||
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
||||
}, 4),
|
||||
}
|
||||
|
||||
testGraph, err := createTestGraphFromChannels(testChannels, "a")
|
||||
defer testGraph.cleanUp()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create graph: %v", err)
|
||||
}
|
||||
|
||||
const startingBlockHeight = 101
|
||||
|
||||
ctx, cleanUp, err := createTestCtxFromGraphInstance(startingBlockHeight,
|
||||
testGraph)
|
||||
|
||||
defer cleanUp()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create router: %v", err)
|
||||
}
|
||||
|
||||
// Create a payment to node c.
|
||||
payment := LightningPayment{
|
||||
Target: ctx.aliases["c"],
|
||||
Amount: lnwire.NewMSatFromSatoshis(1000),
|
||||
FeeLimit: noFeeLimit,
|
||||
PaymentHash: lntypes.Hash{},
|
||||
}
|
||||
|
||||
// We'll modify the SendToSwitch method so that it simulates hop b as a
|
||||
// node that returns an unparsable failure if approached via the a->b
|
||||
// channel.
|
||||
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||
|
||||
// If channel a->b is used, return an error without
|
||||
// source and message. The sender won't know the origin
|
||||
// of the error.
|
||||
if firstHop.ToUint64() == 1 {
|
||||
return [32]byte{},
|
||||
htlcswitch.ErrUnreadableFailureMessage
|
||||
}
|
||||
|
||||
// Otherwise the payment succeeds.
|
||||
return lntypes.Preimage{}, nil
|
||||
})
|
||||
|
||||
// Send off the payment request to the router. The expectation is that
|
||||
// the route a->b->c is tried first. An unreadable faiure is returned
|
||||
// which should pruning the channel a->b. We expect the payment to
|
||||
// succeed via a->d.
|
||||
_, _, err = ctx.router.SendPayment(&payment)
|
||||
if err != nil {
|
||||
t.Fatalf("expected payment to succeed, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// assertChannelsPruned ensures that only the given channels are pruned from the
|
||||
// graph out of the set of all channels.
|
||||
func assertChannelsPruned(t *testing.T, graph *channeldb.ChannelGraph,
|
||||
|
||||
Reference in new issue
Block a user