Skip to content

Commit

Permalink
Merge 03d33cb into 9b1ecbd
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Sep 24, 2019
2 parents 9b1ecbd + 03d33cb commit 04dd936
Show file tree
Hide file tree
Showing 12 changed files with 1,007 additions and 145 deletions.
28 changes: 28 additions & 0 deletions channeldb/graph.go
Expand Up @@ -155,6 +155,9 @@ const (
// would be possible for a node to create a ton of updates and slowly
// fill our disk, and also waste bandwidth due to relaying.
MaxAllowedExtraOpaqueBytes = 10000

// feeRateParts is the total number of parts used to express fee rates.
feeRateParts = 1e6
)

// ChannelGraph is a persistent, on-disk graph representation of the Lightning
Expand Down Expand Up @@ -2828,6 +2831,31 @@ func (c *ChannelEdgePolicy) IsDisabled() bool {
lnwire.ChanUpdateDisabled
}

// ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
// the passed active payment channel. This value is currently computed as
// specified in BOLT07, but will likely change in the near future.
func (c *ChannelEdgePolicy) ComputeFee(
amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {

return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
}

// divideCeil divides dividend by factor and rounds the result up.
func divideCeil(dividend, factor lnwire.MilliSatoshi) lnwire.MilliSatoshi {
return (dividend + factor - 1) / factor
}

// ComputeFeeFromIncoming computes the fee to forward an HTLC given the incoming
// amount.
func (c *ChannelEdgePolicy) ComputeFeeFromIncoming(
incomingAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {

return incomingAmt - divideCeil(
feeRateParts*(incomingAmt-c.FeeBaseMSat),
feeRateParts+c.FeeProportionalMillionths,
)
}

// FetchChannelEdgesByOutpoint attempts to lookup the two directed edges for
// the channel identified by the funding outpoint. If the channel can't be
// found, then ErrEdgeNotFound is returned. A struct which houses the general
Expand Down
22 changes: 22 additions & 0 deletions channeldb/graph_test.go
Expand Up @@ -3173,3 +3173,25 @@ func TestLightningNodeSigVerification(t *testing.T) {
t.Fatalf("unable to verify sig")
}
}

// TestComputeFee tests fee calculation based on both in- and outgoing amt.
func TestComputeFee(t *testing.T) {
var (
policy = ChannelEdgePolicy{
FeeBaseMSat: 10000,
FeeProportionalMillionths: 30000,
}
outgoingAmt = lnwire.MilliSatoshi(1000000)
expectedFee = lnwire.MilliSatoshi(40000)
)

fee := policy.ComputeFee(outgoingAmt)
if fee != expectedFee {
t.Fatalf("expected fee %v, got %v", expectedFee, fee)
}

fwdFee := policy.ComputeFeeFromIncoming(outgoingAmt + fee)
if fwdFee != expectedFee {
t.Fatalf("expected fee %v, but got %v", fee, fwdFee)
}
}
94 changes: 94 additions & 0 deletions cmd/lncli/cmd_build_route.go
@@ -0,0 +1,94 @@
// +build routerrpc

package main

import (
"context"
"errors"
"fmt"
"strings"

"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli"
)

var buildRouteCommand = cli.Command{
Name: "buildroute",
Category: "Payments",
Usage: "Build a route from a list of hop pubkeys.",
Action: actionDecorator(buildRoute),
Flags: []cli.Flag{
cli.Int64Flag{
Name: "amt",
Usage: "the amount to send expressed in satoshis. If" +
"not set, the minimum routable amount is used",
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "number of blocks the last hop has to reveal " +
"the preimage",
Value: lnd.DefaultBitcoinTimeLockDelta,
},
cli.StringFlag{
Name: "hops",
Usage: "comma separated hex pubkeys",
},
cli.Uint64Flag{
Name: "outgoing_chan_id",
Usage: "short channel id of the outgoing channel to " +
"use for the first hop of the payment",
Value: 0,
},
},
}

func buildRoute(ctx *cli.Context) error {
conn := getClientConn(ctx, false)
defer conn.Close()

client := routerrpc.NewRouterClient(conn)

if !ctx.IsSet("hops") {
return errors.New("hops required")
}

// Build list of hop addresses for the rpc.
hops := strings.Split(ctx.String("hops"), ",")
rpcHops := make([][]byte, 0, len(hops))
for _, k := range hops {
pubkey, err := route.NewVertexFromStr(k)
if err != nil {
return fmt.Errorf("error parsing %v: %v", k, err)
}
rpcHops = append(rpcHops, pubkey[:])
}

var amtMsat int64
hasAmt := ctx.IsSet("amt")
if hasAmt {
amtMsat = ctx.Int64("amt") * 1000
if amtMsat == 0 {
return fmt.Errorf("non-zero amount required")
}
}

// Call BuildRoute rpc.
req := &routerrpc.BuildRouteRequest{
AmtMsat: amtMsat,
FinalCltvDelta: int32(ctx.Int64("final_cltv_delta")),
HopPubkeys: rpcHops,
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
}

rpcCtx := context.Background()
route, err := client.BuildRoute(rpcCtx, req)
if err != nil {
return err
}

printJSON(route)

return nil
}
56 changes: 35 additions & 21 deletions cmd/lncli/commands.go
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh/terminal"
Expand Down Expand Up @@ -2381,22 +2382,23 @@ var sendToRouteCommand = cli.Command{
Usage: "Send a payment over a predefined route.",
Description: `
Send a payment over Lightning using a specific route. One must specify
a list of routes to attempt and the payment hash. This command can even
be chained with the response to queryroutes. This command can be used
to implement channel rebalancing by crafting a self-route, or even
atomic swaps using a self-route that crosses multiple chains.
the route to attempt and the payment hash. This command can even
be chained with the response to queryroutes or buildroute. This command
can be used to implement channel rebalancing by crafting a self-route,
or even atomic swaps using a self-route that crosses multiple chains.
There are three ways to specify routes:
There are three ways to specify a route:
* using the --routes parameter to manually specify a JSON encoded
set of routes in the format of the return value of queryroutes:
route in the format of the return value of queryroutes or
buildroute:
(lncli sendtoroute --payment_hash=<pay_hash> --routes=<route>)
* passing the routes as a positional argument:
* passing the route as a positional argument:
(lncli sendtoroute --payment_hash=pay_hash <route>)
* or reading in the routes from stdin, which can allow chaining the
response from queryroutes, or even read in a file with a set of
pre-computed routes:
* or reading in the route from stdin, which can allow chaining the
response from queryroutes or buildroute, or even read in a file
with a pre-computed route:
(lncli queryroutes --args.. | lncli sendtoroute --payment_hash= -
notice the '-' at the end, which signals that lncli should read
Expand Down Expand Up @@ -2474,25 +2476,37 @@ func sendToRoute(ctx *cli.Context) error {
jsonRoutes = string(b)
}

// Try to parse the provided json both in the legacy QueryRoutes format
// that contains a list of routes and the single route BuildRoute
// format.
var route *lnrpc.Route
routes := &lnrpc.QueryRoutesResponse{}
err = jsonpb.UnmarshalString(jsonRoutes, routes)
if err != nil {
return fmt.Errorf("unable to unmarshal json string "+
"from incoming array of routes: %v", err)
}
if err == nil {
if len(routes.Routes) == 0 {
return fmt.Errorf("no routes provided")
}

if len(routes.Routes) == 0 {
return fmt.Errorf("no routes provided")
}
if len(routes.Routes) != 1 {
return fmt.Errorf("expected a single route, but got %v",
len(routes.Routes))
}

route = routes.Routes[0]
} else {
routes := &routerrpc.BuildRouteResponse{}
err = jsonpb.UnmarshalString(jsonRoutes, routes)
if err != nil {
return fmt.Errorf("unable to unmarshal json string "+
"from incoming array of routes: %v", err)
}

if len(routes.Routes) != 1 {
return fmt.Errorf("expected a single route, but got %v",
len(routes.Routes))
route = routes.Route
}

req := &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: routes.Routes[0],
Route: route,
}

return sendToRouteRequest(ctx, req)
Expand Down
6 changes: 5 additions & 1 deletion cmd/lncli/routerrpc_active.go
Expand Up @@ -6,5 +6,9 @@ import "github.com/urfave/cli"

// routerCommands will return nil for non-routerrpc builds.
func routerCommands() []cli.Command {
return []cli.Command{queryMissionControlCommand, resetMissionControlCommand}
return []cli.Command{
queryMissionControlCommand,
resetMissionControlCommand,
buildRouteCommand,
}
}

0 comments on commit 04dd936

Please sign in to comment.