Skip to content

Commit

Permalink
routing+channeldb: send payment metadata from invoice
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Apr 13, 2022
1 parent 135e27d commit 9195f29
Show file tree
Hide file tree
Showing 17 changed files with 1,580 additions and 1,401 deletions.
11 changes: 11 additions & 0 deletions channeldb/payments.go
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,10 @@ func serializeHop(w io.Writer, h *route.Hop) error {
records = append(records, h.MPP.Record())
}

if h.Metadata != nil {
records = append(records, record.NewMetadataRecord(&h.Metadata))
}

// Final sanity check to absolutely rule out custom records that are not
// custom and write into the standard range.
if err := h.CustomRecords.Validate(); err != nil {
Expand Down Expand Up @@ -1255,6 +1259,13 @@ func deserializeHop(r io.Reader) (*route.Hop, error) {
h.MPP = mpp
}

metadataType := uint64(record.MetadataOnionType)
if metadata, ok := tlvMap[metadataType]; ok {
delete(tlvMap, metadataType)

h.Metadata = metadata
}

h.CustomRecords = tlvMap

return h, nil
Expand Down
3 changes: 2 additions & 1 deletion channeldb/payments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ var (
65536: []byte{},
80001: []byte{},
},
MPP: record.NewMPP(32, [32]byte{0x42}),
MPP: record.NewMPP(32, [32]byte{0x42}),
Metadata: []byte{1, 2, 3},
}

testHop2 = &route.Hop{
Expand Down
11 changes: 11 additions & 0 deletions docs/release-notes/release-notes-0.15.0.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Release Notes

## Payments

Support according to the
[spec](https://github.com/lightningnetwork/lightning-rfc/pull/912) has been
added for [payment metadata in
invoices](https://github.com/lightningnetwork/lnd/pull/5810). If metadata is
present in the invoice, it is encoded as a tlv record for the receiver.

This functionality unlocks future features such as [stateless
invoices](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-September/003236.html).

## Security

* [Misconfigured ZMQ
Expand Down
2,809 changes: 1,410 additions & 1,399 deletions lnrpc/lightning.pb.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions lnrpc/lightning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2792,6 +2792,9 @@ message Hop {
to drop off at each hop within the onion.
*/
map<uint64, bytes> custom_records = 11;

// The payment metadata to send along with the payment to the payee.
bytes metadata = 13;
}

message MPPRecord {
Expand Down
5 changes: 5 additions & 0 deletions lnrpc/lightning.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4616,6 +4616,11 @@
"format": "byte"
},
"description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion."
},
"metadata": {
"type": "string",
"format": "byte",
"description": "The payment metadata to send along with the payment to the payee."
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions lnrpc/routerrpc/router.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,11 @@
"format": "byte"
},
"description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion."
},
"metadata": {
"type": "string",
"format": "byte",
"description": "The payment metadata to send along with the payment to the payee."
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions lnrpc/routerrpc/router_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error)
CustomRecords: hop.CustomRecords,
TlvPayload: !hop.LegacyPayload,
MppRecord: mpp,
Metadata: hop.Metadata,
}
incomingAmt = hop.AmtToForward
}
Expand Down Expand Up @@ -766,6 +767,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.DestFeatures = payReq.Features
payIntent.PaymentAddr = payAddr
payIntent.PaymentRequest = []byte(rpcPayReq.PaymentRequest)
payIntent.Metadata = payReq.Metadata
} else {
// Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment
Expand Down
12 changes: 12 additions & 0 deletions lnwire/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ const (
// TODO: Decide on actual feature bit value.
ExplicitChannelTypeOptional = 45

// PaymentMetadataRequired is a required bit that denotes that if an
// invoice contains metadata, it must be passed along with the payment
// htlc(s).
PaymentMetadataRequired = 48

// PaymentMetadataOptional is an optional bit that denotes that if an
// invoice contains metadata, it may be passed along with the payment
// htlc(s).
PaymentMetadataOptional = 49

// ScriptEnforcedLeaseOptional is an optional feature bit that signals
// that the node requires channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments, along with an
Expand Down Expand Up @@ -218,6 +228,8 @@ var Features = map[FeatureBit]string{
WumboChannelsOptional: "wumbo-channels",
AMPRequired: "amp",
AMPOptional: "amp",
PaymentMetadataOptional: "payment-metadata",
PaymentMetadataRequired: "payment-metadata",
ExplicitChannelTypeOptional: "explicit-commitment-type",
ExplicitChannelTypeRequired: "explicit-commitment-type",
ScriptEnforcedLeaseRequired: "script-enforced-lease",
Expand Down
16 changes: 16 additions & 0 deletions record/hop.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const (
// NextHopOnionType is the type used in the onion to reference the ID
// of the next hop.
NextHopOnionType tlv.Type = 6

// MetadataOnionType is the type used in the onion for the payment
// metadata.
MetadataOnionType tlv.Type = 16
)

// NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward
Expand Down Expand Up @@ -45,3 +49,15 @@ func NewLockTimeRecord(lockTime *uint32) tlv.Record {
func NewNextHopIDRecord(cid *uint64) tlv.Record {
return tlv.MakePrimitiveRecord(NextHopOnionType, cid)
}

// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10)
// for an onion payload.
func NewMetadataRecord(metadata *[]byte) tlv.Record {
return tlv.MakeDynamicRecord(
MetadataOnionType, metadata,
func() uint64 {
return uint64(len(*metadata))
},
tlv.EVarBytes, tlv.DVarBytes,
)
}
23 changes: 22 additions & 1 deletion routing/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ type finalHopParams struct {
cltvDelta uint16
records record.CustomSet
paymentAddr *[32]byte

// metadata is additional data that is sent along with the payment to
// the payee.
metadata []byte
}

// newRoute constructs a route using the provided path and final hop constraints.
Expand Down Expand Up @@ -138,6 +142,7 @@ func newRoute(sourceVertex route.Vertex,
tlvPayload bool
customRecords record.CustomSet
mpp *record.MPP
metadata []byte
)

// Define a helper function that checks this edge's feature
Expand Down Expand Up @@ -202,6 +207,8 @@ func newRoute(sourceVertex route.Vertex,
*finalHop.paymentAddr,
)
}

metadata = finalHop.metadata
} else {
// The amount that the current hop needs to forward is
// equal to the incoming amount of the next hop.
Expand Down Expand Up @@ -232,6 +239,7 @@ func newRoute(sourceVertex route.Vertex,
LegacyPayload: !tlvPayload,
CustomRecords: customRecords,
MPP: mpp,
Metadata: metadata,
}

hops = append([]*route.Hop{currentHop}, hops...)
Expand Down Expand Up @@ -330,6 +338,10 @@ type RestrictParams struct {
// mitigate probing vectors and payment sniping attacks on overpaid
// invoices.
PaymentAddr *[32]byte

// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
}

// PathFindingConfig defines global parameters that control the trade-off in
Expand Down Expand Up @@ -474,6 +486,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
return nil, errNoPaymentAddr
}

// If the caller needs to send custom records, check that our
// destination feature vector supports TLV.
if r.Metadata != nil &&
!features.HasFeature(lnwire.TLVOnionPayloadOptional) {

return nil, errNoTlvPayload
}

// Set up outgoing channel map for quicker access.
var outgoingChanMap map[uint64]struct{}
if len(r.OutgoingChannelIDs) > 0 {
Expand Down Expand Up @@ -547,7 +567,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
LegacyPayload: !features.HasFeature(
lnwire.TLVOnionPayloadOptional,
),
MPP: mpp,
MPP: mpp,
Metadata: r.Metadata,
}

// We can't always assume that the end destination is publicly
Expand Down
55 changes: 55 additions & 0 deletions routing/pathfind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)

const (
Expand Down Expand Up @@ -840,6 +841,9 @@ func TestPathFinding(t *testing.T) {
}, {
name: "route to self",
fn: runRouteToSelf,
}, {
name: "with metadata",
fn: runFindPathWithMetadata,
}}

// Run with graph cache enabled.
Expand All @@ -866,6 +870,46 @@ func TestPathFinding(t *testing.T) {
}
}

// runFindPathWithMetadata tests that metadata is taken into account during
// pathfinding.
func runFindPathWithMetadata(t *testing.T, useCache bool) {
testChannels := []*testChannel{
symmetricTestChannel("alice", "bob", 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: 100000000,
}),
}

ctx := newPathFindingTestContext(t, useCache, testChannels, "alice")
defer ctx.cleanup()

paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.keyFromAlias("bob")

// Assert that a path is found when metadata is specified.
ctx.restrictParams.Metadata = []byte{1, 2, 3}
ctx.restrictParams.DestFeatures = tlvFeatures

path, err := ctx.findPath(target, paymentAmt)
require.NoError(t, err)
require.Len(t, path, 1)

// Assert that no path is found when metadata is too large.
ctx.restrictParams.Metadata = make([]byte, 2000)

_, err = ctx.findPath(target, paymentAmt)
require.ErrorIs(t, errNoPathFound, err)

// Assert that tlv payload support takes precedence over metadata
// issues.
ctx.restrictParams.DestFeatures = lnwire.EmptyFeatureVector()

_, err = ctx.findPath(target, paymentAmt)
require.ErrorIs(t, errNoTlvPayload, err)
}

// runFindLowestFeePath tests that out of two routes with identical total
// time lock values, the route with the lowest total fee should be returned.
// The fee rates are chosen such that the test failed on the previous edge
Expand Down Expand Up @@ -1340,6 +1384,9 @@ func TestNewRoute(t *testing.T) {

paymentAddr *[32]byte

// metadata is the payment metadata to attach to the route.
metadata []byte

// expectedFees is a list of fees that every hop is expected
// to charge for forwarding.
expectedFees []lnwire.MilliSatoshi
Expand Down Expand Up @@ -1380,6 +1427,7 @@ func TestNewRoute(t *testing.T) {
hops: []*channeldb.CachedEdgePolicy{
createHop(100, 1000, 1000000, 10),
},
metadata: []byte{1, 2, 3},
expectedFees: []lnwire.MilliSatoshi{0},
expectedTimeLocks: []uint32{1},
expectedTotalAmount: 100000,
Expand Down Expand Up @@ -1561,6 +1609,12 @@ func TestNewRoute(t *testing.T) {
" but got: %v instead",
testCase.expectedMPP, finalHop.MPP)
}

if !bytes.Equal(finalHop.Metadata, testCase.metadata) {
t.Errorf("Expected final metadata field: %v, "+
" but got: %v instead",
testCase.metadata, finalHop.Metadata)
}
}

t.Run(testCase.name, func(t *testing.T) {
Expand All @@ -1572,6 +1626,7 @@ func TestNewRoute(t *testing.T) {
cltvDelta: finalHopCLTV,
records: nil,
paymentAddr: testCase.paymentAddr,
metadata: testCase.metadata,
},
)

Expand Down
2 changes: 2 additions & 0 deletions routing/payment_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
DestCustomRecords: p.payment.DestCustomRecords,
DestFeatures: p.payment.DestFeatures,
PaymentAddr: p.payment.PaymentAddr,
Metadata: p.payment.Metadata,
}

finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
Expand Down Expand Up @@ -388,6 +389,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
cltvDelta: finalCltvDelta,
records: p.payment.DestCustomRecords,
paymentAddr: p.payment.PaymentAddr,
metadata: p.payment.Metadata,
},
)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions routing/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ type Hop struct {
// understand the new TLV payload, so we must instead use the legacy
// payload.
LegacyPayload bool

// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
}

// Copy returns a deep copy of the Hop.
Expand Down Expand Up @@ -205,6 +209,13 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
}
}

// If metadata is specified, generate a tlv record for it.
if h.Metadata != nil {
records = append(records,
record.NewMetadataRecord(&h.Metadata),
)
}

// Append any custom types destined for this hop.
tlvRecords := tlv.MapToRecords(h.CustomRecords)
records = append(records, tlvRecords...)
Expand Down Expand Up @@ -259,6 +270,11 @@ func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
addRecord(record.AMPOnionType, h.AMP.PayloadSize())
}

// Add metadata if present.
if h.Metadata != nil {
addRecord(record.MetadataOnionType, uint64(len(h.Metadata)))
}

// Add custom records.
for k, v := range h.CustomRecords {
addRecord(tlv.Type(k), uint64(len(v)))
Expand Down
1 change: 1 addition & 0 deletions routing/route/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func TestPayloadSize(t *testing.T) {
100000: {1, 2, 3},
1000000: {4, 5},
},
Metadata: []byte{10, 11},
},
}

Expand Down
Loading

0 comments on commit 9195f29

Please sign in to comment.