diff --git a/channeldb/migration32/migration_test.go b/channeldb/migration32/migration_test.go index 73c1cd4b8..9a9b37f16 100644 --- a/channeldb/migration32/migration_test.go +++ b/channeldb/migration32/migration_test.go @@ -118,6 +118,32 @@ var ( }, } + resultOld3 = paymentResultOld{ + id: 3, + timeFwd: time.Unix(0, 4), + timeReply: time.Unix(0, 7), + success: false, + failure: nil, + failureSourceIdx: &failureIndex, + route: &Route{ + TotalTimeLock: 101, + TotalAmount: 401, + SourcePubKey: testPub2, + Hops: []*Hop{ + { + PubKeyBytes: testPub, + ChannelID: 800, + OutgoingTimeLock: 4, + AmtToForward: 4, + BlindingPoint: pubkey, + EncryptedData: []byte{1, 2, 3}, + CustomRecords: customRecord, + TotalAmtMsat: 600, + }, + }, + }, + } + //nolint:ll resultNew1Hop1 = &mcHop{ channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](100), @@ -182,7 +208,7 @@ var ( ), failure: tlv.SomeRecordT( tlv.NewRecordT[tlv.TlvType3]( - *newPaymentFailure( + newPaymentFailure( &failureIndex, &lnwire.FailFeeInsufficient{}, ), @@ -217,6 +243,31 @@ var ( }), }), } + + //nolint:ll + resultNew3 = paymentResultNew{ + id: 3, + timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64]( + uint64(time.Unix(0, 4).UnixNano()), + ), + timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1, uint64]( + uint64(time.Unix(0, 7).UnixNano()), + ), + failure: tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType3]( + newPaymentFailure( + &failureIndex, nil, + ), + ), + ), + route: tlv.NewRecordT[tlv.TlvType2](mcRoute{ + sourcePubKey: tlv.NewRecordT[tlv.TlvType0](testPub2), + totalAmount: tlv.NewRecordT[tlv.TlvType1, lnwire.MilliSatoshi](401), + hops: tlv.NewRecordT[tlv.TlvType2](mcHops{ + resultNew2Hop1, + }), + }), + } ) // TestMigrateMCRouteSerialisation tests that the MigrateMCRouteSerialisation @@ -225,10 +276,10 @@ var ( func TestMigrateMCRouteSerialisation(t *testing.T) { var ( resultsOld = []*paymentResultOld{ - &resultOld1, &resultOld2, + &resultOld1, &resultOld2, &resultOld3, } expectedResultsNew = []*paymentResultNew{ - &resultNew1, &resultNew2, + &resultNew1, &resultNew2, &resultNew3, } ) diff --git a/channeldb/migration32/mission_control_store.go b/channeldb/migration32/mission_control_store.go index 060bb2b9c..701c4b31e 100644 --- a/channeldb/migration32/mission_control_store.go +++ b/channeldb/migration32/mission_control_store.go @@ -93,9 +93,12 @@ func deserializeOldResult(k, v []byte) (*paymentResultOld, error) { // convertPaymentResult converts a paymentResultOld to a paymentResultNew. func convertPaymentResult(old *paymentResultOld) *paymentResultNew { - var failure *paymentFailure + var failure fn.Option[paymentFailure] if !old.success { - failure = newPaymentFailure(old.failureSourceIdx, old.failure) + failure = fn.Some(newPaymentFailure( + old.failureSourceIdx, + old.failure, + )) } return newPaymentResult( @@ -106,7 +109,7 @@ func convertPaymentResult(old *paymentResultOld) *paymentResultNew { // newPaymentResult constructs a new paymentResult. func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time, - failure *paymentFailure) *paymentResultNew { + failure fn.Option[paymentFailure]) *paymentResultNew { result := &paymentResultNew{ id: id, @@ -119,11 +122,11 @@ func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time, route: tlv.NewRecordT[tlv.TlvType2](*rt), } - if failure != nil { + failure.WhenSome(func(f paymentFailure) { result.failure = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3](*failure), + tlv.NewRecordT[tlv.TlvType3](f), ) - } + }) return result } @@ -142,33 +145,49 @@ type paymentResultNew struct { failure tlv.OptionalRecordT[tlv.TlvType3, paymentFailure] } -// paymentFailure represents the presence of a payment failure. It may or may -// not include additional information about said failure. -type paymentFailure struct { - info tlv.OptionalRecordT[tlv.TlvType0, paymentFailureInfo] -} - // newPaymentFailure constructs a new paymentFailure struct. If the source // index is nil, then an empty paymentFailure is returned. This represents a // failure with unknown details. Otherwise, the index and failure message are // used to populate the info field of the paymentFailure. func newPaymentFailure(sourceIdx *int, - failureMsg lnwire.FailureMessage) *paymentFailure { + failureMsg lnwire.FailureMessage) paymentFailure { + // If we can't identify a failure source, we also won't have a decrypted + // failure message. In this case we return an empty payment failure. if sourceIdx == nil { - return &paymentFailure{} + return paymentFailure{} } - info := paymentFailureInfo{ - sourceIdx: tlv.NewPrimitiveRecord[tlv.TlvType0]( - uint8(*sourceIdx), + info := paymentFailure{ + sourceIdx: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType0]( + uint8(*sourceIdx), + ), ), - msg: tlv.NewRecordT[tlv.TlvType1](failureMessage{failureMsg}), } - return &paymentFailure{ - info: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType0](info)), + if failureMsg != nil { + info.msg = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType1]( + failureMessage{failureMsg}, + ), + ) } + + return info +} + +// paymentFailure holds additional information about a payment failure. +type paymentFailure struct { + // sourceIdx is the hop the error was reported from. In order to be able + // to decrypt the error message, we need to know the source, which is + // why an error message can only be present if the source is known. + sourceIdx tlv.OptionalRecordT[tlv.TlvType0, uint8] + + // msg is the error why a payment failed. If we identify the failure of + // a certain hop at the above index, but aren't able to decode the + // failure message we indicate this by not setting this field. + msg tlv.OptionalRecordT[tlv.TlvType1, failureMessage] } // Record returns a TLV record that can be used to encode/decode a @@ -194,14 +213,27 @@ func (r *paymentFailure) Record() tlv.Record { func encodePaymentFailure(w io.Writer, val interface{}, _ *[8]byte) error { if v, ok := val.(*paymentFailure); ok { var recordProducers []tlv.RecordProducer - v.info.WhenSome( - func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) { - recordProducers = append(recordProducers, &r) + + v.sourceIdx.WhenSome( + func(r tlv.RecordT[tlv.TlvType0, uint8]) { + recordProducers = append( + recordProducers, &r, + ) + }, + ) + + v.msg.WhenSome( + func(r tlv.RecordT[tlv.TlvType1, failureMessage]) { + recordProducers = append( + recordProducers, &r, + ) }, ) return lnwire.EncodeRecordsTo( - w, lnwire.ProduceRecordsSorted(recordProducers...), + w, lnwire.ProduceRecordsSorted( + recordProducers..., + ), ) } @@ -210,90 +242,36 @@ func encodePaymentFailure(w io.Writer, val interface{}, _ *[8]byte) error { func decodePaymentFailure(r io.Reader, val interface{}, _ *[8]byte, l uint64) error { - if v, ok := val.(*paymentFailure); ok { var h paymentFailure - info := tlv.ZeroRecordT[tlv.TlvType0, paymentFailureInfo]() + sourceIdx := tlv.ZeroRecordT[tlv.TlvType0, uint8]() + msg := tlv.ZeroRecordT[tlv.TlvType1, failureMessage]() + typeMap, err := lnwire.DecodeRecords( - r, lnwire.ProduceRecordsSorted(&info)..., - ) - if err != nil { - return err - } - - if _, ok := typeMap[h.info.TlvType()]; ok { - h.info = tlv.SomeRecordT(info) - } - - *v = h - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "routing.paymentFailure", l, l) -} - -// paymentFailureInfo holds additional information about a payment failure. -type paymentFailureInfo struct { - sourceIdx tlv.RecordT[tlv.TlvType0, uint8] - msg tlv.RecordT[tlv.TlvType1, failureMessage] -} - -// Record returns a TLV record that can be used to encode/decode a -// paymentFailureInfo to/from a TLV stream. -func (r *paymentFailureInfo) Record() tlv.Record { - recordSize := func() uint64 { - var ( - b bytes.Buffer - buf [8]byte - ) - if err := encodePaymentFailureInfo(&b, r, &buf); err != nil { - panic(err) - } - - return uint64(len(b.Bytes())) - } - - return tlv.MakeDynamicRecord( - 0, r, recordSize, encodePaymentFailureInfo, - decodePaymentFailureInfo, - ) -} - -func encodePaymentFailureInfo(w io.Writer, val interface{}, _ *[8]byte) error { - if v, ok := val.(*paymentFailureInfo); ok { - return lnwire.EncodeRecordsTo( - w, lnwire.ProduceRecordsSorted( - &v.sourceIdx, &v.msg, - ), - ) - } - - return tlv.NewTypeForEncodingErr(val, "routing.paymentFailureInfo") -} - -func decodePaymentFailureInfo(r io.Reader, val interface{}, _ *[8]byte, - l uint64) error { - - if v, ok := val.(*paymentFailureInfo); ok { - var h paymentFailureInfo - - _, err := lnwire.DecodeRecords( r, - lnwire.ProduceRecordsSorted(&h.sourceIdx, &h.msg)..., + lnwire.ProduceRecordsSorted(&sourceIdx, &msg)..., ) + if err != nil { return err } + if _, ok := typeMap[h.sourceIdx.TlvType()]; ok { + h.sourceIdx = tlv.SomeRecordT(sourceIdx) + } + + if _, ok := typeMap[h.msg.TlvType()]; ok { + h.msg = tlv.SomeRecordT(msg) + } + *v = h return nil } return tlv.NewTypeForDecodingErr( - val, "routing.paymentFailureInfo", l, l, + val, "routing.paymentFailure", l, l, ) } diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md index e2b1f0b6a..fa5012c3f 100644 --- a/docs/release-notes/release-notes-0.19.0.md +++ b/docs/release-notes/release-notes-0.19.0.md @@ -484,10 +484,12 @@ the on going rate we'll permit. ## Database -* [Migrate the mission control - store](https://github.com/lightningnetwork/lnd/pull/8911) to use a more - minimal encoding for payment attempt routes as well as use [pure TLV - encoding](https://github.com/lightningnetwork/lnd/pull/9167). +* [Migrate the mission control + store](https://github.com/lightningnetwork/lnd/pull/8911) to use a more + minimal encoding for payment attempt routes as well as use [pure TLV + encoding](https://github.com/lightningnetwork/lnd/pull/9167). [A + fix](https://github.com/lightningnetwork/lnd/pull/9770) was added to handle + nil routing failure messages and the serialization was optimized. * [Migrate the mission control store](https://github.com/lightningnetwork/lnd/pull/9001) so that results are diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 3bc9be7ab..89e14f523 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -276,7 +276,7 @@ type paymentResult struct { // newPaymentResult constructs a new paymentResult. func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time, - failure *paymentFailure) *paymentResult { + failure fn.Option[paymentFailure]) *paymentResult { result := &paymentResult{ id: id, @@ -289,11 +289,13 @@ func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time, route: tlv.NewRecordT[tlv.TlvType2](*rt), } - if failure != nil { - result.failure = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3](*failure), - ) - } + failure.WhenSome( + func(f paymentFailure) { + result.failure = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType3](f), + ) + }, + ) return result } @@ -621,7 +623,7 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route, result := newPaymentResult( paymentID, extractMCRoute(rt), timestamp, timestamp, - newPaymentFailure(failureSourceIdx, failure), + fn.Some(newPaymentFailure(failureSourceIdx, failure)), ) return m.processPaymentResult(result) @@ -635,7 +637,8 @@ func (m *MissionControl) ReportPaymentSuccess(paymentID uint64, timestamp := m.cfg.clock.Now() result := newPaymentResult( - paymentID, extractMCRoute(rt), timestamp, timestamp, nil, + paymentID, extractMCRoute(rt), timestamp, timestamp, + fn.None[paymentFailure](), ) _, err := m.processPaymentResult(result) @@ -827,33 +830,49 @@ func (n *namespacedDB) purge() error { }, func() {}) } -// paymentFailure represents the presence of a payment failure. It may or may -// not include additional information about said failure. -type paymentFailure struct { - info tlv.OptionalRecordT[tlv.TlvType0, paymentFailureInfo] -} - // newPaymentFailure constructs a new paymentFailure struct. If the source // index is nil, then an empty paymentFailure is returned. This represents a // failure with unknown details. Otherwise, the index and failure message are // used to populate the info field of the paymentFailure. func newPaymentFailure(sourceIdx *int, - failureMsg lnwire.FailureMessage) *paymentFailure { + failureMsg lnwire.FailureMessage) paymentFailure { + // If we can't identify a failure source, we also won't have a decrypted + // failure message. In this case we return an empty payment failure. if sourceIdx == nil { - return &paymentFailure{} + return paymentFailure{} } - info := paymentFailureInfo{ - sourceIdx: tlv.NewPrimitiveRecord[tlv.TlvType0]( - uint8(*sourceIdx), + info := paymentFailure{ + sourceIdx: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType0]( + uint8(*sourceIdx), + ), ), - msg: tlv.NewRecordT[tlv.TlvType1](failureMessage{failureMsg}), } - return &paymentFailure{ - info: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType0](info)), + if failureMsg != nil { + info.msg = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType1]( + failureMessage{failureMsg}, + ), + ) } + + return info +} + +// paymentFailure holds additional information about a payment failure. +type paymentFailure struct { + // sourceIdx is the hop the error was reported from. In order to be able + // to decrypt the error message, we need to know the source, which is + // why an error message can only be present if the source is known. + sourceIdx tlv.OptionalRecordT[tlv.TlvType0, uint8] + + // msg is the error why a payment failed. If we identify the failure of + // a certain hop at the above index, but aren't able to decode the + // failure message we indicate this by not setting this field. + msg tlv.OptionalRecordT[tlv.TlvType1, failureMessage] } // Record returns a TLV record that can be used to encode/decode a @@ -872,21 +891,35 @@ func (r *paymentFailure) Record() tlv.Record { } return tlv.MakeDynamicRecord( - 0, r, recordSize, encodePaymentFailure, decodePaymentFailure, + 0, r, recordSize, encodePaymentFailure, + decodePaymentFailure, ) } func encodePaymentFailure(w io.Writer, val interface{}, _ *[8]byte) error { if v, ok := val.(*paymentFailure); ok { var recordProducers []tlv.RecordProducer - v.info.WhenSome( - func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) { - recordProducers = append(recordProducers, &r) + + v.sourceIdx.WhenSome( + func(r tlv.RecordT[tlv.TlvType0, uint8]) { + recordProducers = append( + recordProducers, &r, + ) + }, + ) + + v.msg.WhenSome( + func(r tlv.RecordT[tlv.TlvType1, failureMessage]) { + recordProducers = append( + recordProducers, &r, + ) }, ) return lnwire.EncodeRecordsTo( - w, lnwire.ProduceRecordsSorted(recordProducers...), + w, lnwire.ProduceRecordsSorted( + recordProducers..., + ), ) } @@ -899,85 +932,32 @@ func decodePaymentFailure(r io.Reader, val interface{}, _ *[8]byte, if v, ok := val.(*paymentFailure); ok { var h paymentFailure - info := tlv.ZeroRecordT[tlv.TlvType0, paymentFailureInfo]() + sourceIdx := tlv.ZeroRecordT[tlv.TlvType0, uint8]() + msg := tlv.ZeroRecordT[tlv.TlvType1, failureMessage]() + typeMap, err := lnwire.DecodeRecords( - r, lnwire.ProduceRecordsSorted(&info)..., - ) - if err != nil { - return err - } - - if _, ok := typeMap[h.info.TlvType()]; ok { - h.info = tlv.SomeRecordT(info) - } - - *v = h - - return nil - } - - return tlv.NewTypeForDecodingErr(val, "routing.paymentFailure", l, l) -} - -// paymentFailureInfo holds additional information about a payment failure. -type paymentFailureInfo struct { - sourceIdx tlv.RecordT[tlv.TlvType0, uint8] - msg tlv.RecordT[tlv.TlvType1, failureMessage] -} - -// Record returns a TLV record that can be used to encode/decode a -// paymentFailureInfo to/from a TLV stream. -func (r *paymentFailureInfo) Record() tlv.Record { - recordSize := func() uint64 { - var ( - b bytes.Buffer - buf [8]byte - ) - if err := encodePaymentFailureInfo(&b, r, &buf); err != nil { - panic(err) - } - - return uint64(len(b.Bytes())) - } - - return tlv.MakeDynamicRecord( - 0, r, recordSize, encodePaymentFailureInfo, - decodePaymentFailureInfo, - ) -} - -func encodePaymentFailureInfo(w io.Writer, val interface{}, _ *[8]byte) error { - if v, ok := val.(*paymentFailureInfo); ok { - return lnwire.EncodeRecordsTo( - w, lnwire.ProduceRecordsSorted( - &v.sourceIdx, &v.msg, - ), - ) - } - - return tlv.NewTypeForEncodingErr(val, "routing.paymentFailureInfo") -} - -func decodePaymentFailureInfo(r io.Reader, val interface{}, _ *[8]byte, - l uint64) error { - - if v, ok := val.(*paymentFailureInfo); ok { - var h paymentFailureInfo - - _, err := lnwire.DecodeRecords( r, - lnwire.ProduceRecordsSorted(&h.sourceIdx, &h.msg)..., + lnwire.ProduceRecordsSorted(&sourceIdx, &msg)..., ) + if err != nil { return err } + if _, ok := typeMap[h.sourceIdx.TlvType()]; ok { + h.sourceIdx = tlv.SomeRecordT(sourceIdx) + } + + if _, ok := typeMap[h.msg.TlvType()]; ok { + h.msg = tlv.SomeRecordT(msg) + } + *v = h return nil } return tlv.NewTypeForDecodingErr( - val, "routing.paymentFailureInfo", l, l, + val, "routing.paymentFailure", l, l, ) } diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index b83a526e9..b020fcbb4 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" @@ -85,19 +86,19 @@ func TestMissionControlStore(t *testing.T) { result1 := newPaymentResult( 99, mcStoreTestRoute, testTime, testTime, - newPaymentFailure( + fn.Some(newPaymentFailure( &failureSourceIdx, lnwire.NewFailIncorrectDetails(100, 1000), - ), + )), ) result2 := newPaymentResult( 2, mcStoreTestRoute, testTime.Add(time.Hour), testTime.Add(time.Hour), - newPaymentFailure( + fn.Some(newPaymentFailure( &failureSourceIdx, lnwire.NewFailIncorrectDetails(100, 1000), - ), + )), ) // Store result. @@ -134,7 +135,7 @@ func TestMissionControlStore(t *testing.T) { ) result3.id = 3 result3.failure = tlv.SomeRecordT( - tlv.NewRecordT[tlv.TlvType3](*newPaymentFailure( + tlv.NewRecordT[tlv.TlvType3](newPaymentFailure( &failureSourceIdx, &lnwire.FailMPPTimeout{}, )), ) @@ -153,7 +154,7 @@ func TestMissionControlStore(t *testing.T) { // Also demonstrate the persistence of a success result. result4 := newPaymentResult( 5, mcStoreTestRoute, testTime.Add(3*time.Hour), - testTime.Add(3*time.Hour), nil, + testTime.Add(3*time.Hour), fn.None[paymentFailure](), ) store.AddResult(result4) require.NoError(t, store.storeResults()) @@ -186,7 +187,10 @@ func TestMissionControlStoreFlushing(t *testing.T) { return newPaymentResult( lastID, mcStoreTestRoute, testTime.Add(-time.Hour), testTime, - newPaymentFailure(&failureSourceIdx, failureDetails), + fn.Some(newPaymentFailure( + &failureSourceIdx, + failureDetails, + )), ) } @@ -287,10 +291,10 @@ func BenchmarkMissionControlStoreFlushing(b *testing.B) { result := newPaymentResult( lastID, mcStoreTestRoute, testTimeFwd, testTime, - newPaymentFailure( + fn.Some(newPaymentFailure( &failureSourceIdx, failureDetails, - ), + )), ) store.AddResult(result) } @@ -305,10 +309,10 @@ func BenchmarkMissionControlStoreFlushing(b *testing.B) { results[i] = newPaymentResult( 0, mcStoreTestRoute, testTimeFwd, testTime, - newPaymentFailure( + fn.Some(newPaymentFailure( &failureSourceIdx, failureDetails, - ), + )), ) } diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 60dd22c43..55c4246f5 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -126,7 +126,9 @@ func (i *interpretedResult) processSuccess(route *mcRoute) { // processFail processes a failed payment attempt. func (i *interpretedResult) processFail(rt *mcRoute, failure paymentFailure) { - if failure.info.IsNone() { + // Not having a source index means that we were unable to decrypt the + // error message. + if failure.sourceIdx.IsNone() { i.processPaymentOutcomeUnknown(rt) return } @@ -136,10 +138,17 @@ func (i *interpretedResult) processFail(rt *mcRoute, failure paymentFailure) { failMsg lnwire.FailureMessage ) - failure.info.WhenSome( - func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) { - idx = int(r.Val.sourceIdx.Val) - failMsg = r.Val.msg.Val.FailureMessage + failure.sourceIdx.WhenSome( + func(r tlv.RecordT[tlv.TlvType0, uint8]) { + idx = int(r.Val) + + failure.msg.WhenSome( + func(r tlv.RecordT[tlv.TlvType1, + failureMessage]) { + + failMsg = r.Val.FailureMessage + }, + ) }, ) diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index a0c3e9062..000093b07 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -732,7 +732,7 @@ func TestResultInterpretation(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { var failure fn.Option[paymentFailure] if !testCase.success { - failure = fn.Some(*newPaymentFailure( + failure = fn.Some(newPaymentFailure( &testCase.failureSrcIdx, testCase.failure, ))