mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-28 10:41:57 +01:00
Merge pull request #2640 from joostjager/cltv-limit
routing: add cltv limit
This commit is contained in:
commit
4d8100cc9a
@ -1954,6 +1954,12 @@ func closedChannels(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cltvLimitFlag = cli.UintFlag{
|
||||||
|
Name: "cltv_limit",
|
||||||
|
Usage: "the maximum time lock that may be used for " +
|
||||||
|
"this payment",
|
||||||
|
}
|
||||||
|
|
||||||
var sendPaymentCommand = cli.Command{
|
var sendPaymentCommand = cli.Command{
|
||||||
Name: "sendpayment",
|
Name: "sendpayment",
|
||||||
Category: "Payments",
|
Category: "Payments",
|
||||||
@ -2000,6 +2006,7 @@ var sendPaymentCommand = cli.Command{
|
|||||||
Usage: "percentage of the payment's amount used as the" +
|
Usage: "percentage of the payment's amount used as the" +
|
||||||
"maximum fee allowed when sending the payment",
|
"maximum fee allowed when sending the payment",
|
||||||
},
|
},
|
||||||
|
cltvLimitFlag,
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "payment_hash, r",
|
Name: "payment_hash, r",
|
||||||
Usage: "the hash to use within the payment's HTLC",
|
Usage: "the hash to use within the payment's HTLC",
|
||||||
@ -2119,6 +2126,7 @@ func sendPayment(ctx *cli.Context) error {
|
|||||||
Amt: ctx.Int64("amt"),
|
Amt: ctx.Int64("amt"),
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
||||||
|
CltvLimit: uint32(ctx.Int(cltvLimitFlag.Name)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendPaymentRequest(client, req)
|
return sendPaymentRequest(client, req)
|
||||||
@ -2266,6 +2274,7 @@ var payInvoiceCommand = cli.Command{
|
|||||||
Usage: "percentage of the payment's amount used as the" +
|
Usage: "percentage of the payment's amount used as the" +
|
||||||
"maximum fee allowed when sending the payment",
|
"maximum fee allowed when sending the payment",
|
||||||
},
|
},
|
||||||
|
cltvLimitFlag,
|
||||||
cli.Uint64Flag{
|
cli.Uint64Flag{
|
||||||
Name: "outgoing_chan_id",
|
Name: "outgoing_chan_id",
|
||||||
Usage: "short channel id of the outgoing channel to " +
|
Usage: "short channel id of the outgoing channel to " +
|
||||||
@ -2312,7 +2321,9 @@ func payInvoice(ctx *cli.Context) error {
|
|||||||
Amt: ctx.Int64("amt"),
|
Amt: ctx.Int64("amt"),
|
||||||
FeeLimit: feeLimit,
|
FeeLimit: feeLimit,
|
||||||
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
||||||
|
CltvLimit: uint32(ctx.Int(cltvLimitFlag.Name)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendPaymentRequest(client, req)
|
return sendPaymentRequest(client, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1178
lnrpc/rpc.pb.go
1178
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -790,7 +790,14 @@ message SendRequest {
|
|||||||
any channel may be used.
|
any channel may be used.
|
||||||
*/
|
*/
|
||||||
uint64 outgoing_chan_id = 9;
|
uint64 outgoing_chan_id = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An optional maximum total time lock for the route. If zero, there is no
|
||||||
|
maximum enforced.
|
||||||
|
*/
|
||||||
|
uint32 cltv_limit = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SendResponse {
|
message SendResponse {
|
||||||
string payment_error = 1 [json_name = "payment_error"];
|
string payment_error = 1 [json_name = "payment_error"];
|
||||||
bytes payment_preimage = 2 [json_name = "payment_preimage"];
|
bytes payment_preimage = 2 [json_name = "payment_preimage"];
|
||||||
|
@ -2977,6 +2977,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64",
|
"format": "uint64",
|
||||||
"description": "*\nThe channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used."
|
"description": "*\nThe channel id of the channel that must be taken to the first hop. If zero,\nany channel may be used."
|
||||||
|
},
|
||||||
|
"cltv_limit": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "* \nAn optional maximum total time lock for the route. If zero, there is no\nmaximum enforced."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,10 @@ type nodeWithDist struct {
|
|||||||
// amount that includes also the fees for subsequent hops.
|
// amount that includes also the fees for subsequent hops.
|
||||||
amountToReceive lnwire.MilliSatoshi
|
amountToReceive lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// incomingCltv is the expected cltv value for the incoming htlc of this
|
||||||
|
// node. This value does not include the final cltv.
|
||||||
|
incomingCltv uint32
|
||||||
|
|
||||||
// fee is the fee that this node is charging for forwarding.
|
// fee is the fee that this node is charging for forwarding.
|
||||||
fee lnwire.MilliSatoshi
|
fee lnwire.MilliSatoshi
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,7 @@ func (m *missionControl) NewPaymentSession(routeHints [][]zpay32.HopHint,
|
|||||||
bandwidthHints: bandwidthHints,
|
bandwidthHints: bandwidthHints,
|
||||||
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
||||||
mc: m,
|
mc: m,
|
||||||
|
pathFinder: findPath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +241,7 @@ func (m *missionControl) NewPaymentSessionFromRoutes(routes []*Route) *paymentSe
|
|||||||
preBuiltRoutes: routes,
|
preBuiltRoutes: routes,
|
||||||
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
|
||||||
mc: m,
|
mc: m,
|
||||||
|
pathFinder: findPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,11 @@ const (
|
|||||||
RiskFactorBillionths = 15
|
RiskFactorBillionths = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// pathFinder defines the interface of a path finding algorithm.
|
||||||
|
type pathFinder = func(g *graphParams, r *RestrictParams,
|
||||||
|
source, target Vertex, amt lnwire.MilliSatoshi) (
|
||||||
|
[]*channeldb.ChannelEdgePolicy, error)
|
||||||
|
|
||||||
// Hop represents an intermediate or final node of the route. This naming
|
// Hop represents an intermediate or final node of the route. This naming
|
||||||
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
||||||
// The struct houses the channel along which this hop can be reached and
|
// The struct houses the channel along which this hop can be reached and
|
||||||
@ -393,6 +398,11 @@ type RestrictParams struct {
|
|||||||
// OutgoingChannelID is the channel that needs to be taken to the first
|
// OutgoingChannelID is the channel that needs to be taken to the first
|
||||||
// hop. If nil, any channel may be used.
|
// hop. If nil, any channel may be used.
|
||||||
OutgoingChannelID *uint64
|
OutgoingChannelID *uint64
|
||||||
|
|
||||||
|
// CltvLimit is the maximum time lock of the route excluding the final
|
||||||
|
// ctlv. After path finding is complete, the caller needs to increase
|
||||||
|
// all cltv expiry heights with the required final cltv delta.
|
||||||
|
CltvLimit *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// findPath attempts to find a path from the source node within the
|
// findPath attempts to find a path from the source node within the
|
||||||
@ -479,6 +489,7 @@ func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
|
|||||||
node: targetNode,
|
node: targetNode,
|
||||||
amountToReceive: amt,
|
amountToReceive: amt,
|
||||||
fee: 0,
|
fee: 0,
|
||||||
|
incomingCltv: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll use this map as a series of "next" hop pointers. So to get
|
// We'll use this map as a series of "next" hop pointers. So to get
|
||||||
@ -575,6 +586,14 @@ func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
|
|||||||
timeLockDelta = edge.TimeLockDelta
|
timeLockDelta = edge.TimeLockDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
incomingCltv := toNodeDist.incomingCltv +
|
||||||
|
uint32(timeLockDelta)
|
||||||
|
|
||||||
|
// Check that we have cltv limit and that we are within it.
|
||||||
|
if r.CltvLimit != nil && incomingCltv > *r.CltvLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// amountToReceive is the amount that the node that is added to
|
// amountToReceive is the amount that the node that is added to
|
||||||
// the distance map needs to receive from a (to be found)
|
// the distance map needs to receive from a (to be found)
|
||||||
// previous node in the route. That previous node will need to
|
// previous node in the route. That previous node will need to
|
||||||
@ -606,14 +625,11 @@ func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the edge has no time lock delta, the payment will always
|
// Every edge should have a positive time lock delta. If we
|
||||||
// fail, so return.
|
// encounter a zero delta, log a warning line.
|
||||||
//
|
|
||||||
// TODO(joostjager): Is this really true? Can't it be that
|
|
||||||
// nodes take this risk in exchange for a extraordinary high
|
|
||||||
// fee?
|
|
||||||
if edge.TimeLockDelta == 0 {
|
if edge.TimeLockDelta == 0 {
|
||||||
return
|
log.Warnf("Channel %v has zero cltv delta",
|
||||||
|
edge.ChannelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All conditions are met and this new tentative distance is
|
// All conditions are met and this new tentative distance is
|
||||||
@ -625,6 +641,7 @@ func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
|
|||||||
node: fromNode,
|
node: fromNode,
|
||||||
amountToReceive: amountToReceive,
|
amountToReceive: amountToReceive,
|
||||||
fee: fee,
|
fee: fee,
|
||||||
|
incomingCltv: incomingCltv,
|
||||||
}
|
}
|
||||||
|
|
||||||
next[fromVertex] = edge
|
next[fromVertex] = edge
|
||||||
|
@ -2021,3 +2021,111 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
|||||||
"but channel %v was selected instead", route.Hops[0].ChannelID)
|
"but channel %v was selected instead", route.Hops[0].ChannelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCltvLimit asserts that a cltv limit is obeyed by the path finding
|
||||||
|
// algorithm.
|
||||||
|
func TestCltvLimit(t *testing.T) {
|
||||||
|
t.Run("no limit", func(t *testing.T) { testCltvLimit(t, 0, 1) })
|
||||||
|
t.Run("no path", func(t *testing.T) { testCltvLimit(t, 50, 0) })
|
||||||
|
t.Run("force high cost", func(t *testing.T) { testCltvLimit(t, 80, 3) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Set up a test graph with three possible paths to the target. The path
|
||||||
|
// through a is the lowest cost with a high time lock (144). The path
|
||||||
|
// through b has a higher cost but a lower time lock (100). That path
|
||||||
|
// through c and d (two hops) has the same case as the path through b,
|
||||||
|
// but the total time lock is lower (60).
|
||||||
|
testChannels := []*testChannel{
|
||||||
|
symmetricTestChannel("roasbeef", "a", 100000, &testChannelPolicy{}, 1),
|
||||||
|
symmetricTestChannel("a", "target", 100000, &testChannelPolicy{
|
||||||
|
Expiry: 144,
|
||||||
|
FeeBaseMsat: 10000,
|
||||||
|
MinHTLC: 1,
|
||||||
|
}),
|
||||||
|
symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}, 2),
|
||||||
|
symmetricTestChannel("b", "target", 100000, &testChannelPolicy{
|
||||||
|
Expiry: 100,
|
||||||
|
FeeBaseMsat: 20000,
|
||||||
|
MinHTLC: 1,
|
||||||
|
}),
|
||||||
|
symmetricTestChannel("roasbeef", "c", 100000, &testChannelPolicy{}, 3),
|
||||||
|
symmetricTestChannel("c", "d", 100000, &testChannelPolicy{
|
||||||
|
Expiry: 30,
|
||||||
|
FeeBaseMsat: 10000,
|
||||||
|
MinHTLC: 1,
|
||||||
|
}),
|
||||||
|
symmetricTestChannel("d", "target", 100000, &testChannelPolicy{
|
||||||
|
Expiry: 30,
|
||||||
|
FeeBaseMsat: 10000,
|
||||||
|
MinHTLC: 1,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
testGraphInstance, err := createTestGraphFromChannels(testChannels)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create graph: %v", err)
|
||||||
|
}
|
||||||
|
defer testGraphInstance.cleanUp()
|
||||||
|
|
||||||
|
sourceNode, err := testGraphInstance.graph.SourceNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to fetch source node: %v", err)
|
||||||
|
}
|
||||||
|
sourceVertex := Vertex(sourceNode.PubKeyBytes)
|
||||||
|
|
||||||
|
ignoredEdges := make(map[EdgeLocator]struct{})
|
||||||
|
ignoredVertexes := make(map[Vertex]struct{})
|
||||||
|
|
||||||
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||||
|
target := testGraphInstance.aliasMap["target"]
|
||||||
|
|
||||||
|
// Find the best path given the cltv limit.
|
||||||
|
var cltvLimit *uint32
|
||||||
|
if limit != 0 {
|
||||||
|
cltvLimit = &limit
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := findPath(
|
||||||
|
&graphParams{
|
||||||
|
graph: testGraphInstance.graph,
|
||||||
|
},
|
||||||
|
&RestrictParams{
|
||||||
|
IgnoredNodes: ignoredVertexes,
|
||||||
|
IgnoredEdges: ignoredEdges,
|
||||||
|
FeeLimit: noFeeLimit,
|
||||||
|
CltvLimit: cltvLimit,
|
||||||
|
},
|
||||||
|
sourceVertex, target, paymentAmt,
|
||||||
|
)
|
||||||
|
if expectedChannel == 0 {
|
||||||
|
// Finish test if we expect no route.
|
||||||
|
if IsError(err, ErrNoPathFound) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatal("expected no path to be found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to find path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
startingHeight = 100
|
||||||
|
finalHopCLTV = 1
|
||||||
|
)
|
||||||
|
route, err := newRoute(
|
||||||
|
paymentAmt, sourceVertex, path, startingHeight, finalHopCLTV,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the route starts with the expected channel.
|
||||||
|
if route.Hops[0].ChannelID != expectedChannel {
|
||||||
|
t.Fatalf("expected route to pass through channel %v, "+
|
||||||
|
"but channel %v was selected instead", expectedChannel,
|
||||||
|
route.Hops[0].ChannelID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,6 +33,8 @@ type paymentSession struct {
|
|||||||
|
|
||||||
haveRoutes bool
|
haveRoutes bool
|
||||||
preBuiltRoutes []*Route
|
preBuiltRoutes []*Route
|
||||||
|
|
||||||
|
pathFinder pathFinder
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportVertexFailure adds a vertex to the graph prune view after a client
|
// ReportVertexFailure adds a vertex to the graph prune view after a client
|
||||||
@ -136,12 +138,22 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
|||||||
"edges, %v vertexes", len(pruneView.edges),
|
"edges, %v vertexes", len(pruneView.edges),
|
||||||
len(pruneView.vertexes))
|
len(pruneView.vertexes))
|
||||||
|
|
||||||
|
// If a route cltv limit was specified, we need to subtract the final
|
||||||
|
// delta before passing it into path finding. The optimal path is
|
||||||
|
// independent of the final cltv delta and the path finding algorithm is
|
||||||
|
// unaware of this value.
|
||||||
|
var cltvLimit *uint32
|
||||||
|
if payment.CltvLimit != nil {
|
||||||
|
limit := *payment.CltvLimit - uint32(finalCltvDelta)
|
||||||
|
cltvLimit = &limit
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): sync logic amongst dist sys
|
// TODO(roasbeef): sync logic amongst dist sys
|
||||||
|
|
||||||
// Taking into account this prune view, we'll attempt to locate a path
|
// Taking into account this prune view, we'll attempt to locate a path
|
||||||
// to our destination, respecting the recommendations from
|
// to our destination, respecting the recommendations from
|
||||||
// missionControl.
|
// missionControl.
|
||||||
path, err := findPath(
|
path, err := p.pathFinder(
|
||||||
&graphParams{
|
&graphParams{
|
||||||
graph: p.mc.graph,
|
graph: p.mc.graph,
|
||||||
additionalEdges: p.additionalEdges,
|
additionalEdges: p.additionalEdges,
|
||||||
@ -152,6 +164,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
|
|||||||
IgnoredEdges: pruneView.edges,
|
IgnoredEdges: pruneView.edges,
|
||||||
FeeLimit: payment.FeeLimit,
|
FeeLimit: payment.FeeLimit,
|
||||||
OutgoingChannelID: payment.OutgoingChannelID,
|
OutgoingChannelID: payment.OutgoingChannelID,
|
||||||
|
CltvLimit: cltvLimit,
|
||||||
},
|
},
|
||||||
p.mc.selfNode.PubKeyBytes, payment.Target,
|
p.mc.selfNode.PubKeyBytes, payment.Target,
|
||||||
payment.Amount,
|
payment.Amount,
|
||||||
|
60
routing/payment_session_test.go
Normal file
60
routing/payment_session_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequestRoute(t *testing.T) {
|
||||||
|
const (
|
||||||
|
height = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
findPath := func(g *graphParams, r *RestrictParams,
|
||||||
|
source, target Vertex, amt lnwire.MilliSatoshi) (
|
||||||
|
[]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
|
// We expect find path to receive a cltv limit excluding the
|
||||||
|
// final cltv delta.
|
||||||
|
if *r.CltvLimit != 22 {
|
||||||
|
t.Fatal("wrong cltv limit")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := []*channeldb.ChannelEdgePolicy{
|
||||||
|
{
|
||||||
|
Node: &channeldb.LightningNode{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &paymentSession{
|
||||||
|
mc: &missionControl{
|
||||||
|
selfNode: &channeldb.LightningNode{},
|
||||||
|
},
|
||||||
|
pruneViewSnapshot: graphPruneView{},
|
||||||
|
pathFinder: findPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
cltvLimit := uint32(30)
|
||||||
|
finalCltvDelta := uint16(8)
|
||||||
|
|
||||||
|
payment := &LightningPayment{
|
||||||
|
CltvLimit: &cltvLimit,
|
||||||
|
FinalCLTVDelta: &finalCltvDelta,
|
||||||
|
}
|
||||||
|
|
||||||
|
route, err := session.RequestRoute(payment, height, finalCltvDelta)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect an absolute route lock value of height + finalCltvDelta
|
||||||
|
if route.TotalTimeLock != 18 {
|
||||||
|
t.Fatalf("unexpected total time lock of %v",
|
||||||
|
route.TotalTimeLock)
|
||||||
|
}
|
||||||
|
}
|
@ -1523,6 +1523,10 @@ type LightningPayment struct {
|
|||||||
// if there isn't a route with lower fees than this limit.
|
// if there isn't a route with lower fees than this limit.
|
||||||
FeeLimit lnwire.MilliSatoshi
|
FeeLimit lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// CltvLimit is the maximum time lock that is allowed for attempts to
|
||||||
|
// complete this payment.
|
||||||
|
CltvLimit *uint32
|
||||||
|
|
||||||
// PaymentHash is the r-hash value to use within the HTLC extended to
|
// PaymentHash is the r-hash value to use within the HTLC extended to
|
||||||
// the first hop.
|
// the first hop.
|
||||||
PaymentHash [32]byte
|
PaymentHash [32]byte
|
||||||
|
@ -2852,6 +2852,7 @@ func unmarshallSendToRouteRequest(req *lnrpc.SendToRouteRequest,
|
|||||||
type rpcPaymentIntent struct {
|
type rpcPaymentIntent struct {
|
||||||
msat lnwire.MilliSatoshi
|
msat lnwire.MilliSatoshi
|
||||||
feeLimit lnwire.MilliSatoshi
|
feeLimit lnwire.MilliSatoshi
|
||||||
|
cltvLimit *uint32
|
||||||
dest routing.Vertex
|
dest routing.Vertex
|
||||||
rHash [32]byte
|
rHash [32]byte
|
||||||
cltvDelta uint16
|
cltvDelta uint16
|
||||||
@ -2895,6 +2896,11 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
|
|||||||
payIntent.outgoingChannelID = &rpcPayReq.OutgoingChanId
|
payIntent.outgoingChannelID = &rpcPayReq.OutgoingChanId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take cltv limit from request if set.
|
||||||
|
if rpcPayReq.CltvLimit != 0 {
|
||||||
|
payIntent.cltvLimit = &rpcPayReq.CltvLimit
|
||||||
|
}
|
||||||
|
|
||||||
// If the payment request field isn't blank, then the details of the
|
// If the payment request field isn't blank, then the details of the
|
||||||
// invoice are encoded entirely within the encoded payReq. So we'll
|
// invoice are encoded entirely within the encoded payReq. So we'll
|
||||||
// attempt to decode it, populating the payment accordingly.
|
// attempt to decode it, populating the payment accordingly.
|
||||||
@ -3044,6 +3050,7 @@ func (r *rpcServer) dispatchPaymentIntent(
|
|||||||
Target: payIntent.dest,
|
Target: payIntent.dest,
|
||||||
Amount: payIntent.msat,
|
Amount: payIntent.msat,
|
||||||
FeeLimit: payIntent.feeLimit,
|
FeeLimit: payIntent.feeLimit,
|
||||||
|
CltvLimit: payIntent.cltvLimit,
|
||||||
PaymentHash: payIntent.rHash,
|
PaymentHash: payIntent.rHash,
|
||||||
RouteHints: payIntent.routeHints,
|
RouteHints: payIntent.routeHints,
|
||||||
OutgoingChannelID: payIntent.outgoingChannelID,
|
OutgoingChannelID: payIntent.outgoingChannelID,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user