mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-30 18:43:42 +02:00
multi: introduce BlindedPaymentPathSet
This commit introduces a new type, `BlindedPaymentPathSet`. For now, it holds only a single `BlindedPayment` but eventually it will hold and manage a set of blinded payments provided for a specific payment. To make the PR easier to follow though, we start off just letting it hold a single one and do some basic replacements.
This commit is contained in:
@ -280,7 +280,7 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
|
|||||||
var (
|
var (
|
||||||
targetPubKey *route.Vertex
|
targetPubKey *route.Vertex
|
||||||
routeHintEdges map[route.Vertex][]routing.AdditionalEdge
|
routeHintEdges map[route.Vertex][]routing.AdditionalEdge
|
||||||
blindedPmt *routing.BlindedPayment
|
blindedPathSet *routing.BlindedPaymentPathSet
|
||||||
|
|
||||||
// finalCLTVDelta varies depending on whether we're sending to
|
// finalCLTVDelta varies depending on whether we're sending to
|
||||||
// a blinded route or an unblinded node. For blinded paths,
|
// a blinded route or an unblinded node. For blinded paths,
|
||||||
@ -297,13 +297,14 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
|
|||||||
// Validate that the fields provided in the request are sane depending
|
// Validate that the fields provided in the request are sane depending
|
||||||
// on whether it is using a blinded path or not.
|
// on whether it is using a blinded path or not.
|
||||||
if len(in.BlindedPaymentPaths) > 0 {
|
if len(in.BlindedPaymentPaths) > 0 {
|
||||||
blindedPmt, err = parseBlindedPayment(in)
|
blindedPathSet, err = parseBlindedPaymentPaths(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if blindedPmt.Features != nil {
|
pathFeatures := blindedPathSet.Features()
|
||||||
destinationFeatures = blindedPmt.Features.Clone()
|
if pathFeatures != nil {
|
||||||
|
destinationFeatures = pathFeatures.Clone()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we do not have a blinded path, a target pubkey must be
|
// If we do not have a blinded path, a target pubkey must be
|
||||||
@ -390,7 +391,7 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
|
|||||||
DestCustomRecords: record.CustomSet(in.DestCustomRecords),
|
DestCustomRecords: record.CustomSet(in.DestCustomRecords),
|
||||||
CltvLimit: cltvLimit,
|
CltvLimit: cltvLimit,
|
||||||
DestFeatures: destinationFeatures,
|
DestFeatures: destinationFeatures,
|
||||||
BlindedPayment: blindedPmt,
|
BlindedPayment: blindedPathSet.GetPath(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass along an outgoing channel restriction if specified.
|
// Pass along an outgoing channel restriction if specified.
|
||||||
@ -419,39 +420,24 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
|
|||||||
|
|
||||||
return routing.NewRouteRequest(
|
return routing.NewRouteRequest(
|
||||||
sourcePubKey, targetPubKey, amt, in.TimePref, restrictions,
|
sourcePubKey, targetPubKey, amt, in.TimePref, restrictions,
|
||||||
customRecords, routeHintEdges, blindedPmt, finalCLTVDelta,
|
customRecords, routeHintEdges, blindedPathSet.GetPath(),
|
||||||
|
finalCLTVDelta,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBlindedPayment(in *lnrpc.QueryRoutesRequest) (
|
func parseBlindedPaymentPaths(in *lnrpc.QueryRoutesRequest) (
|
||||||
*routing.BlindedPayment, error) {
|
*routing.BlindedPaymentPathSet, error) {
|
||||||
|
|
||||||
if len(in.PubKey) != 0 {
|
if len(in.PubKey) != 0 {
|
||||||
return nil, fmt.Errorf("target pubkey: %x should not be set "+
|
return nil, fmt.Errorf("target pubkey: %x should not be set "+
|
||||||
"when blinded path is provided", in.PubKey)
|
"when blinded path is provided", in.PubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(in.BlindedPaymentPaths) != 1 {
|
|
||||||
return nil, errors.New("query routes only supports a single " +
|
|
||||||
"blinded path")
|
|
||||||
}
|
|
||||||
|
|
||||||
blindedPath := in.BlindedPaymentPaths[0]
|
|
||||||
|
|
||||||
if len(in.RouteHints) > 0 {
|
if len(in.RouteHints) > 0 {
|
||||||
return nil, errors.New("route hints and blinded path can't " +
|
return nil, errors.New("route hints and blinded path can't " +
|
||||||
"both be set")
|
"both be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
blindedPmt, err := unmarshalBlindedPayment(blindedPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse blinded payment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := blindedPmt.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid blinded path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.FinalCltvDelta != 0 {
|
if in.FinalCltvDelta != 0 {
|
||||||
return nil, errors.New("final cltv delta should be " +
|
return nil, errors.New("final cltv delta should be " +
|
||||||
"zero for blinded paths")
|
"zero for blinded paths")
|
||||||
@ -466,7 +452,21 @@ func parseBlindedPayment(in *lnrpc.QueryRoutesRequest) (
|
|||||||
"be populated in blinded path")
|
"be populated in blinded path")
|
||||||
}
|
}
|
||||||
|
|
||||||
return blindedPmt, nil
|
paths := make([]*routing.BlindedPayment, len(in.BlindedPaymentPaths))
|
||||||
|
for i, paymentPath := range in.BlindedPaymentPaths {
|
||||||
|
blindedPmt, err := unmarshalBlindedPayment(paymentPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse blinded payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := blindedPmt.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid blinded path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths[i] = blindedPmt
|
||||||
|
}
|
||||||
|
|
||||||
|
return routing.NewBlindedPaymentPathSet(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalBlindedPayment(rpcPayment *lnrpc.BlindedPaymentPath) (
|
func unmarshalBlindedPayment(rpcPayment *lnrpc.BlindedPaymentPath) (
|
||||||
@ -1001,28 +1001,24 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||||||
payIntent.Metadata = payReq.Metadata
|
payIntent.Metadata = payReq.Metadata
|
||||||
|
|
||||||
if len(payReq.BlindedPaymentPaths) > 0 {
|
if len(payReq.BlindedPaymentPaths) > 0 {
|
||||||
// NOTE: Currently we only choose a single payment path.
|
pathSet, err := BuildBlindedPathSet(
|
||||||
// This will be updated in a future PR to handle
|
payReq.BlindedPaymentPaths,
|
||||||
// multiple blinded payment paths.
|
)
|
||||||
path := payReq.BlindedPaymentPaths[0]
|
if err != nil {
|
||||||
if len(path.Hops) == 0 {
|
return nil, err
|
||||||
return nil, fmt.Errorf("a blinded payment " +
|
|
||||||
"must have at least 1 hop")
|
|
||||||
}
|
}
|
||||||
|
payIntent.BlindedPayment = pathSet.GetPath()
|
||||||
|
|
||||||
finalHop := path.Hops[len(path.Hops)-1]
|
// Replace the target node with the target public key
|
||||||
|
// of the blinded path set.
|
||||||
payIntent.BlindedPayment = MarshalBlindedPayment(path)
|
|
||||||
|
|
||||||
// Replace the target node with the blinded public key
|
|
||||||
// of the blinded path's final node.
|
|
||||||
copy(
|
copy(
|
||||||
payIntent.Target[:],
|
payIntent.Target[:],
|
||||||
finalHop.BlindedNodePub.SerializeCompressed(),
|
pathSet.TargetPubKey().SerializeCompressed(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if !path.Features.IsEmpty() {
|
pathFeatures := pathSet.Features()
|
||||||
payIntent.DestFeatures = path.Features.Clone()
|
if !pathFeatures.IsEmpty() {
|
||||||
|
payIntent.DestFeatures = pathFeatures.Clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1163,9 +1159,29 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||||||
return payIntent, nil
|
return payIntent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBlindedPayment marshals a zpay32.BLindedPaymentPath into a
|
// BuildBlindedPathSet marshals a set of zpay32.BlindedPaymentPath and uses
|
||||||
|
// the result to build a new routing.BlindedPaymentPathSet.
|
||||||
|
func BuildBlindedPathSet(paths []*zpay32.BlindedPaymentPath) (
|
||||||
|
*routing.BlindedPaymentPathSet, error) {
|
||||||
|
|
||||||
|
marshalledPaths := make([]*routing.BlindedPayment, len(paths))
|
||||||
|
for i, path := range paths {
|
||||||
|
paymentPath := marshalBlindedPayment(path)
|
||||||
|
|
||||||
|
err := paymentPath.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
marshalledPaths[i] = paymentPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return routing.NewBlindedPaymentPathSet(marshalledPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalBlindedPayment marshals a zpay32.BLindedPaymentPath into a
|
||||||
// routing.BlindedPayment.
|
// routing.BlindedPayment.
|
||||||
func MarshalBlindedPayment(
|
func marshalBlindedPayment(
|
||||||
path *zpay32.BlindedPaymentPath) *routing.BlindedPayment {
|
path *zpay32.BlindedPaymentPath) *routing.BlindedPayment {
|
||||||
|
|
||||||
return &routing.BlindedPayment{
|
return &routing.BlindedPayment{
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@ -25,6 +26,141 @@ var (
|
|||||||
ErrHTLCRestrictions = errors.New("invalid htlc minimum and maximum")
|
ErrHTLCRestrictions = errors.New("invalid htlc minimum and maximum")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BlindedPaymentPathSet groups the data we need to handle sending to a set of
|
||||||
|
// blinded paths provided by the recipient of a payment.
|
||||||
|
//
|
||||||
|
// NOTE: for now this only holds a single BlindedPayment. By the end of the PR
|
||||||
|
// series, it will handle multiple paths.
|
||||||
|
type BlindedPaymentPathSet struct {
|
||||||
|
// paths is the set of blinded payment paths for a single payment.
|
||||||
|
// NOTE: For now this will always only have a single entry. By the end
|
||||||
|
// of this PR, it can hold multiple.
|
||||||
|
paths []*BlindedPayment
|
||||||
|
|
||||||
|
// targetPubKey is the ephemeral node pub key that we will inject into
|
||||||
|
// each path as the last hop. This is only for the sake of path finding.
|
||||||
|
// Once the path has been found, the original destination pub key is
|
||||||
|
// used again. In the edge case where there is only a single hop in the
|
||||||
|
// path (the introduction node is the destination node), then this will
|
||||||
|
// just be the introduction node's real public key.
|
||||||
|
targetPubKey *btcec.PublicKey
|
||||||
|
|
||||||
|
// features is the set of relay features available for the payment.
|
||||||
|
// This is extracted from the set of blinded payment paths. At the
|
||||||
|
// moment we require that all paths for the same payment have the
|
||||||
|
// same feature set.
|
||||||
|
features *lnwire.FeatureVector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlindedPaymentPathSet constructs a new BlindedPaymentPathSet from a set of
|
||||||
|
// BlindedPayments.
|
||||||
|
func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return nil, ErrNoBlindedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we assert that all the paths have the same set of features.
|
||||||
|
features := paths[0].Features
|
||||||
|
noFeatures := features == nil || features.IsEmpty()
|
||||||
|
for i := 1; i < len(paths); i++ {
|
||||||
|
noFeats := paths[i].Features == nil ||
|
||||||
|
paths[i].Features.IsEmpty()
|
||||||
|
|
||||||
|
if noFeatures && !noFeats {
|
||||||
|
return nil, fmt.Errorf("all blinded paths must have " +
|
||||||
|
"the same set of features")
|
||||||
|
}
|
||||||
|
|
||||||
|
if noFeatures {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !features.RawFeatureVector.Equals(
|
||||||
|
paths[i].Features.RawFeatureVector,
|
||||||
|
) {
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("all blinded paths must have " +
|
||||||
|
"the same set of features")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: for now, we just take a single path. By the end of this PR
|
||||||
|
// series, all paths will be kept.
|
||||||
|
path := paths[0]
|
||||||
|
|
||||||
|
finalHop := path.BlindedPath.
|
||||||
|
BlindedHops[len(path.BlindedPath.BlindedHops)-1]
|
||||||
|
|
||||||
|
return &BlindedPaymentPathSet{
|
||||||
|
paths: paths,
|
||||||
|
targetPubKey: finalHop.BlindedNodePub,
|
||||||
|
features: features,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetPubKey returns the public key to be used as the destination node's
|
||||||
|
// public key during pathfinding.
|
||||||
|
func (s *BlindedPaymentPathSet) TargetPubKey() *btcec.PublicKey {
|
||||||
|
return s.targetPubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features returns the set of relay features available for the payment.
|
||||||
|
func (s *BlindedPaymentPathSet) Features() *lnwire.FeatureVector {
|
||||||
|
return s.features
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPath is a temporary getter for the single path that the set holds.
|
||||||
|
// This will be removed later on in this PR.
|
||||||
|
func (s *BlindedPaymentPathSet) GetPath() *BlindedPayment {
|
||||||
|
return s.paths[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// LargestLastHopPayloadPath returns the BlindedPayment in the set that has the
|
||||||
|
// largest last-hop payload. This is to be used for onion size estimation in
|
||||||
|
// path finding.
|
||||||
|
func (s *BlindedPaymentPathSet) LargestLastHopPayloadPath() *BlindedPayment {
|
||||||
|
var (
|
||||||
|
largestPath *BlindedPayment
|
||||||
|
currentMax int
|
||||||
|
)
|
||||||
|
for _, path := range s.paths {
|
||||||
|
numHops := len(path.BlindedPath.BlindedHops)
|
||||||
|
lastHop := path.BlindedPath.BlindedHops[numHops-1]
|
||||||
|
|
||||||
|
if len(lastHop.CipherText) > currentMax {
|
||||||
|
largestPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return largestPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRouteHints converts the blinded path payment set into a RouteHints map so
|
||||||
|
// that the blinded payment paths can be treated like route hints throughout the
|
||||||
|
// code base.
|
||||||
|
func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
|
||||||
|
hints := make(RouteHints)
|
||||||
|
|
||||||
|
for _, path := range s.paths {
|
||||||
|
pathHints, err := path.toRouteHints()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for from, edges := range pathHints {
|
||||||
|
hints[from] = append(hints[from], edges...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hints) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return hints, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BlindedPayment provides the path and payment parameters required to send a
|
// BlindedPayment provides the path and payment parameters required to send a
|
||||||
// payment along a blinded path.
|
// payment along a blinded path.
|
||||||
type BlindedPayment struct {
|
type BlindedPayment struct {
|
||||||
|
28
rpcserver.go
28
rpcserver.go
@ -5242,28 +5242,24 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
|
|||||||
payIntent.metadata = payReq.Metadata
|
payIntent.metadata = payReq.Metadata
|
||||||
|
|
||||||
if len(payReq.BlindedPaymentPaths) > 0 {
|
if len(payReq.BlindedPaymentPaths) > 0 {
|
||||||
// NOTE: Currently we only choose a single payment path.
|
pathSet, err := routerrpc.BuildBlindedPathSet(
|
||||||
// This will be updated in a future PR to handle
|
payReq.BlindedPaymentPaths,
|
||||||
// multiple blinded payment paths.
|
)
|
||||||
path := payReq.BlindedPaymentPaths[0]
|
if err != nil {
|
||||||
if len(path.Hops) == 0 {
|
return payIntent, err
|
||||||
return payIntent, fmt.Errorf("a blinded " +
|
|
||||||
"payment must have at least 1 hop")
|
|
||||||
}
|
}
|
||||||
|
payIntent.blindedPayment = pathSet.GetPath()
|
||||||
|
|
||||||
finalHop := path.Hops[len(path.Hops)-1]
|
// Replace the destination node with the target public
|
||||||
payIntent.blindedPayment =
|
// key of the blinded path set.
|
||||||
routerrpc.MarshalBlindedPayment(path)
|
|
||||||
|
|
||||||
// Replace the target node with the blinded public key
|
|
||||||
// of the blinded path's final node.
|
|
||||||
copy(
|
copy(
|
||||||
payIntent.dest[:],
|
payIntent.dest[:],
|
||||||
finalHop.BlindedNodePub.SerializeCompressed(),
|
pathSet.TargetPubKey().SerializeCompressed(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if !payReq.BlindedPaymentPaths[0].Features.IsEmpty() {
|
pathFeatures := pathSet.Features()
|
||||||
payIntent.destFeatures = path.Features.Clone()
|
if !pathFeatures.IsEmpty() {
|
||||||
|
payIntent.destFeatures = pathFeatures.Clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user