From e0c86f1e7112484ea1dc36b078b81619b7dea5b7 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH] htlcswitch+invoices: fail mpp timeouts with FailMPPTimeout This commit adds a getResolutionFailure function which returns an appropriate wire failure based on the outcome of a htlc resolution. It also updates the MissionControlStore test to ensure that lnd can handle failures which occur due to mpp timeout. --- htlcswitch/link.go | 27 +++++++++++++++++++++------ routing/missioncontrol_store_test.go | 9 +++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 005b2e3b..0bba3f65 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1209,12 +1209,8 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, l.log.Debugf("received cancel resolution for %v with outcome: %v", circuitKey, resolution.Outcome) - // The htlc has failed so we cancel it with FailIncorrectDetails. This - // error covers invoice failures and hodl cancels (which return it to avoid - // leaking information). - failure := lnwire.NewFailIncorrectDetails( - htlc.pd.Amount, uint32(resolution.AcceptHeight), - ) + // Get the lnwire failure message based on the resolution result. + failure := getResolutionFailure(resolution, htlc.pd.Amount) l.sendHTLCError( htlc.pd.HtlcIndex, failure, htlc.obfuscator, @@ -1223,6 +1219,25 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, return nil } +// getResolutionFailure returns the wire message that a htlc resolution should +// be failed with. +func getResolutionFailure(resolution invoices.HtlcResolution, + amount lnwire.MilliSatoshi) lnwire.FailureMessage { + + // If the resolution has been resolved as part of a MPP timeout, we need + // to fail the htlc with lnwire.FailMppTimeout. + if resolution.Outcome == invoices.ResultMppTimeout { + return &lnwire.FailMPPTimeout{} + } + + // If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails + // This covers hodl cancels (which return it to avoid leaking information + // and other invoice failures such as underpayment or expiry too soon. + return lnwire.NewFailIncorrectDetails( + amount, uint32(resolution.AcceptHeight), + ) +} + // randomFeeUpdateTimeout returns a random timeout between the bounds defined // within the link's configuration that will be used to determine when the link // should propose an update to its commitment fee rate. diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 9d070f4d..e44c5919 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -16,8 +16,12 @@ import ( const testMaxRecords = 2 +// TestMissionControlStore tests the recording of payment failure events +// in mission control. It tests encoding and decoding of differing lnwire +// failures (FailIncorrectDetails and FailMppTimeout), pruning of results +// and idempotent writes. func TestMissionControlStore(t *testing.T) { - // Set time zone explictly to keep test deterministic. + // Set time zone explicitly to keep test deterministic. time.Local = time.UTC file, err := ioutil.TempFile("", "*.db") @@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) { t.Fatal(err) } - // Add a newer result. + // Add a newer result which failed due to mpp timeout. result3 := result1 result3.timeReply = result1.timeReply.Add(2 * time.Hour) result3.timeFwd = result1.timeReply.Add(2 * time.Hour) result3.id = 3 + result3.failure = &lnwire.FailMPPTimeout{} err = store.AddResult(&result3) if err != nil {