mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-19 12:01:27 +02:00
routing+migration32: update MC encoding to use pure TLV
In this commit, we update an existing migration which at the time of writing has not been included in a release. We update it so that it converts the format used for MissionControl result encoding to use pure TLV instead. The 3 structs that have been updated are: `mcHop`, `mcRoute` and `paymentResult`.
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migtest"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,20 +25,197 @@ var (
|
||||
_ = pubKeyY.SetByteSlice(pubkeyBytes)
|
||||
pubkey = btcec.NewPublicKey(new(btcec.FieldVal).SetInt(4), pubKeyY)
|
||||
|
||||
paymentResultCommon1 = paymentResultCommon{
|
||||
customRecord = map[uint64][]byte{
|
||||
65536: {4, 2, 2},
|
||||
}
|
||||
|
||||
resultOld1 = paymentResultOld{
|
||||
id: 0,
|
||||
timeFwd: time.Unix(0, 1),
|
||||
timeReply: time.Unix(0, 2),
|
||||
success: false,
|
||||
failureSourceIdx: &failureIndex,
|
||||
failure: &lnwire.FailFeeInsufficient{},
|
||||
route: &Route{
|
||||
TotalTimeLock: 100,
|
||||
TotalAmount: 400,
|
||||
SourcePubKey: testPub,
|
||||
Hops: []*Hop{
|
||||
// A hop with MPP, AMP and custom
|
||||
// records.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 100,
|
||||
OutgoingTimeLock: 300,
|
||||
AmtToForward: 500,
|
||||
MPP: &MPP{
|
||||
paymentAddr: [32]byte{4, 5},
|
||||
totalMsat: 900,
|
||||
},
|
||||
AMP: &{
|
||||
rootShare: [32]byte{0, 0},
|
||||
setID: [32]byte{5, 5, 5},
|
||||
childIndex: 90,
|
||||
},
|
||||
CustomRecords: customRecord,
|
||||
Metadata: []byte{6, 7, 7},
|
||||
},
|
||||
// A legacy hop.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
LegacyPayload: true,
|
||||
},
|
||||
// A hop with a blinding key.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
BlindingPoint: pubkey,
|
||||
EncryptedData: []byte{1, 2, 3},
|
||||
TotalAmtMsat: 600,
|
||||
},
|
||||
// A hop with a blinding key and custom
|
||||
// records.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
CustomRecords: customRecord,
|
||||
BlindingPoint: pubkey,
|
||||
EncryptedData: []byte{1, 2, 3},
|
||||
TotalAmtMsat: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
paymentResultCommon2 = paymentResultCommon{
|
||||
resultOld2 = paymentResultOld{
|
||||
id: 2,
|
||||
timeFwd: time.Unix(0, 4),
|
||||
timeReply: time.Unix(0, 7),
|
||||
success: true,
|
||||
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:lll
|
||||
resultNew1Hop1 = &mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](100),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](testPub),
|
||||
amtToFwd: tlv.NewPrimitiveRecord[tlv.TlvType2, lnwire.MilliSatoshi](500),
|
||||
hasCustomRecords: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean](),
|
||||
),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew1Hop2 = &mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](800),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](testPub),
|
||||
amtToFwd: tlv.NewPrimitiveRecord[tlv.TlvType2, lnwire.MilliSatoshi](4),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew1Hop3 = &mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](800),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](testPub),
|
||||
amtToFwd: tlv.NewPrimitiveRecord[tlv.TlvType2, lnwire.MilliSatoshi](4),
|
||||
hasBlindingPoint: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean](),
|
||||
),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew1Hop4 = &mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](800),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](testPub),
|
||||
amtToFwd: tlv.NewPrimitiveRecord[tlv.TlvType2, lnwire.MilliSatoshi](4),
|
||||
hasCustomRecords: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean](),
|
||||
),
|
||||
hasBlindingPoint: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean](),
|
||||
),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew2Hop1 = &mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](800),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](testPub),
|
||||
amtToFwd: tlv.NewPrimitiveRecord[tlv.TlvType2, lnwire.MilliSatoshi](4),
|
||||
hasCustomRecords: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean](),
|
||||
),
|
||||
hasBlindingPoint: tlv.SomeRecordT(
|
||||
tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean](),
|
||||
),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew1 = paymentResultNew{
|
||||
id: 0,
|
||||
timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||
uint64(time.Unix(0, 1).UnixNano()),
|
||||
),
|
||||
timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
uint64(time.Unix(0, 2).UnixNano()),
|
||||
),
|
||||
failure: tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType3](
|
||||
*newPaymentFailure(
|
||||
&failureIndex,
|
||||
&lnwire.FailFeeInsufficient{},
|
||||
),
|
||||
),
|
||||
),
|
||||
route: tlv.NewRecordT[tlv.TlvType2](mcRoute{
|
||||
sourcePubKey: tlv.NewRecordT[tlv.TlvType0](testPub),
|
||||
totalAmount: tlv.NewRecordT[tlv.TlvType1, lnwire.MilliSatoshi](400),
|
||||
hops: tlv.NewRecordT[tlv.TlvType2, mcHops](mcHops{
|
||||
resultNew1Hop1,
|
||||
resultNew1Hop2,
|
||||
resultNew1Hop3,
|
||||
resultNew1Hop4,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
resultNew2 = paymentResultNew{
|
||||
id: 2,
|
||||
timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](
|
||||
uint64(time.Unix(0, 4).UnixNano()),
|
||||
),
|
||||
timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1, uint64](
|
||||
uint64(time.Unix(0, 7).UnixNano()),
|
||||
),
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -45,153 +223,14 @@ var (
|
||||
// migration function correctly migrates the MC store from using the old route
|
||||
// encoding to using the newer, more minimal route encoding.
|
||||
func TestMigrateMCRouteSerialisation(t *testing.T) {
|
||||
customRecord := map[uint64][]byte{
|
||||
65536: {4, 2, 2},
|
||||
}
|
||||
|
||||
resultsOld := []*paymentResultOld{
|
||||
{
|
||||
paymentResultCommon: paymentResultCommon1,
|
||||
route: &Route{
|
||||
TotalTimeLock: 100,
|
||||
TotalAmount: 400,
|
||||
SourcePubKey: testPub,
|
||||
Hops: []*Hop{
|
||||
// A hop with MPP, AMP and custom
|
||||
// records.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 100,
|
||||
OutgoingTimeLock: 300,
|
||||
AmtToForward: 500,
|
||||
MPP: &MPP{
|
||||
paymentAddr: [32]byte{
|
||||
4, 5,
|
||||
},
|
||||
totalMsat: 900,
|
||||
},
|
||||
AMP: &{
|
||||
rootShare: [32]byte{
|
||||
0, 0,
|
||||
},
|
||||
setID: [32]byte{
|
||||
5, 5, 5,
|
||||
},
|
||||
childIndex: 90,
|
||||
},
|
||||
CustomRecords: customRecord,
|
||||
Metadata: []byte{6, 7, 7},
|
||||
},
|
||||
// A legacy hop.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
LegacyPayload: true,
|
||||
},
|
||||
// A hop with a blinding key.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
BlindingPoint: pubkey,
|
||||
EncryptedData: []byte{
|
||||
1, 2, 3,
|
||||
},
|
||||
TotalAmtMsat: 600,
|
||||
},
|
||||
// A hop with a blinding key and custom
|
||||
// records.
|
||||
{
|
||||
PubKeyBytes: testPub,
|
||||
ChannelID: 800,
|
||||
OutgoingTimeLock: 4,
|
||||
AmtToForward: 4,
|
||||
CustomRecords: customRecord,
|
||||
BlindingPoint: pubkey,
|
||||
EncryptedData: []byte{
|
||||
1, 2, 3,
|
||||
},
|
||||
TotalAmtMsat: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
paymentResultCommon: paymentResultCommon2,
|
||||
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,
|
||||
},
|
||||
TotalAmtMsat: 600,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedResultsNew := []*paymentResultNew{
|
||||
{
|
||||
paymentResultCommon: paymentResultCommon1,
|
||||
route: &mcRoute{
|
||||
sourcePubKey: testPub,
|
||||
totalAmount: 400,
|
||||
hops: []*mcHop{
|
||||
{
|
||||
channelID: 100,
|
||||
pubKeyBytes: testPub,
|
||||
amtToFwd: 500,
|
||||
hasCustomRecords: true,
|
||||
},
|
||||
{
|
||||
channelID: 800,
|
||||
pubKeyBytes: testPub,
|
||||
amtToFwd: 4,
|
||||
},
|
||||
{
|
||||
channelID: 800,
|
||||
pubKeyBytes: testPub,
|
||||
amtToFwd: 4,
|
||||
hasBlindingPoint: true,
|
||||
},
|
||||
{
|
||||
channelID: 800,
|
||||
pubKeyBytes: testPub,
|
||||
amtToFwd: 4,
|
||||
hasBlindingPoint: true,
|
||||
hasCustomRecords: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
paymentResultCommon: paymentResultCommon2,
|
||||
route: &mcRoute{
|
||||
sourcePubKey: testPub2,
|
||||
totalAmount: 401,
|
||||
hops: []*mcHop{
|
||||
{
|
||||
channelID: 800,
|
||||
pubKeyBytes: testPub,
|
||||
amtToFwd: 4,
|
||||
hasBlindingPoint: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var (
|
||||
resultsOld = []*paymentResultOld{
|
||||
&resultOld1, &resultOld2,
|
||||
}
|
||||
expectedResultsNew = []*paymentResultNew{
|
||||
&resultNew1, &resultNew2,
|
||||
}
|
||||
)
|
||||
|
||||
// Prime the database with some mission control data that uses the
|
||||
// old route encoding.
|
||||
|
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,30 +24,22 @@ var (
|
||||
resultsKey = []byte("missioncontrol-results")
|
||||
)
|
||||
|
||||
// paymentResultCommon holds the fields that are shared by the old and new
|
||||
// payment result encoding.
|
||||
type paymentResultCommon struct {
|
||||
id uint64
|
||||
timeFwd, timeReply time.Time
|
||||
success bool
|
||||
failureSourceIdx *int
|
||||
failure lnwire.FailureMessage
|
||||
}
|
||||
|
||||
// paymentResultOld is the information that becomes available when a payment
|
||||
// attempt completes.
|
||||
type paymentResultOld struct {
|
||||
paymentResultCommon
|
||||
route *Route
|
||||
id uint64
|
||||
timeFwd, timeReply time.Time
|
||||
route *Route
|
||||
success bool
|
||||
failureSourceIdx *int
|
||||
failure lnwire.FailureMessage
|
||||
}
|
||||
|
||||
// deserializeOldResult deserializes a payment result using the old encoding.
|
||||
func deserializeOldResult(k, v []byte) (*paymentResultOld, error) {
|
||||
// Parse payment id.
|
||||
result := paymentResultOld{
|
||||
paymentResultCommon: paymentResultCommon{
|
||||
id: byteOrder.Uint64(k[8:]),
|
||||
},
|
||||
id: byteOrder.Uint64(k[8:]),
|
||||
}
|
||||
|
||||
r := bytes.NewReader(v)
|
||||
@@ -99,67 +93,563 @@ func deserializeOldResult(k, v []byte) (*paymentResultOld, error) {
|
||||
|
||||
// convertPaymentResult converts a paymentResultOld to a paymentResultNew.
|
||||
func convertPaymentResult(old *paymentResultOld) *paymentResultNew {
|
||||
return &paymentResultNew{
|
||||
paymentResultCommon: old.paymentResultCommon,
|
||||
route: extractMCRoute(old.route),
|
||||
var failure *paymentFailure
|
||||
if !old.success {
|
||||
failure = newPaymentFailure(old.failureSourceIdx, old.failure)
|
||||
}
|
||||
|
||||
return newPaymentResult(
|
||||
old.id, extractMCRoute(old.route), old.timeFwd, old.timeReply,
|
||||
failure,
|
||||
)
|
||||
}
|
||||
|
||||
// newPaymentResult constructs a new paymentResult.
|
||||
func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time,
|
||||
failure *paymentFailure) *paymentResultNew {
|
||||
|
||||
result := &paymentResultNew{
|
||||
id: id,
|
||||
timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||
uint64(timeFwd.UnixNano()),
|
||||
),
|
||||
timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1](
|
||||
uint64(timeReply.UnixNano()),
|
||||
),
|
||||
route: tlv.NewRecordT[tlv.TlvType2](*rt),
|
||||
}
|
||||
|
||||
if failure != nil {
|
||||
result.failure = tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType3](*failure),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// paymentResultNew is the information that becomes available when a payment
|
||||
// attempt completes.
|
||||
type paymentResultNew struct {
|
||||
paymentResultCommon
|
||||
route *mcRoute
|
||||
id uint64
|
||||
timeFwd tlv.RecordT[tlv.TlvType0, uint64]
|
||||
timeReply tlv.RecordT[tlv.TlvType1, uint64]
|
||||
route tlv.RecordT[tlv.TlvType2, mcRoute]
|
||||
|
||||
// failure holds information related to the failure of a payment. The
|
||||
// presence of this record indicates a payment failure. The absence of
|
||||
// this record indicates a successful payment.
|
||||
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 {
|
||||
|
||||
if sourceIdx == nil {
|
||||
return &paymentFailure{}
|
||||
}
|
||||
|
||||
info := paymentFailureInfo{
|
||||
sourceIdx: tlv.NewPrimitiveRecord[tlv.TlvType0](
|
||||
uint8(*sourceIdx),
|
||||
),
|
||||
msg: tlv.NewRecordT[tlv.TlvType1](failureMessage{failureMsg}),
|
||||
}
|
||||
|
||||
return &paymentFailure{
|
||||
info: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType0](info)),
|
||||
}
|
||||
}
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode a
|
||||
// paymentFailure to/from a TLV stream.
|
||||
func (r *paymentFailure) Record() tlv.Record {
|
||||
recordSize := func() uint64 {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
buf [8]byte
|
||||
)
|
||||
if err := encodePaymentFailure(&b, r, &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uint64(len(b.Bytes()))
|
||||
}
|
||||
|
||||
return tlv.MakeDynamicRecord(
|
||||
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)
|
||||
},
|
||||
)
|
||||
|
||||
return lnwire.EncodeRecordsTo(
|
||||
w, lnwire.ProduceRecordsSorted(recordProducers...),
|
||||
)
|
||||
}
|
||||
|
||||
return tlv.NewTypeForEncodingErr(val, "routing.paymentFailure")
|
||||
}
|
||||
|
||||
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]()
|
||||
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)...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = h
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForDecodingErr(
|
||||
val, "routing.paymentFailureInfo", l, l,
|
||||
)
|
||||
}
|
||||
|
||||
type failureMessage struct {
|
||||
lnwire.FailureMessage
|
||||
}
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode a list of
|
||||
// failureMessage to/from a TLV stream.
|
||||
func (r *failureMessage) Record() tlv.Record {
|
||||
recordSize := func() uint64 {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
buf [8]byte
|
||||
)
|
||||
if err := encodeFailureMessage(&b, r, &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uint64(len(b.Bytes()))
|
||||
}
|
||||
|
||||
return tlv.MakeDynamicRecord(
|
||||
0, r, recordSize, encodeFailureMessage, decodeFailureMessage,
|
||||
)
|
||||
}
|
||||
|
||||
func encodeFailureMessage(w io.Writer, val interface{}, _ *[8]byte) error {
|
||||
if v, ok := val.(*failureMessage); ok {
|
||||
var b bytes.Buffer
|
||||
err := lnwire.EncodeFailureMessage(&b, v.FailureMessage, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(b.Bytes())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return tlv.NewTypeForEncodingErr(val, "routing.failureMessage")
|
||||
}
|
||||
|
||||
func decodeFailureMessage(r io.Reader, val interface{}, _ *[8]byte,
|
||||
l uint64) error {
|
||||
|
||||
if v, ok := val.(*failureMessage); ok {
|
||||
msg, err := lnwire.DecodeFailureMessage(r, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = failureMessage{
|
||||
FailureMessage: msg,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForDecodingErr(val, "routing.failureMessage", l, l)
|
||||
}
|
||||
|
||||
// extractMCRoute extracts the fields required by MC from the Route struct to
|
||||
// create the more minimal mcRoute struct.
|
||||
func extractMCRoute(route *Route) *mcRoute {
|
||||
func extractMCRoute(r *Route) *mcRoute {
|
||||
return &mcRoute{
|
||||
sourcePubKey: route.SourcePubKey,
|
||||
totalAmount: route.TotalAmount,
|
||||
hops: extractMCHops(route.Hops),
|
||||
sourcePubKey: tlv.NewRecordT[tlv.TlvType0](r.SourcePubKey),
|
||||
totalAmount: tlv.NewRecordT[tlv.TlvType1](r.TotalAmount),
|
||||
hops: tlv.NewRecordT[tlv.TlvType2](
|
||||
extractMCHops(r.Hops),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// extractMCHops extracts the Hop fields that MC actually uses from a slice of
|
||||
// Hops.
|
||||
func extractMCHops(hops []*Hop) []*mcHop {
|
||||
mcHops := make([]*mcHop, len(hops))
|
||||
for i, hop := range hops {
|
||||
mcHops[i] = extractMCHop(hop)
|
||||
}
|
||||
|
||||
return mcHops
|
||||
func extractMCHops(hops []*Hop) mcHops {
|
||||
return fn.Map(extractMCHop, hops)
|
||||
}
|
||||
|
||||
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
|
||||
func extractMCHop(hop *Hop) *mcHop {
|
||||
return &mcHop{
|
||||
channelID: hop.ChannelID,
|
||||
pubKeyBytes: hop.PubKeyBytes,
|
||||
amtToFwd: hop.AmtToForward,
|
||||
hasBlindingPoint: hop.BlindingPoint != nil,
|
||||
hasCustomRecords: len(hop.CustomRecords) > 0,
|
||||
h := mcHop{
|
||||
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](
|
||||
hop.ChannelID,
|
||||
),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1, Vertex](
|
||||
hop.PubKeyBytes,
|
||||
),
|
||||
amtToFwd: tlv.NewRecordT[tlv.TlvType2, lnwire.MilliSatoshi](
|
||||
hop.AmtToForward,
|
||||
),
|
||||
}
|
||||
|
||||
if hop.BlindingPoint != nil {
|
||||
h.hasBlindingPoint = tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType3, lnwire.TrueBoolean](
|
||||
lnwire.TrueBoolean{},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if len(hop.CustomRecords) != 0 {
|
||||
h.hasCustomRecords = tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType4, lnwire.TrueBoolean](
|
||||
lnwire.TrueBoolean{},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return &h
|
||||
}
|
||||
|
||||
// mcRoute holds the bare minimum info about a payment attempt route that MC
|
||||
// requires.
|
||||
type mcRoute struct {
|
||||
sourcePubKey Vertex
|
||||
totalAmount lnwire.MilliSatoshi
|
||||
hops []*mcHop
|
||||
sourcePubKey tlv.RecordT[tlv.TlvType0, Vertex]
|
||||
totalAmount tlv.RecordT[tlv.TlvType1, lnwire.MilliSatoshi]
|
||||
hops tlv.RecordT[tlv.TlvType2, mcHops]
|
||||
}
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode an mcRoute
|
||||
// to/from a TLV stream.
|
||||
func (r *mcRoute) Record() tlv.Record {
|
||||
recordSize := func() uint64 {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
buf [8]byte
|
||||
)
|
||||
if err := encodeMCRoute(&b, r, &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uint64(len(b.Bytes()))
|
||||
}
|
||||
|
||||
return tlv.MakeDynamicRecord(
|
||||
0, r, recordSize, encodeMCRoute, decodeMCRoute,
|
||||
)
|
||||
}
|
||||
|
||||
func encodeMCRoute(w io.Writer, val interface{}, _ *[8]byte) error {
|
||||
if v, ok := val.(*mcRoute); ok {
|
||||
return serializeRoute(w, v)
|
||||
}
|
||||
|
||||
return tlv.NewTypeForEncodingErr(val, "routing.mcRoute")
|
||||
}
|
||||
|
||||
func decodeMCRoute(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
|
||||
if v, ok := val.(*mcRoute); ok {
|
||||
route, err := deserializeRoute(io.LimitReader(r, int64(l)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = *route
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForDecodingErr(val, "routing.mcRoute", l, l)
|
||||
}
|
||||
|
||||
// mcHops is a list of mcHop records.
|
||||
type mcHops []*mcHop
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode a list of
|
||||
// mcHop to/from a TLV stream.
|
||||
func (h *mcHops) Record() tlv.Record {
|
||||
recordSize := func() uint64 {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
buf [8]byte
|
||||
)
|
||||
if err := encodeMCHops(&b, h, &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uint64(len(b.Bytes()))
|
||||
}
|
||||
|
||||
return tlv.MakeDynamicRecord(
|
||||
0, h, recordSize, encodeMCHops, decodeMCHops,
|
||||
)
|
||||
}
|
||||
|
||||
func encodeMCHops(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*mcHops); ok {
|
||||
// Encode the number of hops as a var int.
|
||||
if err := tlv.WriteVarInt(w, uint64(len(*v)), buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With that written out, we'll now encode the entries
|
||||
// themselves as a sub-TLV record, which includes its _own_
|
||||
// inner length prefix.
|
||||
for _, hop := range *v {
|
||||
var hopBytes bytes.Buffer
|
||||
if err := serializeNewHop(&hopBytes, hop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We encode the record with a varint length followed by
|
||||
// the _raw_ TLV bytes.
|
||||
tlvLen := uint64(len(hopBytes.Bytes()))
|
||||
if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(hopBytes.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForEncodingErr(val, "routing.mcHops")
|
||||
}
|
||||
|
||||
func decodeMCHops(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
if v, ok := val.(*mcHops); ok {
|
||||
// First, we'll decode the varint that encodes how many hops
|
||||
// are encoded in the stream.
|
||||
numHops, err := tlv.ReadVarInt(r, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that we know how many records we'll need to read, we can
|
||||
// iterate and read them all out in series.
|
||||
for i := uint64(0); i < numHops; i++ {
|
||||
// Read out the varint that encodes the size of this
|
||||
// inner TLV record.
|
||||
hopSize, err := tlv.ReadVarInt(r, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Using this information, we'll create a new limited
|
||||
// reader that'll return an EOF once the end has been
|
||||
// reached so the stream stops consuming bytes.
|
||||
innerTlvReader := &io.LimitedReader{
|
||||
R: r,
|
||||
N: int64(hopSize),
|
||||
}
|
||||
|
||||
hop, err := deserializeNewHop(innerTlvReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = append(*v, hop)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForDecodingErr(val, "routing.mcHops", l, l)
|
||||
}
|
||||
|
||||
// serializeRoute serializes a mcRoute and writes the resulting bytes to the
|
||||
// given io.Writer.
|
||||
func serializeRoute(w io.Writer, r *mcRoute) error {
|
||||
records := lnwire.ProduceRecordsSorted(
|
||||
&r.sourcePubKey,
|
||||
&r.totalAmount,
|
||||
&r.hops,
|
||||
)
|
||||
|
||||
return lnwire.EncodeRecordsTo(w, records)
|
||||
}
|
||||
|
||||
// deserializeRoute deserializes the mcRoute from the given io.Reader.
|
||||
func deserializeRoute(r io.Reader) (*mcRoute, error) {
|
||||
var rt mcRoute
|
||||
records := lnwire.ProduceRecordsSorted(
|
||||
&rt.sourcePubKey,
|
||||
&rt.totalAmount,
|
||||
&rt.hops,
|
||||
)
|
||||
|
||||
_, err := lnwire.DecodeRecords(r, records...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &rt, nil
|
||||
}
|
||||
|
||||
// deserializeNewHop deserializes the mcHop from the given io.Reader.
|
||||
func deserializeNewHop(r io.Reader) (*mcHop, error) {
|
||||
var (
|
||||
h mcHop
|
||||
blinding = tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean]()
|
||||
custom = tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean]()
|
||||
)
|
||||
records := lnwire.ProduceRecordsSorted(
|
||||
&h.channelID,
|
||||
&h.pubKeyBytes,
|
||||
&h.amtToFwd,
|
||||
&blinding,
|
||||
&custom,
|
||||
)
|
||||
|
||||
typeMap, err := lnwire.DecodeRecords(r, records...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := typeMap[h.hasBlindingPoint.TlvType()]; ok {
|
||||
h.hasBlindingPoint = tlv.SomeRecordT(blinding)
|
||||
}
|
||||
|
||||
if _, ok := typeMap[h.hasCustomRecords.TlvType()]; ok {
|
||||
h.hasCustomRecords = tlv.SomeRecordT(custom)
|
||||
}
|
||||
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// serializeNewHop serializes a mcHop and writes the resulting bytes to the
|
||||
// given io.Writer.
|
||||
func serializeNewHop(w io.Writer, h *mcHop) error {
|
||||
recordProducers := []tlv.RecordProducer{
|
||||
&h.channelID,
|
||||
&h.pubKeyBytes,
|
||||
&h.amtToFwd,
|
||||
}
|
||||
|
||||
h.hasBlindingPoint.WhenSome(func(
|
||||
hasBlinding tlv.RecordT[tlv.TlvType3, lnwire.TrueBoolean]) {
|
||||
|
||||
recordProducers = append(recordProducers, &hasBlinding)
|
||||
})
|
||||
|
||||
h.hasCustomRecords.WhenSome(func(
|
||||
hasCustom tlv.RecordT[tlv.TlvType4, lnwire.TrueBoolean]) {
|
||||
|
||||
recordProducers = append(recordProducers, &hasCustom)
|
||||
})
|
||||
|
||||
return lnwire.EncodeRecordsTo(
|
||||
w, lnwire.ProduceRecordsSorted(recordProducers...),
|
||||
)
|
||||
}
|
||||
|
||||
// mcHop holds the bare minimum info about a payment attempt route hop that MC
|
||||
// requires.
|
||||
type mcHop struct {
|
||||
channelID uint64
|
||||
pubKeyBytes Vertex
|
||||
amtToFwd lnwire.MilliSatoshi
|
||||
hasBlindingPoint bool
|
||||
hasCustomRecords bool
|
||||
channelID tlv.RecordT[tlv.TlvType0, uint64]
|
||||
pubKeyBytes tlv.RecordT[tlv.TlvType1, Vertex]
|
||||
amtToFwd tlv.RecordT[tlv.TlvType2, lnwire.MilliSatoshi]
|
||||
hasBlindingPoint tlv.OptionalRecordT[tlv.TlvType3, lnwire.TrueBoolean]
|
||||
hasCustomRecords tlv.OptionalRecordT[tlv.TlvType4, lnwire.TrueBoolean]
|
||||
}
|
||||
|
||||
// serializeOldResult serializes a payment result and returns a key and value
|
||||
@@ -225,48 +715,30 @@ func getResultKeyOld(rp *paymentResultOld) []byte {
|
||||
// serializeNewResult serializes a payment result and returns a key and value
|
||||
// byte slice to insert into the bucket.
|
||||
func serializeNewResult(rp *paymentResultNew) ([]byte, []byte, error) {
|
||||
// Write timestamps, success status, failure source index and route.
|
||||
var b bytes.Buffer
|
||||
|
||||
var dbFailureSourceIdx int32
|
||||
if rp.failureSourceIdx == nil {
|
||||
dbFailureSourceIdx = unknownFailureSourceIdx
|
||||
} else {
|
||||
dbFailureSourceIdx = int32(*rp.failureSourceIdx)
|
||||
recordProducers := []tlv.RecordProducer{
|
||||
&rp.timeFwd,
|
||||
&rp.timeReply,
|
||||
&rp.route,
|
||||
}
|
||||
|
||||
err := WriteElements(
|
||||
&b,
|
||||
uint64(rp.timeFwd.UnixNano()),
|
||||
uint64(rp.timeReply.UnixNano()),
|
||||
rp.success, dbFailureSourceIdx,
|
||||
rp.failure.WhenSome(
|
||||
func(failure tlv.RecordT[tlv.TlvType3, paymentFailure]) {
|
||||
recordProducers = append(recordProducers, &failure)
|
||||
},
|
||||
)
|
||||
|
||||
// Compose key that identifies this result.
|
||||
key := getResultKeyNew(rp)
|
||||
|
||||
var buff bytes.Buffer
|
||||
err := lnwire.EncodeRecordsTo(
|
||||
&buff, lnwire.ProduceRecordsSorted(recordProducers...),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := serializeMCRoute(&b, rp.route); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Write failure. If there is no failure message, write an empty
|
||||
// byte slice.
|
||||
var failureBytes bytes.Buffer
|
||||
if rp.failure != nil {
|
||||
err := lnwire.EncodeFailureMessage(&failureBytes, rp.failure, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
err = wire.WriteVarBytes(&b, 0, failureBytes.Bytes())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Compose key that identifies this result.
|
||||
key := getResultKeyNew(rp)
|
||||
|
||||
return key, b.Bytes(), nil
|
||||
return key, buff.Bytes(), err
|
||||
}
|
||||
|
||||
// getResultKeyNew returns a byte slice representing a unique key for this
|
||||
@@ -278,43 +750,9 @@ func getResultKeyNew(rp *paymentResultNew) []byte {
|
||||
// key. This allows importing mission control data from an external
|
||||
// source without key collisions and keeps the records sorted
|
||||
// chronologically.
|
||||
byteOrder.PutUint64(keyBytes[:], uint64(rp.timeReply.UnixNano()))
|
||||
byteOrder.PutUint64(keyBytes[:], rp.timeReply.Val)
|
||||
byteOrder.PutUint64(keyBytes[8:], rp.id)
|
||||
copy(keyBytes[16:], rp.route.sourcePubKey[:])
|
||||
copy(keyBytes[16:], rp.route.Val.sourcePubKey.Val[:])
|
||||
|
||||
return keyBytes[:]
|
||||
}
|
||||
|
||||
// serializeMCRoute serializes an mcRoute and writes the bytes to the given
|
||||
// io.Writer.
|
||||
func serializeMCRoute(w io.Writer, r *mcRoute) error {
|
||||
if err := WriteElements(
|
||||
w, r.totalAmount, r.sourcePubKey[:],
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := WriteElements(w, uint32(len(r.hops))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, h := range r.hops {
|
||||
if err := serializeNewHop(w, h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serializeMCRoute serializes an mcHop and writes the bytes to the given
|
||||
// io.Writer.
|
||||
func serializeNewHop(w io.Writer, h *mcHop) error {
|
||||
return WriteElements(w,
|
||||
h.pubKeyBytes[:],
|
||||
h.channelID,
|
||||
h.amtToFwd,
|
||||
h.hasBlindingPoint,
|
||||
h.hasCustomRecords,
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user