Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions macaroon_recipes.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,25 @@ var (
// implemented in lndclient and the value is the original name of the
// RPC method defined in the proto.
renames = map[string]string{
"ChannelBackup": "ExportChannelBackup",
"ChannelBackups": "ExportAllChannelBackups",
"ConfirmedWalletBalance": "WalletBalance",
"Connect": "ConnectPeer",
"DecodePaymentRequest": "DecodePayReq",
"ListTransactions": "GetTransactions",
"PayInvoice": "SendPaymentSync",
"UpdateChanPolicy": "UpdateChannelPolicy",
"NetworkInfo": "GetNetworkInfo",
"SubscribeGraph": "SubscribeChannelGraph",
"InterceptHtlcs": "HtlcInterceptor",
"ImportMissionControl": "XImportMissionControl",
"EstimateFeeRate": "EstimateFee",
"EstimateFeeToP2WSH": "EstimateFee",
"OpenChannelStream": "OpenChannel",
"ListSweepsVerbose": "ListSweeps",
"MinRelayFee": "EstimateFee",
"SignOutputRawKeyLocator": "SignOutputRaw",
"ChannelBackup": "ExportChannelBackup",
"ChannelBackups": "ExportAllChannelBackups",
"ConfirmedWalletBalance": "WalletBalance",
"Connect": "ConnectPeer",
"DecodePaymentRequest": "DecodePayReq",
"ListTransactions": "GetTransactions",
"PayInvoice": "SendPaymentSync",
"UpdateChanPolicy": "UpdateChannelPolicy",
"NetworkInfo": "GetNetworkInfo",
"SubscribeGraph": "SubscribeChannelGraph",
"InterceptHtlcs": "HtlcInterceptor",
"ImportMissionControl": "XImportMissionControl",
"EstimateFeeRate": "EstimateFee",
"EstimateFeeToP2WSH": "EstimateFee",
"EstimateRouteFeeWithRequest": "EstimateRouteFee",
"OpenChannelStream": "OpenChannel",
"ListSweepsVerbose": "ListSweeps",
"MinRelayFee": "EstimateFee",
"SignOutputRawKeyLocator": "SignOutputRaw",
}

// ignores is a list of method names on the client implementations that
Expand Down
98 changes: 94 additions & 4 deletions router_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,19 @@ type RouterClient interface {

// EstimateRouteFee uses the channel router's internal state to estimate
// the routing cost of the given amount to the destination node.
//
// Deprecated: use EstimateRouteFeeWithRequest for full request and
// response support.
EstimateRouteFee(ctx context.Context, dest route.Vertex,
amt btcutil.Amount) (lnwire.MilliSatoshi, error)

// EstimateRouteFeeWithRequest estimates routing costs either by using
// the channel router's internal graph, or by probing with a payment
// request.
EstimateRouteFeeWithRequest(ctx context.Context,
request EstimateRouteFeeRequest) (*EstimateRouteFeeResponse,
error)

// SubscribeHtlcEvents subscribes to a stream of htlc events from the
// router.
SubscribeHtlcEvents(ctx context.Context) (<-chan *routerrpc.HtlcEvent,
Expand Down Expand Up @@ -318,6 +328,43 @@ type SendPaymentRequest struct {
FirstHopCustomRecords map[uint64][]byte
}

// EstimateRouteFeeRequest defines the parameters for a route fee estimate.
type EstimateRouteFeeRequest struct {
// Dest is the destination one wishes to obtain a routing fee quote to.
// If set, Amount must also be set. This triggers a graph-based fee
// estimation and cannot be used with PaymentRequest.
Dest *route.Vertex

// Amount is the amount one wishes to send to the target destination in
// satoshis. It is only used in combination with Dest.
Amount btcutil.Amount

// PaymentRequest is an encoded payment request of the target node. If
// set, lnd sends probe payments to estimate routing fees. It cannot be
// used with Dest and Amount.
PaymentRequest string

// Timeout is the maximum time that a probe payment should be allowed to
// take. It is only used in combination with PaymentRequest.
Timeout time.Duration
}

// EstimateRouteFeeResponse is the response of a route fee estimate.
type EstimateRouteFeeResponse struct {
// RoutingFee is a lower bound of the estimated fee to the target
// destination within the network.
RoutingFee lnwire.MilliSatoshi

// TimeLockDelay is an estimate of the worst-case time delay that can
// occur. Callers still need to factor in the final CLTV delta of the
// last hop into this value.
TimeLockDelay int64

// FailureReason indicates whether a probing payment succeeded or
// whether and why it failed. FAILURE_REASON_NONE indicates success.
FailureReason lnrpc.PaymentFailureReason
}

// InterceptedHtlc contains information about a htlc that was intercepted in
// lnd's switch.
type InterceptedHtlc struct {
Expand Down Expand Up @@ -604,21 +651,64 @@ func (r *routerClient) trackPayment(ctx context.Context,

// EstimateRouteFee uses the channel router's internal state to estimate the
// routing cost of the given amount to the destination node.
//
// Deprecated: use EstimateRouteFeeWithRequest for full request and response
// support.
func (r *routerClient) EstimateRouteFee(ctx context.Context, dest route.Vertex,
amt btcutil.Amount) (lnwire.MilliSatoshi, error) {

res, err := r.EstimateRouteFeeWithRequest(ctx, EstimateRouteFeeRequest{
Dest: &dest,
Amount: amt,
})
if err != nil {
return 0, err
}

return res.RoutingFee, nil
}

// EstimateRouteFeeWithRequest estimates routing costs either by using the
// channel router's internal graph, or by probing with a payment request.
func (r *routerClient) EstimateRouteFeeWithRequest(ctx context.Context,
request EstimateRouteFeeRequest) (*EstimateRouteFeeResponse, error) {

rpcCtx := r.routerKitMac.WithMacaroonAuth(ctx)
rpcReq := &routerrpc.RouteFeeRequest{
Dest: dest[:],
AmtSat: int64(amt),
AmtSat: int64(request.Amount),
PaymentRequest: request.PaymentRequest,
}

if request.Dest != nil {
rpcReq.Dest = request.Dest[:]
}

if request.Timeout < 0 {
return nil, fmt.Errorf("timeout must not be negative")
}

if request.Timeout > 0 {
const maxTimeout = time.Duration(^uint32(0)) * time.Second
if request.Timeout > maxTimeout {
return nil, fmt.Errorf("timeout exceeds maximum of %v",
maxTimeout)
}

rpcReq.Timeout = uint32(
(request.Timeout + time.Second - 1) / time.Second,
)
}

rpcRes, err := r.client.EstimateRouteFee(rpcCtx, rpcReq)
if err != nil {
return 0, err
return nil, err
}

return lnwire.MilliSatoshi(rpcRes.RoutingFeeMsat), nil
return &EstimateRouteFeeResponse{
RoutingFee: lnwire.MilliSatoshi(rpcRes.RoutingFeeMsat),
TimeLockDelay: rpcRes.TimeLockDelay,
FailureReason: rpcRes.FailureReason,
}, nil
}

// unmarshallPaymentStatus converts an rpc status update to the PaymentStatus
Expand Down
151 changes: 151 additions & 0 deletions router_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package lndclient

import (
"context"
"testing"
"time"

"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)

type mockRouterRPCClient struct {
routerrpc.RouterClient

request *routerrpc.RouteFeeRequest
response *routerrpc.RouteFeeResponse
err error
}

func (m *mockRouterRPCClient) EstimateRouteFee(_ context.Context,
request *routerrpc.RouteFeeRequest, _ ...grpc.CallOption) (
*routerrpc.RouteFeeResponse, error) {

m.request = request
return m.response, m.err
}

func TestEstimateRouteFeeWithRequestGraph(t *testing.T) {
t.Parallel()

dest := testVertex()
mock := &mockRouterRPCClient{
response: &routerrpc.RouteFeeResponse{
RoutingFeeMsat: 123,
TimeLockDelay: 456,
FailureReason: lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
},
}
client := &routerClient{
client: mock,
}

resp, err := client.EstimateRouteFeeWithRequest(
context.Background(),
EstimateRouteFeeRequest{
Dest: &dest,
Amount: 1000,
},
)
require.NoError(t, err)

require.Equal(t, dest[:], mock.request.Dest)
require.Equal(t, int64(1000), mock.request.AmtSat)
require.Empty(t, mock.request.PaymentRequest)
require.Zero(t, mock.request.Timeout)
require.Equal(t, lnwire.MilliSatoshi(123), resp.RoutingFee)
require.Equal(t, int64(456), resp.TimeLockDelay)
require.Equal(
t, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
resp.FailureReason,
)
}

func TestEstimateRouteFeeWithRequestPaymentRequest(t *testing.T) {
t.Parallel()

mock := &mockRouterRPCClient{
response: &routerrpc.RouteFeeResponse{
RoutingFeeMsat: 987,
TimeLockDelay: 654,
FailureReason: lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
},
}
client := &routerClient{
client: mock,
}

resp, err := client.EstimateRouteFeeWithRequest(
context.Background(),
EstimateRouteFeeRequest{
PaymentRequest: "lnbc1...",
Timeout: 1500 * time.Millisecond,
},
)
require.NoError(t, err)

require.Empty(t, mock.request.Dest)
require.Zero(t, mock.request.AmtSat)
require.Equal(t, "lnbc1...", mock.request.PaymentRequest)
require.Equal(t, uint32(2), mock.request.Timeout)
require.Equal(t, lnwire.MilliSatoshi(987), resp.RoutingFee)
require.Equal(t, int64(654), resp.TimeLockDelay)
require.Equal(
t, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
resp.FailureReason,
)
}

func TestEstimateRouteFee(t *testing.T) {
t.Parallel()

dest := testVertex()
mock := &mockRouterRPCClient{
response: &routerrpc.RouteFeeResponse{
RoutingFeeMsat: 4321,
},
}
client := &routerClient{
client: mock,
}

fee, err := client.EstimateRouteFee(
context.Background(), dest, btcutil.Amount(1000),
)
require.NoError(t, err)

require.Equal(t, dest[:], mock.request.Dest)
require.Equal(t, int64(1000), mock.request.AmtSat)
require.Equal(t, lnwire.MilliSatoshi(4321), fee)
}

func TestEstimateRouteFeeWithRequestRejectsInvalidTimeout(t *testing.T) {
t.Parallel()

client := &routerClient{
client: &mockRouterRPCClient{},
}

_, err := client.EstimateRouteFeeWithRequest(
context.Background(),
EstimateRouteFeeRequest{
PaymentRequest: "lnbc1...",
Timeout: -time.Second,
},
)
require.ErrorContains(t, err, "timeout must not be negative")
}

func testVertex() route.Vertex {
var vertex route.Vertex
for i := range vertex {
vertex[i] = byte(i + 1)
}

return vertex
}
Loading