From 7aa4a7c7fcbbe5058960ad8edfea9a8228bd1c12 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 11 Dec 2019 10:20:55 +0100 Subject: [PATCH 1/5] channeldb/migration_01_to_11: isolate route structure Before we change the Hop struct, isolate the code that is used in older migrations to prevent breaking them. route.go was taken from commit 6e463c1634061d595953f20813860207e5d485ce --- .../migration_09_legacy_serialization.go | 15 +- .../migration_10_route_tlv_records.go | 15 +- channeldb/migration_01_to_11/migrations.go | 7 +- .../migration_01_to_11/migrations_test.go | 15 +- channeldb/migration_01_to_11/payments.go | 17 +- channeldb/migration_01_to_11/route.go | 330 ++++++++++++++++++ 6 files changed, 362 insertions(+), 37 deletions(-) create mode 100644 channeldb/migration_01_to_11/route.go diff --git a/channeldb/migration_01_to_11/migration_09_legacy_serialization.go b/channeldb/migration_01_to_11/migration_09_legacy_serialization.go index cc6614c9..cc0568a3 100644 --- a/channeldb/migration_01_to_11/migration_09_legacy_serialization.go +++ b/channeldb/migration_01_to_11/migration_09_legacy_serialization.go @@ -10,7 +10,6 @@ import ( "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) var ( @@ -274,7 +273,7 @@ func serializePaymentAttemptInfoMigration9(w io.Writer, a *PaymentAttemptInfo) e return nil } -func serializeHopMigration9(w io.Writer, h *route.Hop) error { +func serializeHopMigration9(w io.Writer, h *Hop) error { if err := WriteElements(w, h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock, h.AmtToForward, @@ -285,7 +284,7 @@ func serializeHopMigration9(w io.Writer, h *route.Hop) error { return nil } -func serializeRouteMigration9(w io.Writer, r route.Route) error { +func serializeRouteMigration9(w io.Writer, r Route) error { if err := WriteElements(w, r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], ); err != nil { @@ -318,8 +317,8 @@ func deserializePaymentAttemptInfoMigration9(r io.Reader) (*PaymentAttemptInfo, return a, nil } -func deserializeRouteMigration9(r io.Reader) (route.Route, error) { - rt := route.Route{} +func deserializeRouteMigration9(r io.Reader) (Route, error) { + rt := Route{} if err := ReadElements(r, &rt.TotalTimeLock, &rt.TotalAmount, ); err != nil { @@ -337,7 +336,7 @@ func deserializeRouteMigration9(r io.Reader) (route.Route, error) { return rt, err } - var hops []*route.Hop + var hops []*Hop for i := uint32(0); i < numHops; i++ { hop, err := deserializeHopMigration9(r) if err != nil { @@ -350,8 +349,8 @@ func deserializeRouteMigration9(r io.Reader) (route.Route, error) { return rt, nil } -func deserializeHopMigration9(r io.Reader) (*route.Hop, error) { - h := &route.Hop{} +func deserializeHopMigration9(r io.Reader) (*Hop, error) { + h := &Hop{} var pub []byte if err := ReadElements(r, &pub); err != nil { diff --git a/channeldb/migration_01_to_11/migration_10_route_tlv_records.go b/channeldb/migration_01_to_11/migration_10_route_tlv_records.go index 648d85ad..e404f575 100644 --- a/channeldb/migration_01_to_11/migration_10_route_tlv_records.go +++ b/channeldb/migration_01_to_11/migration_10_route_tlv_records.go @@ -5,7 +5,6 @@ import ( "io" "github.com/coreos/bbolt" - "github.com/lightningnetwork/lnd/routing/route" ) // MigrateRouteSerialization migrates the way we serialize routes across the @@ -154,8 +153,8 @@ func serializePaymentAttemptInfoLegacy(w io.Writer, a *PaymentAttemptInfo) error return nil } -func deserializeHopLegacy(r io.Reader) (*route.Hop, error) { - h := &route.Hop{} +func deserializeHopLegacy(r io.Reader) (*Hop, error) { + h := &Hop{} var pub []byte if err := ReadElements(r, &pub); err != nil { @@ -172,7 +171,7 @@ func deserializeHopLegacy(r io.Reader) (*route.Hop, error) { return h, nil } -func serializeHopLegacy(w io.Writer, h *route.Hop) error { +func serializeHopLegacy(w io.Writer, h *Hop) error { if err := WriteElements(w, h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock, h.AmtToForward, @@ -183,8 +182,8 @@ func serializeHopLegacy(w io.Writer, h *route.Hop) error { return nil } -func deserializeRouteLegacy(r io.Reader) (route.Route, error) { - rt := route.Route{} +func deserializeRouteLegacy(r io.Reader) (Route, error) { + rt := Route{} if err := ReadElements(r, &rt.TotalTimeLock, &rt.TotalAmount, ); err != nil { @@ -202,7 +201,7 @@ func deserializeRouteLegacy(r io.Reader) (route.Route, error) { return rt, err } - var hops []*route.Hop + var hops []*Hop for i := uint32(0); i < numHops; i++ { hop, err := deserializeHopLegacy(r) if err != nil { @@ -215,7 +214,7 @@ func deserializeRouteLegacy(r io.Reader) (route.Route, error) { return rt, nil } -func serializeRouteLegacy(w io.Writer, r route.Route) error { +func serializeRouteLegacy(w io.Writer, r Route) error { if err := WriteElements(w, r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], ); err != nil { diff --git a/channeldb/migration_01_to_11/migrations.go b/channeldb/migration_01_to_11/migrations.go index 3f841009..511633d8 100644 --- a/channeldb/migration_01_to_11/migrations.go +++ b/channeldb/migration_01_to_11/migrations.go @@ -9,7 +9,6 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) // MigrateNodeAndEdgeUpdateIndex is a migration function that will update the @@ -817,14 +816,14 @@ func MigrateOutgoingPayments(tx *bbolt.Tx) error { // Do the same for the PaymentAttemptInfo. totalAmt := payment.Terms.Value + payment.Fee - rt := route.Route{ + rt := Route{ TotalTimeLock: payment.TimeLockLength, TotalAmount: totalAmt, SourcePubKey: sourcePubKey, - Hops: []*route.Hop{}, + Hops: []*Hop{}, } for _, hop := range payment.Path { - rt.Hops = append(rt.Hops, &route.Hop{ + rt.Hops = append(rt.Hops, &Hop{ PubKeyBytes: hop, AmtToForward: totalAmt, }) diff --git a/channeldb/migration_01_to_11/migrations_test.go b/channeldb/migration_01_to_11/migrations_test.go index 980b029c..fd2c2565 100644 --- a/channeldb/migration_01_to_11/migrations_test.go +++ b/channeldb/migration_01_to_11/migrations_test.go @@ -16,7 +16,6 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" ) // TestPaymentStatusesMigration checks that already completed payments will have @@ -714,25 +713,25 @@ func makeRandPaymentCreationInfo() (*PaymentCreationInfo, error) { func TestPaymentRouteSerialization(t *testing.T) { t.Parallel() - legacyHop1 := &route.Hop{ - PubKeyBytes: route.NewVertex(pub), + legacyHop1 := &Hop{ + PubKeyBytes: NewVertex(pub), ChannelID: 12345, OutgoingTimeLock: 111, LegacyPayload: true, AmtToForward: 555, } - legacyHop2 := &route.Hop{ - PubKeyBytes: route.NewVertex(pub), + legacyHop2 := &Hop{ + PubKeyBytes: NewVertex(pub), ChannelID: 12345, OutgoingTimeLock: 111, LegacyPayload: true, AmtToForward: 555, } - legacyRoute := route.Route{ + legacyRoute := Route{ TotalTimeLock: 123, TotalAmount: 1234567, - SourcePubKey: route.NewVertex(pub), - Hops: []*route.Hop{legacyHop1, legacyHop2}, + SourcePubKey: NewVertex(pub), + Hops: []*Hop{legacyHop1, legacyHop2}, } const numPayments = 4 diff --git a/channeldb/migration_01_to_11/payments.go b/channeldb/migration_01_to_11/payments.go index 1195ca82..16e4a71c 100644 --- a/channeldb/migration_01_to_11/payments.go +++ b/channeldb/migration_01_to_11/payments.go @@ -14,7 +14,6 @@ import ( "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -213,7 +212,7 @@ type PaymentAttemptInfo struct { SessionKey *btcec.PrivateKey // Route is the route attempted to send the HTLC. - Route route.Route + Route Route } // Payment is a wrapper around a payment's PaymentCreationInfo, @@ -464,7 +463,7 @@ func deserializePaymentAttemptInfo(r io.Reader) (*PaymentAttemptInfo, error) { return a, nil } -func serializeHop(w io.Writer, h *route.Hop) error { +func serializeHop(w io.Writer, h *Hop) error { if err := WriteElements(w, h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock, h.AmtToForward, @@ -513,8 +512,8 @@ func serializeHop(w io.Writer, h *route.Hop) error { // to read/write a TLV stream larger than this. const maxOnionPayloadSize = 1300 -func deserializeHop(r io.Reader) (*route.Hop, error) { - h := &route.Hop{} +func deserializeHop(r io.Reader) (*Hop, error) { + h := &Hop{} var pub []byte if err := ReadElements(r, &pub); err != nil { @@ -568,7 +567,7 @@ func deserializeHop(r io.Reader) (*route.Hop, error) { } // SerializeRoute serializes a route. -func SerializeRoute(w io.Writer, r route.Route) error { +func SerializeRoute(w io.Writer, r Route) error { if err := WriteElements(w, r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:], ); err != nil { @@ -589,8 +588,8 @@ func SerializeRoute(w io.Writer, r route.Route) error { } // DeserializeRoute deserializes a route. -func DeserializeRoute(r io.Reader) (route.Route, error) { - rt := route.Route{} +func DeserializeRoute(r io.Reader) (Route, error) { + rt := Route{} if err := ReadElements(r, &rt.TotalTimeLock, &rt.TotalAmount, ); err != nil { @@ -608,7 +607,7 @@ func DeserializeRoute(r io.Reader) (route.Route, error) { return rt, err } - var hops []*route.Hop + var hops []*Hop for i := uint32(0); i < numHops; i++ { hop, err := deserializeHop(r) if err != nil { diff --git a/channeldb/migration_01_to_11/route.go b/channeldb/migration_01_to_11/route.go new file mode 100644 index 00000000..1dbfff60 --- /dev/null +++ b/channeldb/migration_01_to_11/route.go @@ -0,0 +1,330 @@ +package migration_01_to_11 + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "strconv" + "strings" + + "github.com/btcsuite/btcd/btcec" + sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/tlv" +) + +// VertexSize is the size of the array to store a vertex. +const VertexSize = 33 + +// ErrNoRouteHopsProvided is returned when a caller attempts to construct a new +// sphinx packet, but provides an empty set of hops for each route. +var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided") + +// Vertex is a simple alias for the serialization of a compressed Bitcoin +// public key. +type Vertex [VertexSize]byte + +// NewVertex returns a new Vertex given a public key. +func NewVertex(pub *btcec.PublicKey) Vertex { + var v Vertex + copy(v[:], pub.SerializeCompressed()) + return v +} + +// NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a +// byte slice. +func NewVertexFromBytes(b []byte) (Vertex, error) { + vertexLen := len(b) + if vertexLen != VertexSize { + return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+ + "want %v", vertexLen, VertexSize) + } + + var v Vertex + copy(v[:], b) + return v, nil +} + +// NewVertexFromStr returns a new Vertex given its hex-encoded string format. +func NewVertexFromStr(v string) (Vertex, error) { + // Return error if hex string is of incorrect length. + if len(v) != VertexSize*2 { + return Vertex{}, fmt.Errorf("invalid vertex string length of "+ + "%v, want %v", len(v), VertexSize*2) + } + + vertex, err := hex.DecodeString(v) + if err != nil { + return Vertex{}, err + } + + return NewVertexFromBytes(vertex) +} + +// String returns a human readable version of the Vertex which is the +// hex-encoding of the serialized compressed public key. +func (v Vertex) String() string { + return fmt.Sprintf("%x", v[:]) +} + +// Hop represents an intermediate or final node of the route. This naming +// is in line with the definition given in BOLT #4: Onion Routing Protocol. +// The struct houses the channel along which this hop can be reached and +// the values necessary to create the HTLC that needs to be sent to the +// next hop. It is also used to encode the per-hop payload included within +// the Sphinx packet. +type Hop struct { + // PubKeyBytes is the raw bytes of the public key of the target node. + PubKeyBytes Vertex + + // ChannelID is the unique channel ID for the channel. The first 3 + // bytes are the block height, the next 3 the index within the block, + // and the last 2 bytes are the output index for the channel. + ChannelID uint64 + + // OutgoingTimeLock is the timelock value that should be used when + // crafting the _outgoing_ HTLC from this hop. + OutgoingTimeLock uint32 + + // AmtToForward is the amount that this hop will forward to the next + // hop. This value is less than the value that the incoming HTLC + // carries as a fee will be subtracted by the hop. + AmtToForward lnwire.MilliSatoshi + + // TLVRecords if non-nil are a set of additional TLV records that + // should be included in the forwarding instructions for this node. + TLVRecords []tlv.Record + + // LegacyPayload if true, then this signals that this node doesn't + // understand the new TLV payload, so we must instead use the legacy + // payload. + LegacyPayload bool +} + +// PackHopPayload writes to the passed io.Writer, the series of byes that can +// be placed directly into the per-hop payload (EOB) for this hop. This will +// include the required routing fields, as well as serializing any of the +// passed optional TLVRecords. nextChanID is the unique channel ID that +// references the _outgoing_ channel ID that follows this hop. This field +// follows the same semantics as the NextAddress field in the onion: it should +// be set to zero to indicate the terminal hop. +func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { + // If this is a legacy payload, then we'll exit here as this method + // shouldn't be called. + if h.LegacyPayload == true { + return fmt.Errorf("cannot pack hop payloads for legacy " + + "payloads") + } + + // Otherwise, we'll need to make a new stream that includes our + // required routing fields, as well as these optional values. + var records []tlv.Record + + // Every hop must have an amount to forward and CLTV expiry. + amt := uint64(h.AmtToForward) + records = append(records, + record.NewAmtToFwdRecord(&amt), + record.NewLockTimeRecord(&h.OutgoingTimeLock), + ) + + // BOLT 04 says the next_hop_id should be omitted for the final hop, + // but present for all others. + // + // TODO(conner): test using hop.Exit once available + if nextChanID != 0 { + records = append(records, + record.NewNextHopIDRecord(&nextChanID), + ) + } + + // Append any custom types destined for this hop. + records = append(records, h.TLVRecords...) + + // To ensure we produce a canonical stream, we'll sort the records + // before encoding them as a stream in the hop payload. + tlv.SortRecords(records) + + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// Route represents a path through the channel graph which runs over one or +// more channels in succession. This struct carries all the information +// required to craft the Sphinx onion packet, and send the payment along the +// first hop in the path. A route is only selected as valid if all the channels +// have sufficient capacity to carry the initial payment amount after fees are +// accounted for. +type Route struct { + // TotalTimeLock is the cumulative (final) time lock across the entire + // route. This is the CLTV value that should be extended to the first + // hop in the route. All other hops will decrement the time-lock as + // advertised, leaving enough time for all hops to wait for or present + // the payment preimage to complete the payment. + TotalTimeLock uint32 + + // TotalAmount is the total amount of funds required to complete a + // payment over this route. This value includes the cumulative fees at + // each hop. As a result, the HTLC extended to the first-hop in the + // route will need to have at least this many satoshis, otherwise the + // route will fail at an intermediate node due to an insufficient + // amount of fees. + TotalAmount lnwire.MilliSatoshi + + // SourcePubKey is the pubkey of the node where this route originates + // from. + SourcePubKey Vertex + + // Hops contains details concerning the specific forwarding details at + // each hop. + Hops []*Hop +} + +// HopFee returns the fee charged by the route hop indicated by hopIndex. +func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi { + var incomingAmt lnwire.MilliSatoshi + if hopIndex == 0 { + incomingAmt = r.TotalAmount + } else { + incomingAmt = r.Hops[hopIndex-1].AmtToForward + } + + // Fee is calculated as difference between incoming and outgoing amount. + return incomingAmt - r.Hops[hopIndex].AmtToForward +} + +// TotalFees is the sum of the fees paid at each hop within the final route. In +// the case of a one-hop payment, this value will be zero as we don't need to +// pay a fee to ourself. +func (r *Route) TotalFees() lnwire.MilliSatoshi { + if len(r.Hops) == 0 { + return 0 + } + + return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward +} + +// NewRouteFromHops creates a new Route structure from the minimally required +// information to perform the payment. It infers fee amounts and populates the +// node, chan and prev/next hop maps. +func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32, + sourceVertex Vertex, hops []*Hop) (*Route, error) { + + if len(hops) == 0 { + return nil, ErrNoRouteHopsProvided + } + + // First, we'll create a route struct and populate it with the fields + // for which the values are provided as arguments of this function. + // TotalFees is determined based on the difference between the amount + // that is send from the source and the final amount that is received + // by the destination. + route := &Route{ + SourcePubKey: sourceVertex, + Hops: hops, + TotalTimeLock: timeLock, + TotalAmount: amtToSend, + } + + return route, nil +} + +// ToSphinxPath converts a complete route into a sphinx PaymentPath that +// contains the per-hop paylods used to encoding the HTLC routing data for each +// hop in the route. This method also accepts an optional EOB payload for the +// final hop. +func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) { + var path sphinx.PaymentPath + + // For each hop encoded within the route, we'll convert the hop struct + // to an OnionHop with matching per-hop payload within the path as used + // by the sphinx package. + for i, hop := range r.Hops { + pub, err := btcec.ParsePubKey( + hop.PubKeyBytes[:], btcec.S256(), + ) + if err != nil { + return nil, err + } + + // As a base case, the next hop is set to all zeroes in order + // to indicate that the "last hop" as no further hops after it. + nextHop := uint64(0) + + // If we aren't on the last hop, then we set the "next address" + // field to be the channel that directly follows it. + if i != len(r.Hops)-1 { + nextHop = r.Hops[i+1].ChannelID + } + + var payload sphinx.HopPayload + + // If this is the legacy payload, then we can just include the + // hop data as normal. + if hop.LegacyPayload { + // Before we encode this value, we'll pack the next hop + // into the NextAddress field of the hop info to ensure + // we point to the right now. + hopData := sphinx.HopData{ + ForwardAmount: uint64(hop.AmtToForward), + OutgoingCltv: hop.OutgoingTimeLock, + } + binary.BigEndian.PutUint64( + hopData.NextAddress[:], nextHop, + ) + + payload, err = sphinx.NewHopPayload(&hopData, nil) + if err != nil { + return nil, err + } + } else { + // For non-legacy payloads, we'll need to pack the + // routing information, along with any extra TLV + // information into the new per-hop payload format. + // We'll also pass in the chan ID of the hop this + // channel should be forwarded to so we can construct a + // valid payload. + var b bytes.Buffer + err := hop.PackHopPayload(&b, nextHop) + if err != nil { + return nil, err + } + + // TODO(roasbeef): make better API for NewHopPayload? + payload, err = sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + return nil, err + } + } + + path[i] = sphinx.OnionHop{ + NodePub: *pub, + HopPayload: payload, + } + } + + return &path, nil +} + +// String returns a human readable representation of the route. +func (r *Route) String() string { + var b strings.Builder + + for i, hop := range r.Hops { + if i > 0 { + b.WriteString(",") + } + b.WriteString(strconv.FormatUint(hop.ChannelID, 10)) + } + + return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v", + r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock, + b.String(), + ) +} From 8b5bb0ac639c96a5bd165dde568a0a073c5e6394 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 12 Dec 2019 00:01:55 +0100 Subject: [PATCH 2/5] record: move CustomRecordSet --- channeldb/invoice_test.go | 10 +++++----- channeldb/invoices.go | 5 +++-- htlcswitch/hop/payload.go | 25 +++++++++---------------- htlcswitch/hop/payload_test.go | 2 +- invoices/interface.go | 3 +-- invoices/invoiceregistry_test.go | 5 ++--- invoices/update.go | 4 +--- lnrpc/routerrpc/router_backend.go | 5 ++--- record/custom_records.go | 10 ++++++++++ 9 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 record/custom_records.go diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 24eaf180..b38c5cad 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -7,9 +7,9 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" ) var ( @@ -212,7 +212,7 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) { key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4} htlc := HtlcAcceptDesc{ Amt: 500, - CustomRecords: make(hop.CustomRecordSet), + CustomRecords: make(record.CustomSet), } invoice, err := db.UpdateInvoice(paymentHash, func(invoice *Invoice) (*InvoiceUpdateDesc, error) { @@ -439,7 +439,7 @@ func TestDuplicateSettleInvoice(t *testing.T) { AcceptTime: time.Unix(1, 0), ResolveTime: time.Unix(1, 0), State: HtlcStateSettled, - CustomRecords: make(hop.CustomRecordSet), + CustomRecords: make(record.CustomSet), }, } @@ -751,7 +751,7 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback { return nil, ErrInvoiceAlreadySettled } - noRecords := make(hop.CustomRecordSet) + noRecords := make(record.CustomSet) update := &InvoiceUpdateDesc{ State: &InvoiceStateUpdateDesc{ @@ -795,7 +795,7 @@ func TestCustomRecords(t *testing.T) { // Accept an htlc with custom records on this invoice. key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4} - records := hop.CustomRecordSet{ + records := record.CustomSet{ 100000: []byte{}, 100001: []byte{1, 2}, } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 106c0e41..8ded522f 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -12,6 +12,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/tlv" ) @@ -317,7 +318,7 @@ type InvoiceHTLC struct { // CustomRecords contains the custom key/value pairs that accompanied // the htlc. - CustomRecords hop.CustomRecordSet + CustomRecords record.CustomSet } // HtlcAcceptDesc describes the details of a newly accepted htlc. @@ -337,7 +338,7 @@ type HtlcAcceptDesc struct { // CustomRecords contains the custom key/value pairs that accompanied // the htlc. - CustomRecords hop.CustomRecordSet + CustomRecords record.CustomSet } // InvoiceUpdateDesc describes the changes that should be applied to the diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go index 523ffbad..d8aafd9c 100644 --- a/htlcswitch/hop/payload.go +++ b/htlcswitch/hop/payload.go @@ -29,12 +29,6 @@ const ( RequiredViolation ) -const ( - // CustomTypeStart is the start of the custom tlv type range as defined - // in BOLT 01. - CustomTypeStart = 65536 -) - // String returns a human-readable description of the violation as a verb. func (v PayloadViolation) String() string { switch v { @@ -79,9 +73,6 @@ func (e ErrInvalidPayload) Error() string { hopType, e.Violation, e.Type) } -// CustomRecordSet stores a set of custom key/value pairs. -type CustomRecordSet map[uint64][]byte - // Payload encapsulates all information delivered to a hop in an onion payload. // A Hop can represent either a TLV or legacy payload. The primary forwarding // instruction can be accessed via ForwardingInfo, and additional records can be @@ -97,7 +88,7 @@ type Payload struct { // customRecords are user-defined records in the custom type range that // were included in the payload. - customRecords CustomRecordSet + customRecords record.CustomSet } // NewLegacyPayload builds a Payload from the amount, cltv, and next hop @@ -112,7 +103,7 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload { AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount), OutgoingCTLV: f.OutgoingCltv, }, - customRecords: make(CustomRecordSet), + customRecords: make(record.CustomSet), } } @@ -188,10 +179,10 @@ func (h *Payload) ForwardingInfo() ForwardingInfo { // NewCustomRecords filters the types parsed from the tlv stream for custom // records. -func NewCustomRecords(parsedTypes tlv.TypeMap) CustomRecordSet { - customRecords := make(CustomRecordSet) +func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet { + customRecords := make(record.CustomSet) for t, parseResult := range parsedTypes { - if parseResult == nil || t < CustomTypeStart { + if parseResult == nil || t < record.CustomTypeStart { continue } customRecords[uint64(t)] = parseResult @@ -261,7 +252,7 @@ func (h *Payload) MultiPath() *record.MPP { // CustomRecords returns the custom tlv type records that were parsed from the // payload. -func (h *Payload) CustomRecords() CustomRecordSet { +func (h *Payload) CustomRecords() record.CustomSet { return h.customRecords } @@ -280,7 +271,9 @@ func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type { // // We always accept custom fields, because a higher level // application may understand them. - if parseResult == nil || t%2 != 0 || t >= CustomTypeStart { + if parseResult == nil || t%2 != 0 || + t >= record.CustomTypeStart { + continue } diff --git a/htlcswitch/hop/payload_test.go b/htlcswitch/hop/payload_test.go index f8c0df21..b0a92534 100644 --- a/htlcswitch/hop/payload_test.go +++ b/htlcswitch/hop/payload_test.go @@ -244,7 +244,7 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) { // Convert expected nil map to empty map, because we always expect an // initiated map from the payload. - expCustomRecords := make(hop.CustomRecordSet) + expCustomRecords := make(record.CustomSet) if test.expCustomRecords != nil { expCustomRecords = test.expCustomRecords } diff --git a/invoices/interface.go b/invoices/interface.go index bf7f0ed9..c511dded 100644 --- a/invoices/interface.go +++ b/invoices/interface.go @@ -1,7 +1,6 @@ package invoices import ( - "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/record" ) @@ -14,5 +13,5 @@ type Payload interface { // CustomRecords returns the custom tlv type records that were parsed // from the payload. - CustomRecords() hop.CustomRecordSet + CustomRecords() record.CustomSet } diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 8b992258..eabb2ba2 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -690,8 +689,8 @@ func (p *mockPayload) MultiPath() *record.MPP { return p.mpp } -func (p *mockPayload) CustomRecords() hop.CustomRecordSet { - return make(hop.CustomRecordSet) +func (p *mockPayload) CustomRecords() record.CustomSet { + return make(record.CustomSet) } // TestSettleMpp tests settling of an invoice with multiple partial payments. diff --git a/invoices/update.go b/invoices/update.go index 3175efa6..913caeb0 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -3,8 +3,6 @@ package invoices import ( "errors" - "github.com/lightningnetwork/lnd/htlcswitch/hop" - "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -105,7 +103,7 @@ type invoiceUpdateCtx struct { expiry uint32 currentHeight int32 finalCltvRejectDelta int32 - customRecords hop.CustomRecordSet + customRecords record.CustomSet mpp *record.MPP } diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 38c4a1b7..b9de9617 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -385,9 +384,9 @@ func UnmarshallCustomRecords(rpcRecords map[uint64][]byte) ([]tlv.Record, // tlvRecords is sorted, so we only need to check that the first // element is within the custom range. - if uint64(tlvRecords[0].Type()) < hop.CustomTypeStart { + if uint64(tlvRecords[0].Type()) < record.CustomTypeStart { return nil, fmt.Errorf("no custom records with types "+ - "below %v allowed", hop.CustomTypeStart) + "below %v allowed", record.CustomTypeStart) } return tlvRecords, nil diff --git a/record/custom_records.go b/record/custom_records.go new file mode 100644 index 00000000..36e9e5ac --- /dev/null +++ b/record/custom_records.go @@ -0,0 +1,10 @@ +package record + +const ( + // CustomTypeStart is the start of the custom tlv type range as defined + // in BOLT 01. + CustomTypeStart = 65536 +) + +// CustomSet stores a set of custom key/value pairs. +type CustomSet map[uint64][]byte From d02de70d20e2af8a43ce9397a1b71fb63122a99d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 11 Dec 2019 10:52:27 +0100 Subject: [PATCH 3/5] multi: do not use tlv.Record outside wire format handling This commit prepares for more manipulation of custom records. A list of tlv.Record types is more difficult to use than the more basic map[uint64][]byte. Furthermore fields and variables are renamed to make them more consistent. --- channeldb/payments.go | 6 +- channeldb/payments_test.go | 78 ++------------------------ lnrpc/routerrpc/router_backend.go | 49 ++++++---------- lnrpc/routerrpc/router_backend_test.go | 4 +- routing/pathfind.go | 8 +-- routing/payment_session.go | 2 +- routing/route/route.go | 7 ++- routing/router.go | 10 ++-- rpcserver.go | 9 +-- 9 files changed, 46 insertions(+), 127 deletions(-) diff --git a/channeldb/payments.go b/channeldb/payments.go index 3d8c6e34..0e2b865d 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -606,7 +606,9 @@ func serializeHop(w io.Writer, h *route.Hop) error { if h.MPP != nil { records = append(records, h.MPP.Record()) } - records = append(records, h.TLVRecords...) + + tlvRecords := tlv.MapToRecords(h.CustomRecords) + records = append(records, tlvRecords...) // Otherwise, we'll transform our slice of records into a map of the // raw bytes, then serialize them in-line with a length (number of @@ -710,7 +712,7 @@ func deserializeHop(r io.Reader) (*route.Hop, error) { h.MPP = mpp } - h.TLVRecords = tlv.MapToRecords(tlvMap) + h.CustomRecords = tlvMap return h, nil } diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index bec8e528..8eba5e5a 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -2,7 +2,6 @@ package channeldb import ( "bytes" - "errors" "fmt" "math/rand" "reflect" @@ -28,9 +27,9 @@ var ( ChannelID: 12345, OutgoingTimeLock: 111, AmtToForward: 555, - TLVRecords: []tlv.Record{ - tlv.MakeStaticRecord(1, nil, 3, tlvEncoder, nil), - tlv.MakeStaticRecord(2, nil, 3, tlvEncoder, nil), + CustomRecords: record.CustomSet{ + 1: []byte{}, + 2: []byte{}, }, MPP: record.NewMPP(32, [32]byte{0x42}), } @@ -144,25 +143,7 @@ func TestSentPaymentSerialization(t *testing.T) { // assertRouteEquals compares to routes for equality and returns an error if // they are not equal. func assertRouteEqual(a, b *route.Route) error { - err := assertRouteHopRecordsEqual(a, b) - if err != nil { - return err - } - - // TLV records have already been compared and need to be cleared to - // properly compare the remaining fields using DeepEqual. - copyRouteNoHops := func(r *route.Route) *route.Route { - copy := *r - copy.Hops = make([]*route.Hop, len(r.Hops)) - for i, hop := range r.Hops { - hopCopy := *hop - hopCopy.TLVRecords = nil - copy.Hops[i] = &hopCopy - } - return © - } - - if !reflect.DeepEqual(copyRouteNoHops(a), copyRouteNoHops(b)) { + if !reflect.DeepEqual(a, b) { return fmt.Errorf("PaymentAttemptInfos don't match: %v vs %v", spew.Sdump(a), spew.Sdump(b)) } @@ -170,57 +151,6 @@ func assertRouteEqual(a, b *route.Route) error { return nil } -func assertRouteHopRecordsEqual(r1, r2 *route.Route) error { - if len(r1.Hops) != len(r2.Hops) { - return errors.New("route hop count mismatch") - } - - for i := 0; i < len(r1.Hops); i++ { - records1 := r1.Hops[i].TLVRecords - records2 := r2.Hops[i].TLVRecords - if len(records1) != len(records2) { - return fmt.Errorf("route record count for hop %v "+ - "mismatch", i) - } - - for j := 0; j < len(records1); j++ { - expectedRecord := records1[j] - newRecord := records2[j] - - err := assertHopRecordsEqual(expectedRecord, newRecord) - if err != nil { - return fmt.Errorf("route record mismatch: %v", err) - } - } - } - - return nil -} - -func assertHopRecordsEqual(h1, h2 tlv.Record) error { - if h1.Type() != h2.Type() { - return fmt.Errorf("wrong type: expected %v, got %v", h1.Type(), - h2.Type()) - } - - var b bytes.Buffer - if err := h2.Encode(&b); err != nil { - return fmt.Errorf("unable to encode record: %v", err) - } - - if !bytes.Equal(b.Bytes(), tlvBytes) { - return fmt.Errorf("wrong raw record: expected %x, got %x", - tlvBytes, b.Bytes()) - } - - if h1.Size() != h2.Size() { - return fmt.Errorf("wrong size: expected %v, "+ - "got %v", h1.Size(), h2.Size()) - } - - return nil -} - func TestRouteSerialization(t *testing.T) { t.Parallel() diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index b9de9617..dd918f54 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -19,7 +19,6 @@ import ( "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" ) @@ -45,7 +44,7 @@ type RouterBackend struct { // routes. FindRoute func(source, target route.Vertex, amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, - destTlvRecords []tlv.Record, + destCustomRecords record.CustomSet, finalExpiry ...uint16) (*route.Route, error) MissionControl MissionControl @@ -232,7 +231,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, // If we have any TLV records destined for the final hop, then we'll // attempt to decode them now into a form that the router can more // easily manipulate. - destTlvRecords, err := UnmarshallCustomRecords(in.DestCustomRecords) + err = ValidateCustomRecords(in.DestCustomRecords) if err != nil { return nil, err } @@ -242,7 +241,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, // the route. route, err := r.FindRoute( sourcePubKey, targetPubKey, amt, restrictions, - destTlvRecords, finalCLTVDelta, + in.DestCustomRecords, finalCLTVDelta, ) if err != nil { return nil, err @@ -346,11 +345,6 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) } } - tlvMap, err := tlv.RecordsToMap(hop.TLVRecords) - if err != nil { - return nil, err - } - resp.Hops[i] = &lnrpc.Hop{ ChanId: hop.ChannelID, ChanCapacity: int64(chanCapacity), @@ -362,7 +356,7 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) PubKey: hex.EncodeToString( hop.PubKeyBytes[:], ), - CustomRecords: tlvMap, + CustomRecords: hop.CustomRecords, TlvPayload: !hop.LegacyPayload, MppRecord: mpp, } @@ -372,24 +366,16 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) return resp, nil } -// UnmarshallCustomRecords unmarshall rpc custom records to tlv records. -func UnmarshallCustomRecords(rpcRecords map[uint64][]byte) ([]tlv.Record, - error) { - - if len(rpcRecords) == 0 { - return nil, nil +// ValidateCustomRecords checks that all custom records are in the custom type +// range. +func ValidateCustomRecords(rpcRecords map[uint64][]byte) error { + for key := range rpcRecords { + if key < record.CustomTypeStart { + return fmt.Errorf("no custom records with types "+ + "below %v allowed", record.CustomTypeStart) + } } - - tlvRecords := tlv.MapToRecords(rpcRecords) - - // tlvRecords is sorted, so we only need to check that the first - // element is within the custom range. - if uint64(tlvRecords[0].Type()) < record.CustomTypeStart { - return nil, fmt.Errorf("no custom records with types "+ - "below %v allowed", record.CustomTypeStart) - } - - return tlvRecords, nil + return nil } // UnmarshallHopWithPubkey unmarshalls an rpc hop for which the pubkey has @@ -397,7 +383,7 @@ func UnmarshallCustomRecords(rpcRecords map[uint64][]byte) ([]tlv.Record, func UnmarshallHopWithPubkey(rpcHop *lnrpc.Hop, pubkey route.Vertex) (*route.Hop, error) { - tlvRecords, err := UnmarshallCustomRecords(rpcHop.CustomRecords) + err := ValidateCustomRecords(rpcHop.CustomRecords) if err != nil { return nil, err } @@ -412,7 +398,7 @@ func UnmarshallHopWithPubkey(rpcHop *lnrpc.Hop, pubkey route.Vertex) (*route.Hop AmtToForward: lnwire.MilliSatoshi(rpcHop.AmtToForwardMsat), PubKeyBytes: pubkey, ChannelID: rpcHop.ChanId, - TLVRecords: tlvRecords, + CustomRecords: rpcHop.CustomRecords, LegacyPayload: !rpcHop.TlvPayload, MPP: mpp, }, nil @@ -540,12 +526,11 @@ func (r *RouterBackend) extractIntentFromSendRequest( return nil, errors.New("timeout_seconds must be specified") } - payIntent.FinalDestRecords, err = UnmarshallCustomRecords( - rpcPayReq.DestCustomRecords, - ) + err = ValidateCustomRecords(rpcPayReq.DestCustomRecords) if err != nil { return nil, err } + payIntent.DestCustomRecords = rpcPayReq.DestCustomRecords payIntent.PayAttemptTimeout = time.Second * time.Duration(rpcPayReq.TimeoutSeconds) diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index 6e55e5ad..f6dfc2cc 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -8,9 +8,9 @@ import ( "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/lnrpc" ) @@ -92,7 +92,7 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) { findRoute := func(source, target route.Vertex, amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams, - _ []tlv.Record, + _ record.CustomSet, finalExpiry ...uint16) (*route.Route, error) { if int64(amt) != amtSat*1000 { diff --git a/routing/pathfind.go b/routing/pathfind.go index 3f4f8f60..e8db244a 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -11,8 +11,8 @@ import ( "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -100,7 +100,7 @@ type edgePolicyWithSource struct { func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex, pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32, finalCLTVDelta uint16, - finalDestRecords []tlv.Record) (*route.Route, error) { + destCustomRecords record.CustomSet) (*route.Route, error) { var ( hops []*route.Hop @@ -198,8 +198,8 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex, // If this is the last hop, then we'll populate any TLV records // destined for it. - if i == len(pathEdges)-1 && len(finalDestRecords) != 0 { - currentHop.TLVRecords = finalDestRecords + if i == len(pathEdges)-1 && len(destCustomRecords) != 0 { + currentHop.CustomRecords = destCustomRecords } hops = append([]*route.Hop{currentHop}, hops...) diff --git a/routing/payment_session.go b/routing/payment_session.go index 979b7a7a..124becbd 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -129,7 +129,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes) route, err := newRoute( payment.Amount, sourceVertex, path, height, finalCltvDelta, - payment.FinalDestRecords, + payment.DestCustomRecords, ) if err != nil { // TODO(roasbeef): return which edge/vertex didn't work diff --git a/routing/route/route.go b/routing/route/route.go index 6858f3b8..1444b9df 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -107,9 +107,9 @@ type Hop struct { // only be set for the final hop. MPP *record.MPP - // TLVRecords if non-nil are a set of additional TLV records that + // CustomRecords if non-nil are a set of additional TLV records that // should be included in the forwarding instructions for this node. - TLVRecords []tlv.Record + CustomRecords record.CustomSet // LegacyPayload if true, then this signals that this node doesn't // understand the new TLV payload, so we must instead use the legacy @@ -165,7 +165,8 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error { } // Append any custom types destined for this hop. - records = append(records, h.TLVRecords...) + tlvRecords := tlv.MapToRecords(h.CustomRecords) + records = append(records, tlvRecords...) // To ensure we produce a canonical stream, we'll sort the records // before encoding them as a stream in the hop payload. diff --git a/routing/router.go b/routing/router.go index 5020aefc..f157b7bd 100644 --- a/routing/router.go +++ b/routing/router.go @@ -24,10 +24,10 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chanvalidate" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/multimutex" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/chainview" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/ticker" - "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" ) @@ -1401,7 +1401,7 @@ type routingMsg struct { // factoring in channel capacities and cumulative fees along the route. func (r *ChannelRouter) FindRoute(source, target route.Vertex, amt lnwire.MilliSatoshi, restrictions *RestrictParams, - destTlvRecords []tlv.Record, + destCustomRecords record.CustomSet, finalExpiry ...uint16) (*route.Route, error) { var finalCLTVDelta uint16 @@ -1455,7 +1455,7 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex, // Create the route with absolute time lock values. route, err := newRoute( amt, source, path, uint32(currentHeight), finalCLTVDelta, - destTlvRecords, + destCustomRecords, ) if err != nil { return nil, err @@ -1608,11 +1608,11 @@ type LightningPayment struct { // attempting to complete. PaymentRequest []byte - // FinalDestRecords are TLV records that are to be sent to the final + // DestCustomRecords are TLV records that are to be sent to the final // hop in the new onion payload format. If the destination does not // understand this new onion payload format, then the payment will // fail. - FinalDestRecords []tlv.Record + DestCustomRecords record.CustomSet } // SendPayment attempts to send a payment as described within the passed diff --git a/rpcserver.go b/rpcserver.go index 225fdf00..fd2ef044 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -50,11 +50,11 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/monitoring" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sweep" - "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/zpay32" "github.com/tv42/zbase32" @@ -3099,7 +3099,7 @@ type rpcPaymentIntent struct { lastHop *route.Vertex payReq []byte - destTLV []tlv.Record + destCustomRecords record.CustomSet route *route.Route } @@ -3158,12 +3158,13 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme } payIntent.cltvLimit = cltvLimit - payIntent.destTLV, err = routerrpc.UnmarshallCustomRecords( + err = routerrpc.ValidateCustomRecords( rpcPayReq.DestCustomRecords, ) if err != nil { return payIntent, err } + payIntent.destCustomRecords = rpcPayReq.DestCustomRecords validateDest := func(dest route.Vertex) error { if rpcPayReq.AllowSelfPayment { @@ -3348,7 +3349,7 @@ func (r *rpcServer) dispatchPaymentIntent( LastHop: payIntent.lastHop, PaymentRequest: payIntent.payReq, PayAttemptTimeout: routing.DefaultPayAttemptTimeout, - FinalDestRecords: payIntent.destTLV, + DestCustomRecords: payIntent.destCustomRecords, } preImage, route, routerErr = r.server.chanRouter.SendPayment( From c37289cd94520a44e94b4ec9de1bc15e60387c35 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 11 Dec 2019 11:49:03 +0100 Subject: [PATCH 4/5] routing: pass custom records into pathfinding --- lnrpc/routerrpc/router_backend.go | 4 ++-- routing/pathfind.go | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index dd918f54..63bec96e 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -224,8 +224,8 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context, fromNode, toNode, amt, ) }, - DestPayloadTLV: len(in.DestCustomRecords) != 0, - CltvLimit: cltvLimit, + DestCustomRecords: record.CustomSet(in.DestCustomRecords), + CltvLimit: cltvLimit, } // If we have any TLV records destined for the final hop, then we'll diff --git a/routing/pathfind.go b/routing/pathfind.go index e8db244a..3fa48355 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -289,10 +289,9 @@ type RestrictParams struct { // all cltv expiry heights with the required final cltv delta. CltvLimit uint32 - // DestPayloadTLV should be set to true if we need to drop off a TLV - // payload at the final hop in order to properly complete this payment - // attempt. - DestPayloadTLV bool + // DestCustomRecords contains the custom records to drop off at the + // final hop, if any. + DestCustomRecords record.CustomSet } // PathFindingConfig defines global parameters that control the trade-off in @@ -396,7 +395,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, defer tx.Rollback() } - if r.DestPayloadTLV { + if len(r.DestCustomRecords) > 0 { // Check if the target has TLV enabled targetKey, err := btcec.ParsePubKey(target[:], btcec.S256()) From 75aa4e7061c57c095f8d3910cab7261ca2b2fab1 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 11 Dec 2019 10:54:11 +0100 Subject: [PATCH 5/5] routing: require tlv capability for custom record payments Previously if a payment was sent with custom records attached, path finding wouldn't perform a check whether the final node was capable of receiving custom records in a tlv payload. --- routing/payment_session.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routing/payment_session.go b/routing/payment_session.go index 124becbd..5168ba28 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -97,6 +97,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, OutgoingChannelID: payment.OutgoingChannelID, LastHop: payment.LastHop, CltvLimit: cltvLimit, + DestCustomRecords: payment.DestCustomRecords, } // We'll also obtain a set of bandwidthHints from the lower layer for