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:
Elle Mouton
2024-10-08 13:43:30 +02:00
parent d9a073ad7e
commit 5370c90906
7 changed files with 1612 additions and 690 deletions

View File

@@ -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: &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: &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.

View File

@@ -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,
)
}