Skip to content

Commit 440f4d9

Browse files
authored
Merge pull request #5810 from bottlepay/payment-metadata
routing: send payment metadata
2 parents cd8a87c + 62ae038 commit 440f4d9

27 files changed

+1730
-1420
lines changed

channeldb/payments.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,10 @@ func serializeHop(w io.Writer, h *route.Hop) error {
11411141
records = append(records, h.MPP.Record())
11421142
}
11431143

1144+
if h.Metadata != nil {
1145+
records = append(records, record.NewMetadataRecord(&h.Metadata))
1146+
}
1147+
11441148
// Final sanity check to absolutely rule out custom records that are not
11451149
// custom and write into the standard range.
11461150
if err := h.CustomRecords.Validate(); err != nil {
@@ -1255,6 +1259,13 @@ func deserializeHop(r io.Reader) (*route.Hop, error) {
12551259
h.MPP = mpp
12561260
}
12571261

1262+
metadataType := uint64(record.MetadataOnionType)
1263+
if metadata, ok := tlvMap[metadataType]; ok {
1264+
delete(tlvMap, metadataType)
1265+
1266+
h.Metadata = metadata
1267+
}
1268+
12581269
h.CustomRecords = tlvMap
12591270

12601271
return h, nil

channeldb/payments_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ var (
3131
65536: []byte{},
3232
80001: []byte{},
3333
},
34-
MPP: record.NewMPP(32, [32]byte{0x42}),
34+
MPP: record.NewMPP(32, [32]byte{0x42}),
35+
Metadata: []byte{1, 2, 3},
3536
}
3637

3738
testHop2 = &route.Hop{

docs/release-notes/release-notes-0.15.0.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Release Notes
22

3+
## Payments
4+
5+
Support according to the
6+
[spec](https://github.com/lightningnetwork/lightning-rfc/pull/912) has been
7+
added for [payment metadata in
8+
invoices](https://github.com/lightningnetwork/lnd/pull/5810). If metadata is
9+
present in the invoice, it is encoded as a tlv record for the receiver.
10+
11+
This functionality unlocks future features such as [stateless
12+
invoices](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-September/003236.html).
13+
314
## Security
415

516
* [Misconfigured ZMQ

htlcswitch/hop/payload.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ type Payload struct {
9393
// customRecords are user-defined records in the custom type range that
9494
// were included in the payload.
9595
customRecords record.CustomSet
96+
97+
// metadata is additional data that is sent along with the payment to
98+
// the payee.
99+
metadata []byte
96100
}
97101

98102
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
@@ -115,11 +119,12 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload {
115119
// should correspond to the bytes encapsulated in a TLV onion payload.
116120
func NewPayloadFromReader(r io.Reader) (*Payload, error) {
117121
var (
118-
cid uint64
119-
amt uint64
120-
cltv uint32
121-
mpp = &record.MPP{}
122-
amp = &record.AMP{}
122+
cid uint64
123+
amt uint64
124+
cltv uint32
125+
mpp = &record.MPP{}
126+
amp = &record.AMP{}
127+
metadata []byte
123128
)
124129

125130
tlvStream, err := tlv.NewStream(
@@ -128,6 +133,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
128133
record.NewNextHopIDRecord(&cid),
129134
mpp.Record(),
130135
amp.Record(),
136+
record.NewMetadataRecord(&metadata),
131137
)
132138
if err != nil {
133139
return nil, err
@@ -168,6 +174,12 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
168174
amp = nil
169175
}
170176

177+
// If no metadata field was parsed, set the metadata field on the
178+
// resulting payload to nil.
179+
if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
180+
metadata = nil
181+
}
182+
171183
// Filter out the custom records.
172184
customRecords := NewCustomRecords(parsedTypes)
173185

@@ -180,6 +192,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
180192
},
181193
MPP: mpp,
182194
AMP: amp,
195+
metadata: metadata,
183196
customRecords: customRecords,
184197
}, nil
185198
}
@@ -284,6 +297,12 @@ func (h *Payload) CustomRecords() record.CustomSet {
284297
return h.customRecords
285298
}
286299

300+
// Metadata returns the additional data that is sent along with the
301+
// payment to the payee.
302+
func (h *Payload) Metadata() []byte {
303+
return h.metadata
304+
}
305+
287306
// getMinRequiredViolation checks for unrecognized required (even) fields in the
288307
// standard range and returns the lowest required type. Always returning the
289308
// lowest required type allows a failure message to be deterministic.

htlcswitch/hop/payload_test.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import (
1111
"github.com/stretchr/testify/require"
1212
)
1313

14-
const testUnknownRequiredType = 0x10
14+
const testUnknownRequiredType = 0x80
1515

1616
type decodePayloadTest struct {
17-
name string
18-
payload []byte
19-
expErr error
20-
expCustomRecords map[uint64][]byte
21-
shouldHaveMPP bool
22-
shouldHaveAMP bool
17+
name string
18+
payload []byte
19+
expErr error
20+
expCustomRecords map[uint64][]byte
21+
shouldHaveMPP bool
22+
shouldHaveAMP bool
23+
shouldHaveMetadata bool
2324
}
2425

2526
var decodePayloadTests = []decodePayloadTest{
@@ -258,6 +259,18 @@ var decodePayloadTests = []decodePayloadTest{
258259
},
259260
shouldHaveAMP: true,
260261
},
262+
{
263+
name: "final hop with metadata",
264+
payload: []byte{
265+
// amount
266+
0x02, 0x00,
267+
// cltv
268+
0x04, 0x00,
269+
// metadata
270+
0x10, 0x03, 0x01, 0x02, 0x03,
271+
},
272+
shouldHaveMetadata: true,
273+
},
261274
}
262275

263276
// TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
@@ -293,6 +306,7 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
293306
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
294307
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
295308
}
309+
testMetadata = []byte{1, 2, 3}
296310
testChildIndex = uint32(9)
297311
)
298312

@@ -331,6 +345,15 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
331345
t.Fatalf("unexpected AMP payload")
332346
}
333347

348+
if test.shouldHaveMetadata {
349+
if p.Metadata() == nil {
350+
t.Fatalf("payload should have metadata")
351+
}
352+
require.Equal(t, testMetadata, p.Metadata())
353+
} else if p.Metadata() != nil {
354+
t.Fatalf("unexpected metadata")
355+
}
356+
334357
// Convert expected nil map to empty map, because we always expect an
335358
// initiated map from the payload.
336359
expCustomRecords := make(record.CustomSet)

invoices/interface.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ type Payload interface {
1818
// CustomRecords returns the custom tlv type records that were parsed
1919
// from the payload.
2020
CustomRecords() record.CustomSet
21+
22+
// Metadata returns the additional data that is sent along with the
23+
// payment to the payee.
24+
Metadata() []byte
2125
}

invoices/invoiceregistry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
906906
customRecords: payload.CustomRecords(),
907907
mpp: payload.MultiPath(),
908908
amp: payload.AMPRecord(),
909+
metadata: payload.Metadata(),
909910
}
910911

911912
switch {

invoices/test_utils_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type mockPayload struct {
3030
mpp *record.MPP
3131
amp *record.AMP
3232
customRecords record.CustomSet
33+
metadata []byte
3334
}
3435

3536
func (p *mockPayload) MultiPath() *record.MPP {
@@ -50,6 +51,10 @@ func (p *mockPayload) CustomRecords() record.CustomSet {
5051
return p.customRecords
5152
}
5253

54+
func (p *mockPayload) Metadata() []byte {
55+
return p.metadata
56+
}
57+
5358
const (
5459
testHtlcExpiry = uint32(5)
5560

invoices/update.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package invoices
22

33
import (
4+
"encoding/hex"
45
"errors"
56

67
"github.com/lightningnetwork/lnd/amp"
@@ -22,6 +23,7 @@ type invoiceUpdateCtx struct {
2223
customRecords record.CustomSet
2324
mpp *record.MPP
2425
amp *record.AMP
26+
metadata []byte
2527
}
2628

2729
// invoiceRef returns an identifier that can be used to lookup or update the
@@ -52,9 +54,16 @@ func (i invoiceUpdateCtx) setID() *[32]byte {
5254

5355
// log logs a message specific to this update context.
5456
func (i *invoiceUpdateCtx) log(s string) {
57+
// Don't use %x in the log statement below, because it doesn't
58+
// distinguish between nil and empty metadata.
59+
metadata := "<nil>"
60+
if i.metadata != nil {
61+
metadata = hex.EncodeToString(i.metadata)
62+
}
63+
5564
log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+
56-
"amp=%v", i.invoiceRef(), s, i.amtPaid, i.expiry, i.circuitKey,
57-
i.mpp, i.amp)
65+
"amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry,
66+
i.circuitKey, i.mpp, i.amp, metadata)
5867
}
5968

6069
// failRes is a helper function which creates a failure resolution with

0 commit comments

Comments
 (0)