htlcswitch: use distinct error for unreadable failures

This commit is contained in:
Joost Jager committed 2019-07-11 19:49:45 +02:00
1 parent 0cdae56d28
commit 418fe364de
9 files changed
+396 -165

No files matched your search

+10 -19
View File
@@ -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
View File
@@ -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
+89
View File
@@ -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,