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.
This commit is contained in:
carla 2019-12-20 12:25:08 +02:00
parent 1d3bb5aed6
commit e0c86f1e71
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
2 changed files with 28 additions and 8 deletions

@ -1209,12 +1209,8 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
l.log.Debugf("received cancel resolution for %v with outcome: %v", l.log.Debugf("received cancel resolution for %v with outcome: %v",
circuitKey, resolution.Outcome) circuitKey, resolution.Outcome)
// The htlc has failed so we cancel it with FailIncorrectDetails. This // Get the lnwire failure message based on the resolution result.
// error covers invoice failures and hodl cancels (which return it to avoid failure := getResolutionFailure(resolution, htlc.pd.Amount)
// leaking information).
failure := lnwire.NewFailIncorrectDetails(
htlc.pd.Amount, uint32(resolution.AcceptHeight),
)
l.sendHTLCError( l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator, htlc.pd.HtlcIndex, failure, htlc.obfuscator,
@ -1223,6 +1219,25 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
return nil 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 // randomFeeUpdateTimeout returns a random timeout between the bounds defined
// within the link's configuration that will be used to determine when the link // within the link's configuration that will be used to determine when the link
// should propose an update to its commitment fee rate. // should propose an update to its commitment fee rate.

@ -16,8 +16,12 @@ import (
const testMaxRecords = 2 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) { 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 time.Local = time.UTC
file, err := ioutil.TempFile("", "*.db") file, err := ioutil.TempFile("", "*.db")
@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Add a newer result. // Add a newer result which failed due to mpp timeout.
result3 := result1 result3 := result1
result3.timeReply = result1.timeReply.Add(2 * time.Hour) result3.timeReply = result1.timeReply.Add(2 * time.Hour)
result3.timeFwd = result1.timeReply.Add(2 * time.Hour) result3.timeFwd = result1.timeReply.Add(2 * time.Hour)
result3.id = 3 result3.id = 3
result3.failure = &lnwire.FailMPPTimeout{}
err = store.AddResult(&result3) err = store.AddResult(&result3)
if err != nil { if err != nil {