multi: send to a blinded path in an invoice

Update the SendPayment flow so that it is able to send to an invoice
containing a blinded path.
This commit is contained in:
Elle Mouton 2024-05-06 16:39:43 +02:00
parent 34d8fff5f9
commit 735d7d9738
No known key found for this signature in database
GPG key ID: D7D916376026F177
4 changed files with 104 additions and 2 deletions

View file

@ -999,6 +999,32 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.PaymentAddr = payAddr
payIntent.PaymentRequest = []byte(rpcPayReq.PaymentRequest)
payIntent.Metadata = payReq.Metadata
if len(payReq.BlindedPaymentPaths) > 0 {
// NOTE: Currently we only choose a single payment path.
// This will be updated in a future PR to handle
// multiple blinded payment paths.
path := payReq.BlindedPaymentPaths[0]
if len(path.Hops) == 0 {
return nil, fmt.Errorf("a blinded payment " +
"must have at least 1 hop")
}
finalHop := path.Hops[len(path.Hops)-1]
payIntent.BlindedPayment = MarshalBlindedPayment(path)
// Replace the target node with the blinded public key
// of the blinded path's final node.
copy(
payIntent.Target[:],
finalHop.BlindedNodePub.SerializeCompressed(),
)
if !path.Features.IsEmpty() {
payIntent.DestFeatures = path.Features.Clone()
}
}
} else {
// Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment
@ -1137,6 +1163,26 @@ func (r *RouterBackend) extractIntentFromSendRequest(
return payIntent, nil
}
// MarshalBlindedPayment marshals a zpay32.BLindedPaymentPath into a
// routing.BlindedPayment.
func MarshalBlindedPayment(
path *zpay32.BlindedPaymentPath) *routing.BlindedPayment {
return &routing.BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
IntroductionPoint: path.Hops[0].BlindedNodePub,
BlindingPoint: path.FirstEphemeralBlindingPoint,
BlindedHops: path.Hops,
},
BaseFee: path.FeeBaseMsat,
ProportionalFeeRate: path.FeeRate,
CltvExpiryDelta: path.CltvExpiryDelta,
HtlcMinimum: path.HTLCMinMsat,
HtlcMaximum: path.HTLCMaxMsat,
Features: path.Features,
}
}
// unmarshallRouteHints unmarshalls a list of route hints.
func unmarshallRouteHints(rpcRouteHints []*lnrpc.RouteHint) (
[][]zpay32.HopHint, error) {

View file

@ -5,6 +5,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btclog"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
@ -205,6 +206,18 @@ func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
return nil, err
}
if p.BlindedPayment != nil {
if len(edges) != 0 {
return nil, fmt.Errorf("cannot have both route hints " +
"and blinded path")
}
edges, err = p.BlindedPayment.toRouteHints()
if err != nil {
return nil, err
}
}
logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
return &paymentSession{
@ -389,6 +402,11 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
return nil, err
}
var blindedPath *sphinx.BlindedPath
if p.payment.BlindedPayment != nil {
blindedPath = p.payment.BlindedPayment.BlindedPath
}
// With the next candidate path found, we'll attempt to turn
// this into a route by applying the time-lock and fee
// requirements.
@ -401,7 +419,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
records: p.payment.DestCustomRecords,
paymentAddr: p.payment.PaymentAddr,
metadata: p.payment.Metadata,
}, nil,
}, blindedPath,
)
if err != nil {
return nil, err

View file

@ -922,9 +922,19 @@ type LightningPayment struct {
// NOTE: This is optional unless required by the payment. When providing
// multiple routes, ensure the hop hints within each route are chained
// together and sorted in forward order in order to reach the
// destination successfully.
// destination successfully. This is mutually exclusive to the
// BlindedPayment field.
RouteHints [][]zpay32.HopHint
// BlindedPayment holds the information about a blinded path to the
// payment recipient. This is mutually exclusive to the RouteHints
// field.
//
// NOTE: a recipient may provide multiple blinded payment paths in the
// same invoice. Currently, LND will only attempt to use the first one.
// A future PR will handle multiple blinded payment paths.
BlindedPayment *BlindedPayment
// OutgoingChannelIDs is the list of channels that are allowed for the
// first hop. If nil, any channel may be used.
OutgoingChannelIDs []uint64

View file

@ -5110,6 +5110,7 @@ type rpcPaymentIntent struct {
paymentAddr *[32]byte
payReq []byte
metadata []byte
blindedPayment *routing.BlindedPayment
destCustomRecords record.CustomSet
@ -5245,6 +5246,32 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
payIntent.paymentAddr = payReq.PaymentAddr
payIntent.metadata = payReq.Metadata
if len(payReq.BlindedPaymentPaths) > 0 {
// NOTE: Currently we only choose a single payment path.
// This will be updated in a future PR to handle
// multiple blinded payment paths.
path := payReq.BlindedPaymentPaths[0]
if len(path.Hops) == 0 {
return payIntent, fmt.Errorf("a blinded " +
"payment must have at least 1 hop")
}
finalHop := path.Hops[len(path.Hops)-1]
payIntent.blindedPayment =
routerrpc.MarshalBlindedPayment(path)
// Replace the target node with the blinded public key
// of the blinded path's final node.
copy(
payIntent.dest[:],
finalHop.BlindedNodePub.SerializeCompressed(),
)
if !payReq.BlindedPaymentPaths[0].Features.IsEmpty() {
payIntent.destFeatures = path.Features.Clone()
}
}
if err := validateDest(payIntent.dest); err != nil {
return payIntent, err
}
@ -5399,6 +5426,7 @@ func (r *rpcServer) dispatchPaymentIntent(
DestFeatures: payIntent.destFeatures,
PaymentAddr: payIntent.paymentAddr,
Metadata: payIntent.metadata,
BlindedPayment: payIntent.blindedPayment,
// Don't enable multi-part payments on the main rpc.
// Users need to use routerrpc for that.