mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-27 18:22:24 +01:00
multi: add blinded route to route requests expressed as hints
Add the option to include a blinded route in a route request (exclusive to including hop hints, because it's incongruous to include both), and express the route as a chain of hop hints. Using a chain of hints over a single hint to represent the whole path allows us to re-use our route construction to fill in a lot of the path on our behalf.
This commit is contained in:
parent
48e36d93d4
commit
c9609b8214
@ -327,7 +327,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
|
||||
// the route.
|
||||
routeReq, err := routing.NewRouteRequest(
|
||||
sourcePubKey, &targetPubKey, amt, in.TimePref, restrictions,
|
||||
customRecords, routeHintEdges, finalCLTVDelta,
|
||||
customRecords, routeHintEdges, nil, finalCLTVDelta,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -404,7 +404,7 @@ func (s *Server) EstimateRouteFee(ctx context.Context,
|
||||
FeeLimit: feeLimit,
|
||||
CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock,
|
||||
ProbabilitySource: mc.GetProbability,
|
||||
}, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta,
|
||||
}, nil, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -5,7 +5,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -77,3 +79,94 @@ func (b *BlindedPayment) Validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toRouteHints produces a set of chained route hints that represent a blinded
|
||||
// path. In the case of a single hop blinded route (which is paying directly
|
||||
// to the introduction point), no hints will be returned. In this case callers
|
||||
// *must* account for the blinded route's CLTV delta elsewhere (as this is
|
||||
// effectively the final_cltv_delta for the receiving introduction node). In
|
||||
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
|
||||
// hints (both for intermediate hops and the final_cltv_delta for the receiving
|
||||
// node).
|
||||
func (b *BlindedPayment) toRouteHints() RouteHints {
|
||||
// If we just have a single hop in our blinded route, it just contains
|
||||
// an introduction node (this is a valid path according to the spec).
|
||||
// Since we have the un-blinded node ID for the introduction node, we
|
||||
// don't need to add any route hints.
|
||||
if len(b.BlindedPath.BlindedHops) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hintCount := len(b.BlindedPath.BlindedHops) - 1
|
||||
hints := make(
|
||||
map[route.Vertex][]*channeldb.CachedEdgePolicy, hintCount,
|
||||
)
|
||||
|
||||
// Start at the unblinded introduction node, because our pathfinding
|
||||
// will be able to locate this point in the graph.
|
||||
fromNode := route.NewVertex(b.BlindedPath.IntroductionPoint)
|
||||
|
||||
features := lnwire.EmptyFeatureVector()
|
||||
if b.Features != nil {
|
||||
features = b.Features.Clone()
|
||||
}
|
||||
|
||||
// Use the total aggregate relay parameters for the entire blinded
|
||||
// route as the policy for the hint from our introduction node. This
|
||||
// will ensure that pathfinding provides sufficient fees/delay for the
|
||||
// blinded portion to the introduction node.
|
||||
firstBlindedHop := b.BlindedPath.BlindedHops[1].BlindedNodePub
|
||||
hints[fromNode] = []*channeldb.CachedEdgePolicy{
|
||||
{
|
||||
TimeLockDelta: b.CltvExpiryDelta,
|
||||
MinHTLC: lnwire.MilliSatoshi(b.HtlcMinimum),
|
||||
MaxHTLC: lnwire.MilliSatoshi(b.HtlcMaximum),
|
||||
FeeBaseMSat: lnwire.MilliSatoshi(b.BaseFee),
|
||||
FeeProportionalMillionths: lnwire.MilliSatoshi(
|
||||
b.ProportionalFee,
|
||||
),
|
||||
ToNodePubKey: func() route.Vertex {
|
||||
return route.NewVertex(
|
||||
// The first node in this slice is
|
||||
// the introduction node, so we start
|
||||
// at index 1 to get the first blinded
|
||||
// relaying node.
|
||||
firstBlindedHop,
|
||||
)
|
||||
},
|
||||
ToNodeFeatures: features,
|
||||
},
|
||||
}
|
||||
|
||||
// Start at an offset of 1 because the first node in our blinded hops
|
||||
// is the introduction node and terminate at the second-last node
|
||||
// because we're dealing with hops as pairs.
|
||||
for i := 1; i < hintCount; i++ {
|
||||
// Set our origin node to the current
|
||||
fromNode = route.NewVertex(
|
||||
b.BlindedPath.BlindedHops[i].BlindedNodePub,
|
||||
)
|
||||
|
||||
// Create a hint which has no fee or cltv delta. We
|
||||
// specifically want zero values here because our relay
|
||||
// parameters are expressed in encrypted blobs rather than the
|
||||
// route itself for blinded routes.
|
||||
nextHopIdx := i + 1
|
||||
nextNode := route.NewVertex(
|
||||
b.BlindedPath.BlindedHops[nextHopIdx].BlindedNodePub,
|
||||
)
|
||||
|
||||
hint := &channeldb.CachedEdgePolicy{
|
||||
ToNodePubKey: func() route.Vertex {
|
||||
return nextNode
|
||||
},
|
||||
ToNodeFeatures: features,
|
||||
}
|
||||
|
||||
hints[fromNode] = []*channeldb.CachedEdgePolicy{
|
||||
hint,
|
||||
}
|
||||
}
|
||||
|
||||
return hints
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ package routing
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -68,3 +71,113 @@ func TestBlindedPathValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlindedPaymentToHints tests conversion of a blinded path to a chain of
|
||||
// route hints. As our function assumes that the blinded payment has already
|
||||
// been validated.
|
||||
func TestBlindedPaymentToHints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
_, pk1 = btcec.PrivKeyFromBytes([]byte{1})
|
||||
_, pkb1 = btcec.PrivKeyFromBytes([]byte{2})
|
||||
_, pkb2 = btcec.PrivKeyFromBytes([]byte{3})
|
||||
_, pkb3 = btcec.PrivKeyFromBytes([]byte{4})
|
||||
|
||||
v1 = route.NewVertex(pk1)
|
||||
vb2 = route.NewVertex(pkb2)
|
||||
vb3 = route.NewVertex(pkb3)
|
||||
|
||||
baseFee uint32 = 1000
|
||||
ppmFee uint32 = 500
|
||||
cltvDelta uint16 = 140
|
||||
htlcMin uint64 = 100
|
||||
htlcMax uint64 = 100_000_000
|
||||
|
||||
rawFeatures = lnwire.NewRawFeatureVector(
|
||||
lnwire.AMPOptional,
|
||||
)
|
||||
|
||||
features = lnwire.NewFeatureVector(
|
||||
rawFeatures, lnwire.Features,
|
||||
)
|
||||
)
|
||||
|
||||
// Create a blinded payment that's just to the introduction node and
|
||||
// assert that we get nil hints.
|
||||
blindedPayment := &BlindedPayment{
|
||||
BlindedPath: &sphinx.BlindedPath{
|
||||
IntroductionPoint: pk1,
|
||||
BlindedHops: []*sphinx.BlindedHopInfo{
|
||||
{},
|
||||
},
|
||||
},
|
||||
BaseFee: baseFee,
|
||||
ProportionalFee: ppmFee,
|
||||
CltvExpiryDelta: cltvDelta,
|
||||
HtlcMinimum: htlcMin,
|
||||
HtlcMaximum: htlcMax,
|
||||
Features: features,
|
||||
}
|
||||
require.Nil(t, blindedPayment.toRouteHints())
|
||||
|
||||
// Populate the blinded payment with hops.
|
||||
blindedPayment.BlindedPath.BlindedHops = []*sphinx.BlindedHopInfo{
|
||||
{
|
||||
BlindedNodePub: pkb1,
|
||||
},
|
||||
{
|
||||
BlindedNodePub: pkb2,
|
||||
},
|
||||
{
|
||||
BlindedNodePub: pkb3,
|
||||
},
|
||||
}
|
||||
|
||||
expected := RouteHints{
|
||||
v1: {
|
||||
{
|
||||
TimeLockDelta: cltvDelta,
|
||||
MinHTLC: lnwire.MilliSatoshi(htlcMin),
|
||||
MaxHTLC: lnwire.MilliSatoshi(htlcMax),
|
||||
FeeBaseMSat: lnwire.MilliSatoshi(baseFee),
|
||||
FeeProportionalMillionths: lnwire.MilliSatoshi(
|
||||
ppmFee,
|
||||
),
|
||||
ToNodePubKey: func() route.Vertex {
|
||||
return vb2
|
||||
},
|
||||
ToNodeFeatures: features,
|
||||
},
|
||||
},
|
||||
vb2: {
|
||||
{
|
||||
ToNodePubKey: func() route.Vertex {
|
||||
return vb3
|
||||
},
|
||||
ToNodeFeatures: features,
|
||||
},
|
||||
},
|
||||
}
|
||||
actual := blindedPayment.toRouteHints()
|
||||
|
||||
require.Equal(t, len(expected), len(actual))
|
||||
for vertex, expectedHint := range expected {
|
||||
actualHint, ok := actual[vertex]
|
||||
require.True(t, ok, "node not found: %v", vertex)
|
||||
|
||||
require.Len(t, expectedHint, 1)
|
||||
require.Len(t, actualHint, 1)
|
||||
|
||||
// We can't assert that our functions are equal, so we check
|
||||
// their output and then mark as nil so that we can use
|
||||
// require.Equal for all our other fields.
|
||||
require.Equal(t, expectedHint[0].ToNodePubKey(),
|
||||
actualHint[0].ToNodePubKey())
|
||||
|
||||
actualHint[0].ToNodePubKey = nil
|
||||
expectedHint[0].ToNodePubKey = nil
|
||||
|
||||
require.Equal(t, expectedHint[0], actualHint[0])
|
||||
}
|
||||
}
|
||||
|
@ -2293,7 +2293,7 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
const amt lnwire.MilliSatoshi = 4999999
|
||||
req, err := NewRouteRequest(
|
||||
bobNode.PubKeyBytes, &carol, amt, 0, noRestrictions, nil, nil,
|
||||
MinCLTVDelta,
|
||||
nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
|
||||
@ -2346,7 +2346,7 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
// We'll now request a route from A -> B -> C.
|
||||
req, err = NewRouteRequest(
|
||||
source.PubKeyBytes, &carol, amt, 0, noRestrictions, nil, nil,
|
||||
MinCLTVDelta,
|
||||
nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
|
||||
|
@ -91,6 +91,34 @@ var (
|
||||
// ErrRouterShuttingDown is returned if the router is in the process of
|
||||
// shutting down.
|
||||
ErrRouterShuttingDown = fmt.Errorf("router shutting down")
|
||||
|
||||
// ErrSelfIntro is a failure returned when the source node of a
|
||||
// route request is also the introduction node. This is not yet
|
||||
// supported because LND does not support blinded forwardingg.
|
||||
ErrSelfIntro = errors.New("introduction point as own node not " +
|
||||
"supported")
|
||||
|
||||
// ErrHintsAndBlinded is returned if a route request has both
|
||||
// bolt 11 route hints and a blinded path set.
|
||||
ErrHintsAndBlinded = errors.New("bolt 11 route hints and blinded " +
|
||||
"paths are mutually exclusive")
|
||||
|
||||
// ErrExpiryAndBlinded is returned if a final cltv and a blinded path
|
||||
// are provided, as the cltv should be provided within the blinded
|
||||
// path.
|
||||
ErrExpiryAndBlinded = errors.New("final cltv delta and blinded " +
|
||||
"paths are mutually exclusive")
|
||||
|
||||
// ErrTargetAndBlinded is returned is a target destination and a
|
||||
// blinded path are both set (as the target is inferred from the
|
||||
// blinded path).
|
||||
ErrTargetAndBlinded = errors.New("target node and blinded paths " +
|
||||
"are mutually exclusive")
|
||||
|
||||
// ErrNoTarget is returned when the target node for a route is not
|
||||
// provided by either a blinded route or a cleartext pubkey.
|
||||
ErrNoTarget = errors.New("destination not set in target or blinded " +
|
||||
"path")
|
||||
)
|
||||
|
||||
// ChannelGraphSource represents the source of information about the topology
|
||||
@ -1809,12 +1837,16 @@ type routingMsg struct {
|
||||
err chan error
|
||||
}
|
||||
|
||||
// RouteRequest contains the parameters for a pathfinding request.
|
||||
// RouteRequest contains the parameters for a pathfinding request. It may
|
||||
// describe a request to make a regular payment or one to a blinded path
|
||||
// (incdicated by a non-nil BlindedPayment field).
|
||||
type RouteRequest struct {
|
||||
// Source is the node that the path originates from.
|
||||
Source route.Vertex
|
||||
|
||||
// Target is the node that the path terminates at.
|
||||
// Target is the node that the path terminates at. If the route
|
||||
// includes a blinded path, target will be the blinded node id of the
|
||||
// final hop in the blinded route.
|
||||
Target route.Vertex
|
||||
|
||||
// Amount is the Amount in millisatoshis to be delivered to the target
|
||||
@ -1834,39 +1866,131 @@ type RouteRequest struct {
|
||||
CustomRecords record.CustomSet
|
||||
|
||||
// RouteHints contains an additional set of edges to include in our
|
||||
// view of the graph.
|
||||
// view of the graph. This may either be a set of hints for private
|
||||
// channels or a "virtual" hop hint that represents a blinded route.
|
||||
RouteHints RouteHints
|
||||
|
||||
// FinalExpiry is the cltv delta for the final hop.
|
||||
// FinalExpiry is the cltv delta for the final hop. If paying to a
|
||||
// blinded path, this value is a duplicate of the delta provided
|
||||
// in blinded payment.
|
||||
FinalExpiry uint16
|
||||
|
||||
// BlindedPayment contains an optional blinded path and parameters
|
||||
// used to reach a target node via a blinded path. This field is
|
||||
// mutually exclusive with the Target field.
|
||||
BlindedPayment *BlindedPayment
|
||||
}
|
||||
|
||||
// RouteHints is an alias type for a set of route hints, with the source node
|
||||
// as the map's key and the details of the hint(s) in the edge policy.
|
||||
type RouteHints map[route.Vertex][]*channeldb.CachedEdgePolicy
|
||||
|
||||
// NewRouteRequest produces a new route request.
|
||||
// NewRouteRequest produces a new route request for a regular payment or one
|
||||
// to a blinded route, validating that the target, routeHints and finalExpiry
|
||||
// parameters are mutually exclusive with the blindedPayment parameter (which
|
||||
// contains these values for blinded payments).
|
||||
func NewRouteRequest(source route.Vertex, target *route.Vertex,
|
||||
amount lnwire.MilliSatoshi, timePref float64,
|
||||
restrictions *RestrictParams, customRecords record.CustomSet,
|
||||
routeHints RouteHints, finalExpiry uint16) (*RouteRequest, error) {
|
||||
routeHints RouteHints, blindedPayment *BlindedPayment,
|
||||
finalExpiry uint16) (*RouteRequest, error) {
|
||||
|
||||
if target == nil {
|
||||
return nil, errors.New("target node required")
|
||||
var (
|
||||
// Assume that we're starting off with a regular payment.
|
||||
requestHints = routeHints
|
||||
requestExpiry = finalExpiry
|
||||
)
|
||||
|
||||
if blindedPayment != nil {
|
||||
if err := blindedPayment.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid blinded payment: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
introVertex := route.NewVertex(
|
||||
blindedPayment.BlindedPath.IntroductionPoint,
|
||||
)
|
||||
if source == introVertex {
|
||||
return nil, ErrSelfIntro
|
||||
}
|
||||
|
||||
// Check that the values for a clear path have not been set,
|
||||
// as this is an ambiguous signal from the caller.
|
||||
if routeHints != nil {
|
||||
return nil, ErrHintsAndBlinded
|
||||
}
|
||||
|
||||
if finalExpiry != 0 {
|
||||
return nil, ErrExpiryAndBlinded
|
||||
}
|
||||
|
||||
// If we have a blinded path with 1 hop, the cltv expiry
|
||||
// will not be included in any hop hints (since we're just
|
||||
// sending to the introduction node and need no blinded hints).
|
||||
// In this case, we include it to make sure that the final
|
||||
// cltv delta is accounted for (since it's part of the blinded
|
||||
// delta). In the case of a multi-hop route, we set our final
|
||||
// cltv to zero, since it's going to be accounted for in the
|
||||
// delta for our hints.
|
||||
if len(blindedPayment.BlindedPath.BlindedHops) == 1 {
|
||||
requestExpiry = blindedPayment.CltvExpiryDelta
|
||||
}
|
||||
|
||||
requestHints = blindedPayment.toRouteHints()
|
||||
}
|
||||
|
||||
requestTarget, err := getTargetNode(target, blindedPayment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RouteRequest{
|
||||
Source: source,
|
||||
Target: *target,
|
||||
Target: requestTarget,
|
||||
Amount: amount,
|
||||
TimePreference: timePref,
|
||||
Restrictions: restrictions,
|
||||
CustomRecords: customRecords,
|
||||
RouteHints: routeHints,
|
||||
FinalExpiry: finalExpiry,
|
||||
RouteHints: requestHints,
|
||||
FinalExpiry: requestExpiry,
|
||||
BlindedPayment: blindedPayment,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTargetNode(target *route.Vertex, blindedPayment *BlindedPayment) (
|
||||
route.Vertex, error) {
|
||||
|
||||
var (
|
||||
blinded = blindedPayment != nil
|
||||
targetSet = target != nil
|
||||
)
|
||||
|
||||
switch {
|
||||
case blinded && targetSet:
|
||||
return route.Vertex{}, ErrTargetAndBlinded
|
||||
|
||||
case blinded:
|
||||
// If we're dealing with an edge-case blinded path that just
|
||||
// has an introduction node (first hop expected to be the intro
|
||||
// hop), then we return the unblinded introduction node as our
|
||||
// target.
|
||||
hops := blindedPayment.BlindedPath.BlindedHops
|
||||
if len(hops) == 1 {
|
||||
return route.NewVertex(
|
||||
blindedPayment.BlindedPath.IntroductionPoint,
|
||||
), nil
|
||||
}
|
||||
|
||||
return route.NewVertex(hops[len(hops)-1].BlindedNodePub), nil
|
||||
|
||||
case targetSet:
|
||||
return *target, nil
|
||||
|
||||
default:
|
||||
return route.Vertex{}, ErrNoTarget
|
||||
}
|
||||
}
|
||||
|
||||
// FindRoute attempts to query the ChannelRouter for the optimum path to a
|
||||
// particular target destination to which it is able to send `amt` after
|
||||
// factoring in channel capacities and cumulative fees along the route.
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
@ -273,7 +274,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
||||
|
||||
req, err := NewRouteRequest(
|
||||
ctx.router.selfNode.PubKeyBytes, &target, paymentAmt, 0,
|
||||
restrictions, nil, nil, MinCLTVDelta,
|
||||
restrictions, nil, nil, nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
|
||||
@ -1563,7 +1564,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
||||
|
||||
req, err := NewRouteRequest(
|
||||
ctx.router.selfNode.PubKeyBytes, &targetPubKeyBytes,
|
||||
paymentAmt, 0, noRestrictions, nil, nil, MinCLTVDelta,
|
||||
paymentAmt, 0, noRestrictions, nil, nil, nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
_, _, err = ctx.router.FindRoute(req)
|
||||
@ -1605,7 +1606,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
|
||||
// updated.
|
||||
req, err = NewRouteRequest(
|
||||
ctx.router.selfNode.PubKeyBytes, &targetPubKeyBytes,
|
||||
paymentAmt, 0, noRestrictions, nil, nil, MinCLTVDelta,
|
||||
paymentAmt, 0, noRestrictions, nil, nil, nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
|
||||
@ -4612,3 +4613,155 @@ func TestSendToRouteTempFailure(t *testing.T) {
|
||||
payer.AssertExpectations(t)
|
||||
missionControl.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// TestNewRouteRequest tests creation of route requests for blinded and
|
||||
// unblinded routes.
|
||||
func TestNewRouteRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//nolint:lll
|
||||
source, err := route.NewVertexFromStr("0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6")
|
||||
require.NoError(t, err)
|
||||
sourcePubkey, err := btcec.ParsePubKey(source[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
//nolint:lll
|
||||
v1, err := route.NewVertexFromStr("026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318")
|
||||
require.NoError(t, err)
|
||||
pubkey1, err := btcec.ParsePubKey(v1[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
//nolint:lll
|
||||
v2, err := route.NewVertexFromStr("03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99")
|
||||
require.NoError(t, err)
|
||||
pubkey2, err := btcec.ParsePubKey(v2[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
unblindedCltv uint16 = 500
|
||||
blindedCltv uint16 = 1000
|
||||
)
|
||||
|
||||
blindedSelfIntro := &BlindedPayment{
|
||||
CltvExpiryDelta: blindedCltv,
|
||||
BlindedPath: &sphinx.BlindedPath{
|
||||
IntroductionPoint: sourcePubkey,
|
||||
BlindedHops: []*sphinx.BlindedHopInfo{{}},
|
||||
},
|
||||
}
|
||||
|
||||
blindedOtherIntro := &BlindedPayment{
|
||||
CltvExpiryDelta: blindedCltv,
|
||||
BlindedPath: &sphinx.BlindedPath{
|
||||
IntroductionPoint: pubkey1,
|
||||
BlindedHops: []*sphinx.BlindedHopInfo{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
blindedMultiHop := &BlindedPayment{
|
||||
CltvExpiryDelta: blindedCltv,
|
||||
BlindedPath: &sphinx.BlindedPath{
|
||||
IntroductionPoint: pubkey1,
|
||||
BlindedHops: []*sphinx.BlindedHopInfo{
|
||||
{},
|
||||
{
|
||||
BlindedNodePub: pubkey2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
target *route.Vertex
|
||||
routeHints RouteHints
|
||||
blindedPayment *BlindedPayment
|
||||
finalExpiry uint16
|
||||
|
||||
expectedTarget route.Vertex
|
||||
expectedCltv uint16
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "blinded and target",
|
||||
target: &v1,
|
||||
blindedPayment: blindedOtherIntro,
|
||||
err: ErrTargetAndBlinded,
|
||||
},
|
||||
{
|
||||
// For single-hop blinded we have a final cltv.
|
||||
name: "blinded intro node only",
|
||||
blindedPayment: blindedOtherIntro,
|
||||
expectedTarget: v1,
|
||||
expectedCltv: blindedCltv,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
// For multi-hop blinded, we have no final cltv.
|
||||
name: "blinded multi-hop",
|
||||
blindedPayment: blindedMultiHop,
|
||||
expectedTarget: v2,
|
||||
expectedCltv: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "unblinded",
|
||||
target: &v2,
|
||||
finalExpiry: unblindedCltv,
|
||||
expectedTarget: v2,
|
||||
expectedCltv: unblindedCltv,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "source node intro",
|
||||
blindedPayment: blindedSelfIntro,
|
||||
err: ErrSelfIntro,
|
||||
},
|
||||
{
|
||||
name: "hints and blinded",
|
||||
blindedPayment: blindedMultiHop,
|
||||
routeHints: make(
|
||||
map[route.Vertex][]*channeldb.CachedEdgePolicy,
|
||||
),
|
||||
err: ErrHintsAndBlinded,
|
||||
},
|
||||
{
|
||||
name: "expiry and blinded",
|
||||
blindedPayment: blindedMultiHop,
|
||||
finalExpiry: unblindedCltv,
|
||||
err: ErrExpiryAndBlinded,
|
||||
},
|
||||
{
|
||||
name: "invalid blinded payment",
|
||||
blindedPayment: &BlindedPayment{},
|
||||
err: ErrNoBlindedPath,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := NewRouteRequest(
|
||||
source, testCase.target, 1000, 0, nil, nil,
|
||||
testCase.routeHints, testCase.blindedPayment,
|
||||
testCase.finalExpiry,
|
||||
)
|
||||
require.ErrorIs(t, err, testCase.err)
|
||||
|
||||
// Skip request validation if we got a non-nil error.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, req.Target, testCase.expectedTarget)
|
||||
require.Equal(
|
||||
t, req.FinalExpiry, testCase.expectedCltv,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user