Skip to content

Commit

Permalink
Merge pull request #5810 from bottlepay/payment-metadata
Browse files Browse the repository at this point in the history
routing: send payment metadata
  • Loading branch information
Roasbeef committed Apr 13, 2022
2 parents cd8a87c + 62ae038 commit 440f4d9
Show file tree
Hide file tree
Showing 27 changed files with 1,730 additions and 1,420 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
29 changes: 24 additions & 5 deletions htlcswitch/hop/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ type Payload struct {
// customRecords are user-defined records in the custom type range that
// were included in the payload.
customRecords record.CustomSet

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

// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
Expand All @@ -115,11 +119,12 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload {
// should correspond to the bytes encapsulated in a TLV onion payload.
func NewPayloadFromReader(r io.Reader) (*Payload, error) {
var (
cid uint64
amt uint64
cltv uint32
mpp = &record.MPP{}
amp = &record.AMP{}
cid uint64
amt uint64
cltv uint32
mpp = &record.MPP{}
amp = &record.AMP{}
metadata []byte
)

tlvStream, err := tlv.NewStream(
Expand All @@ -128,6 +133,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
record.NewNextHopIDRecord(&cid),
mpp.Record(),
amp.Record(),
record.NewMetadataRecord(&metadata),
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -168,6 +174,12 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
amp = nil
}

// If no metadata field was parsed, set the metadata field on the
// resulting payload to nil.
if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
metadata = nil
}

// Filter out the custom records.
customRecords := NewCustomRecords(parsedTypes)

Expand All @@ -180,6 +192,7 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
},
MPP: mpp,
AMP: amp,
metadata: metadata,
customRecords: customRecords,
}, nil
}
Expand Down Expand Up @@ -284,6 +297,12 @@ func (h *Payload) CustomRecords() record.CustomSet {
return h.customRecords
}

// Metadata returns the additional data that is sent along with the
// payment to the payee.
func (h *Payload) Metadata() []byte {
return h.metadata
}

// getMinRequiredViolation checks for unrecognized required (even) fields in the
// standard range and returns the lowest required type. Always returning the
// lowest required type allows a failure message to be deterministic.
Expand Down
37 changes: 30 additions & 7 deletions htlcswitch/hop/payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import (
"github.com/stretchr/testify/require"
)

const testUnknownRequiredType = 0x10
const testUnknownRequiredType = 0x80

type decodePayloadTest struct {
name string
payload []byte
expErr error
expCustomRecords map[uint64][]byte
shouldHaveMPP bool
shouldHaveAMP bool
name string
payload []byte
expErr error
expCustomRecords map[uint64][]byte
shouldHaveMPP bool
shouldHaveAMP bool
shouldHaveMetadata bool
}

var decodePayloadTests = []decodePayloadTest{
Expand Down Expand Up @@ -258,6 +259,18 @@ var decodePayloadTests = []decodePayloadTest{
},
shouldHaveAMP: true,
},
{
name: "final hop with metadata",
payload: []byte{
// amount
0x02, 0x00,
// cltv
0x04, 0x00,
// metadata
0x10, 0x03, 0x01, 0x02, 0x03,
},
shouldHaveMetadata: true,
},
}

// TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
Expand Down Expand Up @@ -293,6 +306,7 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
}
testMetadata = []byte{1, 2, 3}
testChildIndex = uint32(9)
)

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

if test.shouldHaveMetadata {
if p.Metadata() == nil {
t.Fatalf("payload should have metadata")
}
require.Equal(t, testMetadata, p.Metadata())
} else if p.Metadata() != nil {
t.Fatalf("unexpected metadata")
}

// Convert expected nil map to empty map, because we always expect an
// initiated map from the payload.
expCustomRecords := make(record.CustomSet)
Expand Down
4 changes: 4 additions & 0 deletions invoices/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ type Payload interface {
// CustomRecords returns the custom tlv type records that were parsed
// from the payload.
CustomRecords() record.CustomSet

// Metadata returns the additional data that is sent along with the
// payment to the payee.
Metadata() []byte
}
1 change: 1 addition & 0 deletions invoices/invoiceregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
customRecords: payload.CustomRecords(),
mpp: payload.MultiPath(),
amp: payload.AMPRecord(),
metadata: payload.Metadata(),
}

switch {
Expand Down
5 changes: 5 additions & 0 deletions invoices/test_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type mockPayload struct {
mpp *record.MPP
amp *record.AMP
customRecords record.CustomSet
metadata []byte
}

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

func (p *mockPayload) Metadata() []byte {
return p.metadata
}

const (
testHtlcExpiry = uint32(5)

Expand Down
13 changes: 11 additions & 2 deletions invoices/update.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package invoices

import (
"encoding/hex"
"errors"

"github.com/lightningnetwork/lnd/amp"
Expand All @@ -22,6 +23,7 @@ type invoiceUpdateCtx struct {
customRecords record.CustomSet
mpp *record.MPP
amp *record.AMP
metadata []byte
}

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

// log logs a message specific to this update context.
func (i *invoiceUpdateCtx) log(s string) {
// Don't use %x in the log statement below, because it doesn't
// distinguish between nil and empty metadata.
metadata := "<nil>"
if i.metadata != nil {
metadata = hex.EncodeToString(i.metadata)
}

log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v, "+
"amp=%v", i.invoiceRef(), s, i.amtPaid, i.expiry, i.circuitKey,
i.mpp, i.amp)
"amp=%v, metadata=%v", i.invoiceRef(), s, i.amtPaid, i.expiry,
i.circuitKey, i.mpp, i.amp, metadata)
}

// failRes is a helper function which creates a failure resolution with
Expand Down
Loading

0 comments on commit 440f4d9

Please sign in to comment.