mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-27 06:01:48 +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:
@@ -1,11 +1,15 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// Instantiate variables to allow taking a reference from the failure reason.
|
||||
@@ -76,63 +80,73 @@ type interpretedResult struct {
|
||||
|
||||
// interpretResult interprets a payment outcome and returns an object that
|
||||
// contains information required to update mission control.
|
||||
func interpretResult(rt *mcRoute, success bool, failureSrcIdx *int,
|
||||
failure lnwire.FailureMessage) *interpretedResult {
|
||||
func interpretResult(rt *mcRoute,
|
||||
failure fn.Option[paymentFailure]) *interpretedResult {
|
||||
|
||||
i := &interpretedResult{
|
||||
pairResults: make(map[DirectedNodePair]pairResult),
|
||||
}
|
||||
|
||||
if success {
|
||||
return fn.ElimOption(failure, func() *interpretedResult {
|
||||
i.processSuccess(rt)
|
||||
} else {
|
||||
i.processFail(rt, failureSrcIdx, failure)
|
||||
}
|
||||
return i
|
||||
|
||||
return i
|
||||
}, func(info paymentFailure) *interpretedResult {
|
||||
i.processFail(rt, info)
|
||||
|
||||
return i
|
||||
})
|
||||
}
|
||||
|
||||
// processSuccess processes a successful payment attempt.
|
||||
func (i *interpretedResult) processSuccess(route *mcRoute) {
|
||||
// For successes, all nodes must have acted in the right way. Therefore
|
||||
// we mark all of them with a success result.
|
||||
i.successPairRange(route, 0, len(route.hops)-1)
|
||||
i.successPairRange(route, 0, len(route.hops.Val)-1)
|
||||
}
|
||||
|
||||
// processFail processes a failed payment attempt.
|
||||
func (i *interpretedResult) processFail(rt *mcRoute, errSourceIdx *int,
|
||||
failure lnwire.FailureMessage) {
|
||||
|
||||
if errSourceIdx == nil {
|
||||
func (i *interpretedResult) processFail(rt *mcRoute, failure paymentFailure) {
|
||||
if failure.info.IsNone() {
|
||||
i.processPaymentOutcomeUnknown(rt)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
idx int
|
||||
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
|
||||
},
|
||||
)
|
||||
|
||||
// If the payment was to a blinded route and we received an error from
|
||||
// after the introduction point, handle this error separately - there
|
||||
// has been a protocol violation from the introduction node. This
|
||||
// penalty applies regardless of the error code that is returned.
|
||||
introIdx, isBlinded := introductionPointIndex(rt)
|
||||
if isBlinded && introIdx < *errSourceIdx {
|
||||
i.processPaymentOutcomeBadIntro(rt, introIdx, *errSourceIdx)
|
||||
if isBlinded && introIdx < idx {
|
||||
i.processPaymentOutcomeBadIntro(rt, introIdx, idx)
|
||||
return
|
||||
}
|
||||
|
||||
switch *errSourceIdx {
|
||||
|
||||
switch idx {
|
||||
// We are the source of the failure.
|
||||
case 0:
|
||||
i.processPaymentOutcomeSelf(rt, failure)
|
||||
i.processPaymentOutcomeSelf(rt, failMsg)
|
||||
|
||||
// A failure from the final hop was received.
|
||||
case len(rt.hops):
|
||||
i.processPaymentOutcomeFinal(rt, failure)
|
||||
case len(rt.hops.Val):
|
||||
i.processPaymentOutcomeFinal(rt, failMsg)
|
||||
|
||||
// An intermediate hop failed. Interpret the outcome, update reputation
|
||||
// and try again.
|
||||
default:
|
||||
i.processPaymentOutcomeIntermediate(
|
||||
rt, *errSourceIdx, failure,
|
||||
)
|
||||
i.processPaymentOutcomeIntermediate(rt, idx, failMsg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +172,7 @@ func (i *interpretedResult) processPaymentOutcomeBadIntro(route *mcRoute,
|
||||
// a final failure reason because the recipient can't process the
|
||||
// payment (independent of the introduction failing to convert the
|
||||
// error, we can't complete the payment if the last hop fails).
|
||||
if errSourceIdx == len(route.hops) {
|
||||
if errSourceIdx == len(route.hops.Val) {
|
||||
i.finalFailureReason = &reasonError
|
||||
}
|
||||
}
|
||||
@@ -178,7 +192,7 @@ func (i *interpretedResult) processPaymentOutcomeSelf(rt *mcRoute,
|
||||
i.failNode(rt, 1)
|
||||
|
||||
// If this was a payment to a direct peer, we can stop trying.
|
||||
if len(rt.hops) == 1 {
|
||||
if len(rt.hops.Val) == 1 {
|
||||
i.finalFailureReason = &reasonError
|
||||
}
|
||||
|
||||
@@ -188,7 +202,7 @@ func (i *interpretedResult) processPaymentOutcomeSelf(rt *mcRoute,
|
||||
// available in the link has been updated.
|
||||
default:
|
||||
log.Warnf("Routing failure for local channel %v occurred",
|
||||
rt.hops[0].channelID)
|
||||
rt.hops.Val[0].channelID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +210,7 @@ func (i *interpretedResult) processPaymentOutcomeSelf(rt *mcRoute,
|
||||
func (i *interpretedResult) processPaymentOutcomeFinal(route *mcRoute,
|
||||
failure lnwire.FailureMessage) {
|
||||
|
||||
n := len(route.hops)
|
||||
n := len(route.hops.Val)
|
||||
|
||||
failNode := func() {
|
||||
i.failNode(route, n)
|
||||
@@ -396,8 +410,8 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
|
||||
// Set the node pair for which a channel update may be out of
|
||||
// date. The second chance logic uses the policyFailure field.
|
||||
i.policyFailure = &DirectedNodePair{
|
||||
From: route.hops[errorSourceIdx-1].pubKeyBytes,
|
||||
To: route.hops[errorSourceIdx].pubKeyBytes,
|
||||
From: route.hops.Val[errorSourceIdx-1].pubKeyBytes.Val,
|
||||
To: route.hops.Val[errorSourceIdx].pubKeyBytes.Val,
|
||||
}
|
||||
|
||||
reportOutgoing()
|
||||
@@ -425,8 +439,8 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
|
||||
// Set the node pair for which a channel update may be out of
|
||||
// date. The second chance logic uses the policyFailure field.
|
||||
i.policyFailure = &DirectedNodePair{
|
||||
From: route.hops[errorSourceIdx-1].pubKeyBytes,
|
||||
To: route.hops[errorSourceIdx].pubKeyBytes,
|
||||
From: route.hops.Val[errorSourceIdx-1].pubKeyBytes.Val,
|
||||
To: route.hops.Val[errorSourceIdx].pubKeyBytes.Val,
|
||||
}
|
||||
|
||||
// We report incoming channel. If a second pair is granted in
|
||||
@@ -500,14 +514,14 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
|
||||
// Note that if LND is extended to support multiple blinded
|
||||
// routes, this will terminate the payment without re-trying
|
||||
// the other routes.
|
||||
if introIdx == len(route.hops)-1 {
|
||||
if introIdx == len(route.hops.Val)-1 {
|
||||
i.finalFailureReason = &reasonError
|
||||
} else {
|
||||
// If there are other hops between the recipient and
|
||||
// introduction node, then we just penalize the last
|
||||
// hop in the blinded route to minimize the storage of
|
||||
// results for ephemeral keys.
|
||||
i.failPairBalance(route, len(route.hops)-1)
|
||||
i.failPairBalance(route, len(route.hops.Val)-1)
|
||||
}
|
||||
|
||||
// In all other cases, we penalize the reporting node. These are all
|
||||
@@ -522,8 +536,8 @@ func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
|
||||
// (i.e., that we consider our own node to be at index zero). A boolean is
|
||||
// returned to indicate whether the route contains a blinded portion at all.
|
||||
func introductionPointIndex(route *mcRoute) (int, bool) {
|
||||
for i, hop := range route.hops {
|
||||
if hop.hasBlindingPoint {
|
||||
for i, hop := range route.hops.Val {
|
||||
if hop.hasBlindingPoint.IsSome() {
|
||||
return i + 1, true
|
||||
}
|
||||
}
|
||||
@@ -534,7 +548,7 @@ func introductionPointIndex(route *mcRoute) (int, bool) {
|
||||
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
|
||||
// message or source is available.
|
||||
func (i *interpretedResult) processPaymentOutcomeUnknown(route *mcRoute) {
|
||||
n := len(route.hops)
|
||||
n := len(route.hops.Val)
|
||||
|
||||
// If this is a direct payment, the destination must be at fault.
|
||||
if n == 1 {
|
||||
@@ -551,52 +565,204 @@ func (i *interpretedResult) processPaymentOutcomeUnknown(route *mcRoute) {
|
||||
|
||||
// extractMCRoute extracts the fields required by MC from the Route struct to
|
||||
// create the more minimal mcRoute struct.
|
||||
func extractMCRoute(route *route.Route) *mcRoute {
|
||||
func extractMCRoute(r *route.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 []*route.Hop) []*mcHop {
|
||||
mcHops := make([]*mcHop, len(hops))
|
||||
for i, hop := range hops {
|
||||
mcHops[i] = extractMCHop(hop)
|
||||
}
|
||||
|
||||
return mcHops
|
||||
func extractMCHops(hops []*route.Hop) mcHops {
|
||||
return fn.Map(extractMCHop, hops)
|
||||
}
|
||||
|
||||
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
|
||||
func extractMCHop(hop *route.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](
|
||||
hop.ChannelID,
|
||||
),
|
||||
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](hop.PubKeyBytes),
|
||||
amtToFwd: tlv.NewRecordT[tlv.TlvType2](hop.AmtToForward),
|
||||
}
|
||||
|
||||
if hop.BlindingPoint != nil {
|
||||
h.hasBlindingPoint = tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType3](lnwire.TrueBoolean{}),
|
||||
)
|
||||
}
|
||||
|
||||
if hop.CustomRecords != nil {
|
||||
h.hasCustomRecords = tlv.SomeRecordT(
|
||||
tlv.NewRecordT[tlv.TlvType4](lnwire.TrueBoolean{}),
|
||||
)
|
||||
}
|
||||
|
||||
return &h
|
||||
}
|
||||
|
||||
// mcRoute holds the bare minimum info about a payment attempt route that MC
|
||||
// requires.
|
||||
type mcRoute struct {
|
||||
sourcePubKey route.Vertex
|
||||
totalAmount lnwire.MilliSatoshi
|
||||
hops []*mcHop
|
||||
sourcePubKey tlv.RecordT[tlv.TlvType0, route.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 := serializeHop(&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 := deserializeHop(innerTlvReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*v = append(*v, hop)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.NewTypeForDecodingErr(val, "routing.mcHops", l, l)
|
||||
}
|
||||
|
||||
// mcHop holds the bare minimum info about a payment attempt route hop that MC
|
||||
// requires.
|
||||
type mcHop struct {
|
||||
channelID uint64
|
||||
pubKeyBytes route.Vertex
|
||||
amtToFwd lnwire.MilliSatoshi
|
||||
hasBlindingPoint bool
|
||||
hasCustomRecords bool
|
||||
channelID tlv.RecordT[tlv.TlvType0, uint64]
|
||||
pubKeyBytes tlv.RecordT[tlv.TlvType1, route.Vertex]
|
||||
amtToFwd tlv.RecordT[tlv.TlvType2, lnwire.MilliSatoshi]
|
||||
hasBlindingPoint tlv.OptionalRecordT[tlv.TlvType3, lnwire.TrueBoolean]
|
||||
hasCustomRecords tlv.OptionalRecordT[tlv.TlvType4, lnwire.TrueBoolean]
|
||||
}
|
||||
|
||||
// failNode marks the node indicated by idx in the route as failed. It also
|
||||
@@ -604,7 +770,7 @@ type mcHop struct {
|
||||
// intentionally panics when the self node is failed.
|
||||
func (i *interpretedResult) failNode(rt *mcRoute, idx int) {
|
||||
// Mark the node as failing.
|
||||
i.nodeFailure = &rt.hops[idx-1].pubKeyBytes
|
||||
i.nodeFailure = &rt.hops.Val[idx-1].pubKeyBytes.Val
|
||||
|
||||
// Mark the incoming connection as failed for the node. We intent to
|
||||
// penalize as much as we can for a node level failure, including future
|
||||
@@ -620,7 +786,7 @@ func (i *interpretedResult) failNode(rt *mcRoute, idx int) {
|
||||
|
||||
// If not the ultimate node, mark the outgoing connection as failed for
|
||||
// the node.
|
||||
if idx < len(rt.hops) {
|
||||
if idx < len(rt.hops.Val) {
|
||||
outgoingChannelIdx := idx
|
||||
outPair, _ := getPair(rt, outgoingChannelIdx)
|
||||
i.pairResults[outPair] = failPairResult(0)
|
||||
@@ -667,18 +833,18 @@ func (i *interpretedResult) successPairRange(rt *mcRoute, fromIdx, toIdx int) {
|
||||
func getPair(rt *mcRoute, channelIdx int) (DirectedNodePair,
|
||||
lnwire.MilliSatoshi) {
|
||||
|
||||
nodeTo := rt.hops[channelIdx].pubKeyBytes
|
||||
nodeTo := rt.hops.Val[channelIdx].pubKeyBytes.Val
|
||||
var (
|
||||
nodeFrom route.Vertex
|
||||
amt lnwire.MilliSatoshi
|
||||
)
|
||||
|
||||
if channelIdx == 0 {
|
||||
nodeFrom = rt.sourcePubKey
|
||||
amt = rt.totalAmount
|
||||
nodeFrom = rt.sourcePubKey.Val
|
||||
amt = rt.totalAmount.Val
|
||||
} else {
|
||||
nodeFrom = rt.hops[channelIdx-1].pubKeyBytes
|
||||
amt = rt.hops[channelIdx-1].amtToFwd
|
||||
nodeFrom = rt.hops.Val[channelIdx-1].pubKeyBytes.Val
|
||||
amt = rt.hops.Val[channelIdx-1].amtToFwd.Val
|
||||
}
|
||||
|
||||
pair := NewDirectedNodePair(nodeFrom, nodeTo)
|
||||
|
Reference in New Issue
Block a user