routing+channeldb: send payment metadata from invoice

This commit is contained in:
Joost Jager
2021-10-04 09:33:12 +02:00
parent 135e27ddd3
commit 9195f29e61
17 changed files with 1580 additions and 1401 deletions

View File

@@ -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.
@@ -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
@@ -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.
@@ -232,6 +239,7 @@ func newRoute(sourceVertex route.Vertex,
LegacyPayload: !tlvPayload,
CustomRecords: customRecords,
MPP: mpp,
Metadata: metadata,
}
hops = append([]*route.Hop{currentHop}, hops...)
@@ -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
@@ -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 {
@@ -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

View File

@@ -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 (
@@ -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.
@@ -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
@@ -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
@@ -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,
@@ -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) {
@@ -1572,6 +1626,7 @@ func TestNewRoute(t *testing.T) {
cltvDelta: finalHopCLTV,
records: nil,
paymentAddr: testCase.paymentAddr,
metadata: testCase.metadata,
},
)

View File

@@ -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)
@@ -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 {

View File

@@ -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.
@@ -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...)
@@ -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)))

View File

@@ -186,6 +186,7 @@ func TestPayloadSize(t *testing.T) {
100000: {1, 2, 3},
1000000: {4, 5},
},
Metadata: []byte{10, 11},
},
}

View File

@@ -1971,6 +1971,10 @@ type LightningPayment struct {
// optimize for fees only, to 1 to optimize for reliability only or a
// value in between for a mix.
TimePref float64
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
}
// AMPOptions houses information that must be known in order to send an AMP