Merge pull request #3440 from joostjager/buildroute

routing: add build route functionality
This commit is contained in:
Olaoluwa Osuntokun 2019-09-24 20:24:24 -07:00 committed by GitHub
commit 18f88cbd8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1007 additions and 145 deletions

@ -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
@ -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

@ -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)
}
}

@ -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
}

@ -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"
@ -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
@ -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))
}
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)
}
route = routes.Route
}
req := &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: routes.Routes[0],
Route: route,
}
return sendToRouteRequest(ctx, req)

@ -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,
}
}

@ -1197,6 +1197,122 @@ func (m *PairHistory) GetLastAttemptSuccessful() bool {
return false
}
type BuildRouteRequest struct {
//*
//The amount to send expressed in msat. If set to zero, the minimum routable
//amount is used.
AmtMsat int64 `protobuf:"varint,1,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
//*
//CLTV delta from the current height that should be used for the timelock
//of the final hop
FinalCltvDelta int32 `protobuf:"varint,2,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
//*
//The channel id of the channel that must be taken to the first hop. If zero,
//any channel may be used.
OutgoingChanId uint64 `protobuf:"varint,3,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
//*
//A list of hops that defines the route. This does not include the source hop
//pubkey.
HopPubkeys [][]byte `protobuf:"bytes,4,rep,name=hop_pubkeys,json=hopPubkeys,proto3" json:"hop_pubkeys,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *BuildRouteRequest) Reset() { *m = BuildRouteRequest{} }
func (m *BuildRouteRequest) String() string { return proto.CompactTextString(m) }
func (*BuildRouteRequest) ProtoMessage() {}
func (*BuildRouteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{15}
}
func (m *BuildRouteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_BuildRouteRequest.Unmarshal(m, b)
}
func (m *BuildRouteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_BuildRouteRequest.Marshal(b, m, deterministic)
}
func (m *BuildRouteRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_BuildRouteRequest.Merge(m, src)
}
func (m *BuildRouteRequest) XXX_Size() int {
return xxx_messageInfo_BuildRouteRequest.Size(m)
}
func (m *BuildRouteRequest) XXX_DiscardUnknown() {
xxx_messageInfo_BuildRouteRequest.DiscardUnknown(m)
}
var xxx_messageInfo_BuildRouteRequest proto.InternalMessageInfo
func (m *BuildRouteRequest) GetAmtMsat() int64 {
if m != nil {
return m.AmtMsat
}
return 0
}
func (m *BuildRouteRequest) GetFinalCltvDelta() int32 {
if m != nil {
return m.FinalCltvDelta
}
return 0
}
func (m *BuildRouteRequest) GetOutgoingChanId() uint64 {
if m != nil {
return m.OutgoingChanId
}
return 0
}
func (m *BuildRouteRequest) GetHopPubkeys() [][]byte {
if m != nil {
return m.HopPubkeys
}
return nil
}
type BuildRouteResponse struct {
//*
//Fully specified route that can be used to execute the payment.
Route *lnrpc.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *BuildRouteResponse) Reset() { *m = BuildRouteResponse{} }
func (m *BuildRouteResponse) String() string { return proto.CompactTextString(m) }
func (*BuildRouteResponse) ProtoMessage() {}
func (*BuildRouteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_7a0613f69d37b0a5, []int{16}
}
func (m *BuildRouteResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_BuildRouteResponse.Unmarshal(m, b)
}
func (m *BuildRouteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_BuildRouteResponse.Marshal(b, m, deterministic)
}
func (m *BuildRouteResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_BuildRouteResponse.Merge(m, src)
}
func (m *BuildRouteResponse) XXX_Size() int {
return xxx_messageInfo_BuildRouteResponse.Size(m)
}
func (m *BuildRouteResponse) XXX_DiscardUnknown() {
xxx_messageInfo_BuildRouteResponse.DiscardUnknown(m)
}
var xxx_messageInfo_BuildRouteResponse proto.InternalMessageInfo
func (m *BuildRouteResponse) GetRoute() *lnrpc.Route {
if m != nil {
return m.Route
}
return nil
}
func init() {
proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value)
proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value)
@ -1216,123 +1332,131 @@ func init() {
proto.RegisterType((*QueryMissionControlResponse)(nil), "routerrpc.QueryMissionControlResponse")
proto.RegisterType((*NodeHistory)(nil), "routerrpc.NodeHistory")
proto.RegisterType((*PairHistory)(nil), "routerrpc.PairHistory")
proto.RegisterType((*BuildRouteRequest)(nil), "routerrpc.BuildRouteRequest")
proto.RegisterType((*BuildRouteResponse)(nil), "routerrpc.BuildRouteResponse")
}
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 1769 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x41, 0x73, 0x22, 0xb9,
0x15, 0x5e, 0x0c, 0x18, 0x78, 0x80, 0xdd, 0x96, 0x3d, 0x76, 0x0f, 0x1e, 0xef, 0x7a, 0xd9, 0xcd,
0xac, 0x6b, 0x6a, 0x63, 0x6f, 0x9c, 0xda, 0xad, 0xa9, 0x3d, 0x24, 0xc5, 0x80, 0x58, 0xf7, 0x0c,
0x74, 0x7b, 0x05, 0xcc, 0xee, 0x24, 0x07, 0x95, 0x0c, 0xb2, 0xe9, 0x72, 0xd3, 0xcd, 0x74, 0x0b,
0x67, 0x9c, 0x43, 0x2e, 0xa9, 0x1c, 0x73, 0xcf, 0xbf, 0xc8, 0x6f, 0xca, 0x25, 0xf9, 0x05, 0x39,
0xa6, 0x2a, 0x25, 0xa9, 0x1b, 0x1a, 0x8c, 0x27, 0x39, 0xd1, 0xfa, 0xde, 0xa7, 0x27, 0xe9, 0x3d,
0xbd, 0x4f, 0x0f, 0xd8, 0x0f, 0x83, 0x99, 0xe0, 0x61, 0x38, 0x1d, 0x9e, 0xe9, 0xaf, 0xd3, 0x69,
0x18, 0x88, 0x00, 0x95, 0xe6, 0x78, 0xad, 0x14, 0x4e, 0x87, 0x1a, 0xad, 0xff, 0x27, 0x0b, 0xa8,
0xc7, 0xfd, 0xd1, 0x25, 0xbb, 0x9f, 0x70, 0x5f, 0x10, 0xfe, 0x7e, 0xc6, 0x23, 0x81, 0x10, 0xe4,
0x46, 0x3c, 0x12, 0x66, 0xe6, 0x38, 0x73, 0x52, 0x21, 0xea, 0x1b, 0x19, 0x90, 0x65, 0x13, 0x61,
0x6e, 0x1c, 0x67, 0x4e, 0xb2, 0x44, 0x7e, 0xa2, 0xcf, 0xa1, 0x32, 0xd5, 0xf3, 0xe8, 0x98, 0x45,
0x63, 0x33, 0xab, 0xd8, 0xe5, 0x18, 0xbb, 0x60, 0xd1, 0x18, 0x9d, 0x80, 0x71, 0xed, 0xfa, 0xcc,
0xa3, 0x43, 0x4f, 0xdc, 0xd1, 0x11, 0xf7, 0x04, 0x33, 0x73, 0xc7, 0x99, 0x93, 0x3c, 0xd9, 0x52,
0x78, 0xd3, 0x13, 0x77, 0x2d, 0x89, 0xa2, 0xaf, 0x60, 0x3b, 0x71, 0x16, 0xea, 0x5d, 0x98, 0xf9,
0xe3, 0xcc, 0x49, 0x89, 0x6c, 0x4d, 0x97, 0xf7, 0xf6, 0x15, 0x6c, 0x0b, 0x77, 0xc2, 0x83, 0x99,
0xa0, 0x11, 0x1f, 0x06, 0xfe, 0x28, 0x32, 0x37, 0xb5, 0xc7, 0x18, 0xee, 0x69, 0x14, 0xd5, 0xa1,
0x7a, 0xcd, 0x39, 0xf5, 0xdc, 0x89, 0x2b, 0x68, 0xc4, 0x84, 0x59, 0x50, 0x5b, 0x2f, 0x5f, 0x73,
0xde, 0x91, 0x58, 0x8f, 0x09, 0xb9, 0xbf, 0x60, 0x26, 0x6e, 0x02, 0xd7, 0xbf, 0xa1, 0xc3, 0x31,
0xf3, 0xa9, 0x3b, 0x32, 0x8b, 0xc7, 0x99, 0x93, 0x1c, 0xd9, 0x4a, 0xf0, 0xe6, 0x98, 0xf9, 0xd6,
0x08, 0x1d, 0x01, 0xa8, 0x33, 0x28, 0x77, 0x66, 0x49, 0xad, 0x58, 0x92, 0x88, 0xf2, 0x85, 0xce,
0xa1, 0xac, 0x02, 0x4c, 0xc7, 0xae, 0x2f, 0x22, 0x13, 0x8e, 0xb3, 0x27, 0xe5, 0x73, 0xe3, 0xd4,
0xf3, 0x65, 0xac, 0x89, 0xb4, 0x5c, 0xb8, 0xbe, 0x20, 0x69, 0x12, 0xc2, 0x50, 0x94, 0x91, 0xa5,
0xc2, 0xbb, 0x33, 0xcb, 0x6a, 0xc2, 0x8b, 0xd3, 0x79, 0x96, 0x4e, 0x1f, 0xa6, 0xe5, 0xb4, 0xc5,
0x23, 0xd1, 0xf7, 0xee, 0xb0, 0x2f, 0xc2, 0x7b, 0x52, 0x18, 0xe9, 0x51, 0xed, 0x7b, 0xa8, 0xa4,
0x0d, 0x32, 0x51, 0xb7, 0xfc, 0x5e, 0xe5, 0x2e, 0x47, 0xe4, 0x27, 0xda, 0x83, 0xfc, 0x1d, 0xf3,
0x66, 0x5c, 0x25, 0xaf, 0x42, 0xf4, 0xe0, 0xfb, 0x8d, 0x97, 0x99, 0xfa, 0x4b, 0xd8, 0xed, 0x87,
0x6c, 0x78, 0xbb, 0x92, 0xff, 0xd5, 0xcc, 0x66, 0x1e, 0x64, 0xb6, 0xfe, 0x27, 0xa8, 0xc6, 0x93,
0x7a, 0x82, 0x89, 0x59, 0x84, 0x7e, 0x09, 0xf9, 0x48, 0x30, 0xc1, 0x15, 0x79, 0xeb, 0xfc, 0x20,
0x75, 0x94, 0x14, 0x91, 0x13, 0xcd, 0x42, 0x35, 0x28, 0x4e, 0x43, 0xee, 0x4e, 0xd8, 0x4d, 0xb2,
0xad, 0xf9, 0x18, 0xd5, 0x21, 0xaf, 0x26, 0xab, 0x1b, 0x55, 0x3e, 0xaf, 0xa4, 0xc3, 0x48, 0xb4,
0xa9, 0xfe, 0x1b, 0xd8, 0x56, 0xe3, 0x36, 0xe7, 0x1f, 0xbb, 0xb5, 0x07, 0x50, 0x60, 0x13, 0x9d,
0x7e, 0x7d, 0x73, 0x37, 0xd9, 0x44, 0x66, 0xbe, 0x3e, 0x02, 0x63, 0x31, 0x3f, 0x9a, 0x06, 0x7e,
0xc4, 0xe5, 0x6d, 0x90, 0xce, 0xe5, 0x65, 0x90, 0x37, 0x67, 0x22, 0x67, 0x65, 0xd4, 0xac, 0xad,
0x18, 0x6f, 0x73, 0xde, 0x8d, 0x98, 0x40, 0xcf, 0xf5, 0x25, 0xa4, 0x5e, 0x30, 0xbc, 0x95, 0xd7,
0x9a, 0xdd, 0xc7, 0xee, 0xab, 0x12, 0xee, 0x04, 0xc3, 0xdb, 0x96, 0x04, 0xeb, 0xbf, 0xd7, 0xe5,
0xd5, 0x0f, 0xf4, 0xde, 0xff, 0xef, 0xf0, 0x2e, 0x42, 0xb0, 0xf1, 0x78, 0x08, 0x28, 0xec, 0x2e,
0x39, 0x8f, 0x4f, 0x91, 0x8e, 0x6c, 0x66, 0x25, 0xb2, 0x5f, 0x43, 0xe1, 0x9a, 0xb9, 0xde, 0x2c,
0x4c, 0x1c, 0xa3, 0x54, 0x9a, 0xda, 0xda, 0x42, 0x12, 0x4a, 0xfd, 0x1f, 0x05, 0x28, 0xc4, 0x20,
0x3a, 0x87, 0xdc, 0x30, 0x18, 0x25, 0xd9, 0xfd, 0xf4, 0xe1, 0xb4, 0xe4, 0xb7, 0x19, 0x8c, 0x38,
0x51, 0x5c, 0xf4, 0x5b, 0xd8, 0x92, 0x45, 0xe5, 0x73, 0x8f, 0xce, 0xa6, 0x23, 0x36, 0x4f, 0xa8,
0x99, 0x9a, 0xdd, 0xd4, 0x84, 0x81, 0xb2, 0x93, 0xea, 0x30, 0x3d, 0x44, 0x87, 0x50, 0x1a, 0x0b,
0x6f, 0xa8, 0x33, 0x91, 0x53, 0x17, 0xba, 0x28, 0x01, 0x95, 0x83, 0x3a, 0x54, 0x03, 0xdf, 0x0d,
0x7c, 0x1a, 0x8d, 0x19, 0x3d, 0xff, 0xf6, 0x3b, 0xa5, 0x17, 0x15, 0x52, 0x56, 0x60, 0x6f, 0xcc,
0xce, 0xbf, 0xfd, 0x0e, 0x7d, 0x06, 0x65, 0x55, 0xb5, 0xfc, 0xc3, 0xd4, 0x0d, 0xef, 0x95, 0x50,
0x54, 0x89, 0x2a, 0x64, 0xac, 0x10, 0x59, 0x1a, 0xd7, 0x1e, 0xbb, 0x89, 0x94, 0x38, 0x54, 0x89,
0x1e, 0xa0, 0x6f, 0x60, 0x2f, 0x8e, 0x01, 0x8d, 0x82, 0x59, 0x38, 0xe4, 0xd4, 0xf5, 0x47, 0xfc,
0x83, 0x92, 0x86, 0x2a, 0x41, 0xb1, 0xad, 0xa7, 0x4c, 0x96, 0xb4, 0xa0, 0x7d, 0xd8, 0x1c, 0x73,
0xf7, 0x66, 0xac, 0xa5, 0xa1, 0x4a, 0xe2, 0x51, 0xfd, 0x6f, 0x79, 0x28, 0xa7, 0x02, 0x83, 0x2a,
0x50, 0x24, 0xb8, 0x87, 0xc9, 0x5b, 0xdc, 0x32, 0x3e, 0x41, 0x27, 0xf0, 0xa5, 0x65, 0x37, 0x1d,
0x42, 0x70, 0xb3, 0x4f, 0x1d, 0x42, 0x07, 0xf6, 0x1b, 0xdb, 0xf9, 0xc9, 0xa6, 0x97, 0x8d, 0x77,
0x5d, 0x6c, 0xf7, 0x69, 0x0b, 0xf7, 0x1b, 0x56, 0xa7, 0x67, 0x64, 0xd0, 0x33, 0x30, 0x17, 0xcc,
0xc4, 0xdc, 0xe8, 0x3a, 0x03, 0xbb, 0x6f, 0x6c, 0xa0, 0xcf, 0xe0, 0xb0, 0x6d, 0xd9, 0x8d, 0x0e,
0x5d, 0x70, 0x9a, 0x9d, 0xfe, 0x5b, 0x8a, 0x7f, 0xbe, 0xb4, 0xc8, 0x3b, 0x23, 0xbb, 0x8e, 0x70,
0xd1, 0xef, 0x34, 0x13, 0x0f, 0x39, 0xf4, 0x14, 0x9e, 0x68, 0x82, 0x9e, 0x42, 0xfb, 0x8e, 0x43,
0x7b, 0x8e, 0x63, 0x1b, 0x79, 0xb4, 0x03, 0x55, 0xcb, 0x7e, 0xdb, 0xe8, 0x58, 0x2d, 0x4a, 0x70,
0xa3, 0xd3, 0x35, 0x36, 0xd1, 0x2e, 0x6c, 0xaf, 0xf2, 0x0a, 0xd2, 0x45, 0xc2, 0x73, 0x6c, 0xcb,
0xb1, 0xe9, 0x5b, 0x4c, 0x7a, 0x96, 0x63, 0x1b, 0x45, 0xb4, 0x0f, 0x68, 0xd9, 0x74, 0xd1, 0x6d,
0x34, 0x8d, 0x12, 0x7a, 0x02, 0x3b, 0xcb, 0xf8, 0x1b, 0xfc, 0xce, 0x00, 0x64, 0xc2, 0x9e, 0xde,
0x18, 0x7d, 0x85, 0x3b, 0xce, 0x4f, 0xb4, 0x6b, 0xd9, 0x56, 0x77, 0xd0, 0x35, 0xca, 0x68, 0x0f,
0x8c, 0x36, 0xc6, 0xd4, 0xb2, 0x7b, 0x83, 0x76, 0xdb, 0x6a, 0x5a, 0xd8, 0xee, 0x1b, 0x15, 0xbd,
0xf2, 0xba, 0x83, 0x57, 0xe5, 0x84, 0xe6, 0x45, 0xc3, 0xb6, 0x71, 0x87, 0xb6, 0xac, 0x5e, 0xe3,
0x55, 0x07, 0xb7, 0x8c, 0x2d, 0x74, 0x04, 0x4f, 0xfb, 0xb8, 0x7b, 0xe9, 0x90, 0x06, 0x79, 0x47,
0x13, 0x7b, 0xbb, 0x61, 0x75, 0x06, 0x04, 0x1b, 0xdb, 0xe8, 0x73, 0x38, 0x22, 0xf8, 0xc7, 0x81,
0x45, 0x70, 0x8b, 0xda, 0x4e, 0x0b, 0xd3, 0x36, 0x6e, 0xf4, 0x07, 0x04, 0xd3, 0xae, 0xd5, 0xeb,
0x59, 0xf6, 0x0f, 0x86, 0x81, 0xbe, 0x84, 0xe3, 0x39, 0x65, 0xee, 0x60, 0x85, 0xb5, 0x23, 0xcf,
0x97, 0xa4, 0xd4, 0xc6, 0x3f, 0xf7, 0xe9, 0x25, 0xc6, 0xc4, 0x40, 0xa8, 0x06, 0xfb, 0x8b, 0xe5,
0xf5, 0x02, 0xf1, 0xda, 0xbb, 0xd2, 0x76, 0x89, 0x49, 0xb7, 0x61, 0xcb, 0x04, 0x2f, 0xd9, 0xf6,
0xe4, 0xb6, 0x17, 0xb6, 0xd5, 0x6d, 0x3f, 0x41, 0x7b, 0xb0, 0x9d, 0xac, 0x96, 0x80, 0xff, 0x2c,
0xa0, 0x03, 0x40, 0x03, 0x9b, 0xe0, 0x46, 0x4b, 0x1e, 0x7e, 0x6e, 0xf8, 0x57, 0xe1, 0x75, 0xae,
0xb8, 0x61, 0x64, 0xeb, 0x7f, 0xcf, 0x42, 0x75, 0xa9, 0x06, 0xd1, 0x33, 0x28, 0x45, 0xee, 0x8d,
0xcf, 0x84, 0x54, 0x09, 0x2d, 0x20, 0x0b, 0x40, 0xbd, 0x83, 0x63, 0xe6, 0xfa, 0x5a, 0xb9, 0xb4,
0x72, 0x97, 0x14, 0xa2, 0x74, 0xeb, 0x00, 0x0a, 0xc9, 0x3b, 0x9a, 0x55, 0xf5, 0xba, 0x39, 0xd4,
0xef, 0xe7, 0x33, 0x28, 0x49, 0x69, 0x8c, 0x04, 0x9b, 0x4c, 0x55, 0x29, 0x57, 0xc9, 0x02, 0x40,
0x5f, 0x40, 0x75, 0xc2, 0xa3, 0x88, 0xdd, 0x70, 0xaa, 0xcb, 0x11, 0x14, 0xa3, 0x12, 0x83, 0x6d,
0x55, 0x95, 0x5f, 0x40, 0x22, 0x0f, 0x31, 0x29, 0xaf, 0x49, 0x31, 0xa8, 0x49, 0xab, 0xca, 0x2c,
0x58, 0x5c, 0xf5, 0x69, 0x65, 0x16, 0x0c, 0xbd, 0x80, 0x1d, 0x2d, 0x2d, 0xae, 0xef, 0x4e, 0x66,
0x13, 0x2d, 0x31, 0x05, 0xb5, 0xe5, 0x6d, 0x25, 0x31, 0x1a, 0x57, 0x4a, 0xf3, 0x14, 0x8a, 0x57,
0x2c, 0xe2, 0xf2, 0x51, 0x88, 0x25, 0xa0, 0x20, 0xc7, 0x6d, 0xce, 0xa5, 0x49, 0x3e, 0x15, 0xa1,
0x14, 0x37, 0x5d, 0xf9, 0x85, 0x6b, 0xce, 0x89, 0x8c, 0xe3, 0x7c, 0x05, 0xf6, 0x61, 0xb1, 0x42,
0x39, 0xb5, 0x82, 0xc6, 0xd5, 0x0a, 0x2f, 0x60, 0x87, 0x7f, 0x10, 0x21, 0xa3, 0xc1, 0x94, 0xbd,
0x9f, 0x71, 0x3a, 0x62, 0x82, 0x99, 0x15, 0x15, 0xdc, 0x6d, 0x65, 0x70, 0x14, 0xde, 0x62, 0x82,
0xd5, 0x9f, 0x41, 0x8d, 0xf0, 0x88, 0x8b, 0xae, 0x1b, 0x45, 0x6e, 0xe0, 0x37, 0x03, 0x5f, 0x84,
0x81, 0x17, 0xbf, 0x2d, 0xf5, 0x23, 0x38, 0x5c, 0x6b, 0xd5, 0x8f, 0x83, 0x9c, 0xfc, 0xe3, 0x8c,
0x87, 0xf7, 0xeb, 0x27, 0xdf, 0xc3, 0xe1, 0x5a, 0x6b, 0xfc, 0xb2, 0x7c, 0x0d, 0x79, 0x3f, 0x18,
0xf1, 0xc8, 0xcc, 0xa8, 0x6e, 0x65, 0x3f, 0x25, 0xe3, 0x76, 0x30, 0xe2, 0x17, 0x6e, 0x24, 0x82,
0xf0, 0x9e, 0x68, 0x92, 0x64, 0x4f, 0x99, 0x1b, 0x46, 0xe6, 0xc6, 0x03, 0xf6, 0x25, 0x73, 0xc3,
0x39, 0x5b, 0x91, 0xea, 0x7f, 0xce, 0x40, 0x39, 0xe5, 0x44, 0x0a, 0xea, 0x74, 0x76, 0x95, 0x34,
0x32, 0x15, 0x12, 0x8f, 0xd0, 0x73, 0xd8, 0xf2, 0x58, 0x24, 0xa8, 0xd4, 0x60, 0x2a, 0x53, 0x1a,
0x3f, 0xbc, 0x2b, 0x28, 0x3a, 0x05, 0x14, 0x88, 0x31, 0x0f, 0x69, 0x34, 0x1b, 0x0e, 0x79, 0x14,
0xd1, 0x69, 0x18, 0x5c, 0xa9, 0x3b, 0xb9, 0x41, 0xd6, 0x58, 0x5e, 0xe7, 0x8a, 0x39, 0x23, 0x5f,
0xff, 0x77, 0x06, 0xca, 0xa9, 0xcd, 0xc9, 0x5b, 0x2b, 0x0f, 0x43, 0xaf, 0xc3, 0x60, 0x92, 0xd4,
0xc2, 0x1c, 0x40, 0x26, 0x14, 0xd4, 0x40, 0x04, 0x71, 0x21, 0x24, 0xc3, 0xe5, 0xdb, 0x9e, 0x55,
0x1b, 0x4c, 0xdd, 0xf6, 0x73, 0xd8, 0x9b, 0xb8, 0x3e, 0x9d, 0x72, 0x9f, 0x79, 0xee, 0x1f, 0x39,
0x4d, 0x3a, 0x94, 0x9c, 0x22, 0xae, 0xb5, 0xa1, 0x3a, 0x54, 0x96, 0x4e, 0x92, 0x57, 0x27, 0x59,
0xc2, 0xd0, 0x4b, 0x38, 0x50, 0x51, 0x60, 0x42, 0xf0, 0xc9, 0x54, 0x24, 0x07, 0xbc, 0x9e, 0x79,
0xaa, 0x06, 0x8a, 0xe4, 0x31, 0xf3, 0x8b, 0xbf, 0x66, 0xa0, 0x92, 0xee, 0xd2, 0x50, 0x15, 0x4a,
0x96, 0x4d, 0xdb, 0x1d, 0xeb, 0x87, 0x8b, 0xbe, 0xf1, 0x89, 0x1c, 0xf6, 0x06, 0xcd, 0x26, 0xc6,
0x2d, 0xdc, 0x32, 0x32, 0x08, 0xc1, 0x96, 0x14, 0x12, 0xdc, 0xa2, 0x7d, 0xab, 0x8b, 0x9d, 0x81,
0x7c, 0x83, 0x76, 0x61, 0x3b, 0xc6, 0x6c, 0x87, 0x12, 0x67, 0xd0, 0xc7, 0x46, 0x16, 0x19, 0x50,
0x89, 0x41, 0x4c, 0x88, 0x43, 0x8c, 0x9c, 0x14, 0xce, 0x18, 0x79, 0xf8, 0x9e, 0x25, 0xcf, 0x5d,
0xfe, 0xfc, 0x2f, 0x39, 0xd8, 0x54, 0x5d, 0x4d, 0x88, 0x2e, 0xa0, 0x9c, 0x6a, 0x85, 0xd1, 0xd1,
0x47, 0x5b, 0xe4, 0x9a, 0xb9, 0xbe, 0xed, 0x9c, 0x45, 0xdf, 0x64, 0xd0, 0x6b, 0xa8, 0xa4, 0x9b,
0x5d, 0x94, 0x6e, 0x62, 0xd6, 0x74, 0xc1, 0x1f, 0xf5, 0xf5, 0x06, 0x0c, 0x1c, 0x09, 0x77, 0x22,
0x9b, 0x96, 0xb8, 0x8d, 0x44, 0xb5, 0x14, 0x7f, 0xa5, 0x37, 0xad, 0x1d, 0xae, 0xb5, 0xc5, 0x75,
0xd5, 0xd1, 0x47, 0x8c, 0x1b, 0xb9, 0x07, 0x47, 0x5c, 0xee, 0x1e, 0x6b, 0x9f, 0x3e, 0x66, 0x8e,
0xbd, 0x8d, 0x60, 0x77, 0x8d, 0x02, 0xa0, 0x5f, 0xa4, 0x77, 0xf0, 0xa8, 0x7e, 0xd4, 0x9e, 0xff,
0x2f, 0xda, 0x62, 0x95, 0x35, 0x52, 0xb1, 0xb4, 0xca, 0xe3, 0x42, 0xb3, 0xb4, 0xca, 0x47, 0x14,
0xe7, 0xd5, 0xaf, 0x7e, 0x77, 0x76, 0xe3, 0x8a, 0xf1, 0xec, 0xea, 0x74, 0x18, 0x4c, 0xce, 0x3c,
0xd9, 0x52, 0xf9, 0xae, 0x7f, 0xe3, 0x73, 0xf1, 0x87, 0x20, 0xbc, 0x3d, 0xf3, 0xfc, 0xd1, 0x99,
0x6a, 0x8c, 0xcf, 0xe6, 0xee, 0xae, 0x36, 0xd5, 0x3f, 0xdb, 0x5f, 0xff, 0x37, 0x00, 0x00, 0xff,
0xff, 0x3c, 0xe4, 0x5c, 0x67, 0x09, 0x0f, 0x00, 0x00,
// 1859 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0x22, 0xc7,
0x15, 0x37, 0x02, 0x04, 0x3c, 0x40, 0x1a, 0xb5, 0xb4, 0xd2, 0x2c, 0x92, 0xbc, 0x32, 0x76, 0xd6,
0xaa, 0x2d, 0x47, 0x72, 0x94, 0xb2, 0x6b, 0xcb, 0x87, 0xa4, 0x58, 0x68, 0xac, 0xd9, 0x85, 0x19,
0xb9, 0x81, 0xb5, 0x37, 0x39, 0x74, 0xb5, 0xa0, 0x25, 0xa6, 0x34, 0xcc, 0xb0, 0x33, 0x8d, 0xb2,
0xca, 0x21, 0x97, 0x9c, 0x73, 0xcf, 0x3d, 0x1f, 0x20, 0x5f, 0x25, 0x5f, 0x21, 0x97, 0xe4, 0x13,
0xe4, 0x98, 0xaa, 0x54, 0x77, 0xcf, 0xc0, 0x20, 0xa1, 0x8d, 0x4f, 0x9a, 0xfe, 0xbd, 0xd7, 0xaf,
0x5f, 0xbf, 0x3f, 0xbf, 0x7e, 0x02, 0x76, 0xc3, 0x60, 0x26, 0x78, 0x18, 0x4e, 0x87, 0xa7, 0xfa,
0xeb, 0x64, 0x1a, 0x06, 0x22, 0x40, 0xa5, 0x39, 0x5e, 0x2b, 0x85, 0xd3, 0xa1, 0x46, 0xeb, 0xff,
0xcd, 0x02, 0xea, 0x71, 0x7f, 0x74, 0xc1, 0xee, 0x26, 0xdc, 0x17, 0x84, 0xbf, 0x9f, 0xf1, 0x48,
0x20, 0x04, 0xb9, 0x11, 0x8f, 0x84, 0x99, 0x39, 0xca, 0x1c, 0x57, 0x88, 0xfa, 0x46, 0x06, 0x64,
0xd9, 0x44, 0x98, 0x6b, 0x47, 0x99, 0xe3, 0x2c, 0x91, 0x9f, 0xe8, 0x33, 0xa8, 0x4c, 0xf5, 0x3e,
0x3a, 0x66, 0xd1, 0xd8, 0xcc, 0x2a, 0xed, 0x72, 0x8c, 0x9d, 0xb3, 0x68, 0x8c, 0x8e, 0xc1, 0xb8,
0x72, 0x7d, 0xe6, 0xd1, 0xa1, 0x27, 0x6e, 0xe9, 0x88, 0x7b, 0x82, 0x99, 0xb9, 0xa3, 0xcc, 0x71,
0x9e, 0x6c, 0x28, 0xbc, 0xe9, 0x89, 0xdb, 0x96, 0x44, 0xd1, 0x97, 0xb0, 0x99, 0x18, 0x0b, 0xb5,
0x17, 0x66, 0xfe, 0x28, 0x73, 0x5c, 0x22, 0x1b, 0xd3, 0x65, 0xdf, 0xbe, 0x84, 0x4d, 0xe1, 0x4e,
0x78, 0x30, 0x13, 0x34, 0xe2, 0xc3, 0xc0, 0x1f, 0x45, 0xe6, 0xba, 0xb6, 0x18, 0xc3, 0x3d, 0x8d,
0xa2, 0x3a, 0x54, 0xaf, 0x38, 0xa7, 0x9e, 0x3b, 0x71, 0x05, 0x8d, 0x98, 0x30, 0x0b, 0xca, 0xf5,
0xf2, 0x15, 0xe7, 0x1d, 0x89, 0xf5, 0x98, 0x90, 0xfe, 0x05, 0x33, 0x71, 0x1d, 0xb8, 0xfe, 0x35,
0x1d, 0x8e, 0x99, 0x4f, 0xdd, 0x91, 0x59, 0x3c, 0xca, 0x1c, 0xe7, 0xc8, 0x46, 0x82, 0x37, 0xc7,
0xcc, 0xb7, 0x46, 0xe8, 0x10, 0x40, 0xdd, 0x41, 0x99, 0x33, 0x4b, 0xea, 0xc4, 0x92, 0x44, 0x94,
0x2d, 0x74, 0x06, 0x65, 0x15, 0x60, 0x3a, 0x76, 0x7d, 0x11, 0x99, 0x70, 0x94, 0x3d, 0x2e, 0x9f,
0x19, 0x27, 0x9e, 0x2f, 0x63, 0x4d, 0xa4, 0xe4, 0xdc, 0xf5, 0x05, 0x49, 0x2b, 0x21, 0x0c, 0x45,
0x19, 0x59, 0x2a, 0xbc, 0x5b, 0xb3, 0xac, 0x36, 0xbc, 0x38, 0x99, 0x67, 0xe9, 0xe4, 0x61, 0x5a,
0x4e, 0x5a, 0x3c, 0x12, 0x7d, 0xef, 0x16, 0xfb, 0x22, 0xbc, 0x23, 0x85, 0x91, 0x5e, 0xd5, 0xbe,
0x83, 0x4a, 0x5a, 0x20, 0x13, 0x75, 0xc3, 0xef, 0x54, 0xee, 0x72, 0x44, 0x7e, 0xa2, 0x1d, 0xc8,
0xdf, 0x32, 0x6f, 0xc6, 0x55, 0xf2, 0x2a, 0x44, 0x2f, 0xbe, 0x5b, 0x7b, 0x99, 0xa9, 0xbf, 0x84,
0xed, 0x7e, 0xc8, 0x86, 0x37, 0xf7, 0xf2, 0x7f, 0x3f, 0xb3, 0x99, 0x07, 0x99, 0xad, 0xff, 0x09,
0xaa, 0xf1, 0xa6, 0x9e, 0x60, 0x62, 0x16, 0xa1, 0x5f, 0x42, 0x3e, 0x12, 0x4c, 0x70, 0xa5, 0xbc,
0x71, 0xb6, 0x97, 0xba, 0x4a, 0x4a, 0x91, 0x13, 0xad, 0x85, 0x6a, 0x50, 0x9c, 0x86, 0xdc, 0x9d,
0xb0, 0xeb, 0xc4, 0xad, 0xf9, 0x1a, 0xd5, 0x21, 0xaf, 0x36, 0xab, 0x8a, 0x2a, 0x9f, 0x55, 0xd2,
0x61, 0x24, 0x5a, 0x54, 0xff, 0x0d, 0x6c, 0xaa, 0x75, 0x9b, 0xf3, 0x8f, 0x55, 0xed, 0x1e, 0x14,
0xd8, 0x44, 0xa7, 0x5f, 0x57, 0xee, 0x3a, 0x9b, 0xc8, 0xcc, 0xd7, 0x47, 0x60, 0x2c, 0xf6, 0x47,
0xd3, 0xc0, 0x8f, 0xb8, 0xac, 0x06, 0x69, 0x5c, 0x16, 0x83, 0xac, 0x9c, 0x89, 0xdc, 0x95, 0x51,
0xbb, 0x36, 0x62, 0xbc, 0xcd, 0x79, 0x37, 0x62, 0x02, 0x3d, 0xd7, 0x45, 0x48, 0xbd, 0x60, 0x78,
0x23, 0xcb, 0x9a, 0xdd, 0xc5, 0xe6, 0xab, 0x12, 0xee, 0x04, 0xc3, 0x9b, 0x96, 0x04, 0xeb, 0xbf,
0xd7, 0xed, 0xd5, 0x0f, 0xb4, 0xef, 0x3f, 0x3b, 0xbc, 0x8b, 0x10, 0xac, 0x3d, 0x1e, 0x02, 0x0a,
0xdb, 0x4b, 0xc6, 0xe3, 0x5b, 0xa4, 0x23, 0x9b, 0xb9, 0x17, 0xd9, 0xaf, 0xa0, 0x70, 0xc5, 0x5c,
0x6f, 0x16, 0x26, 0x86, 0x51, 0x2a, 0x4d, 0x6d, 0x2d, 0x21, 0x89, 0x4a, 0xfd, 0x9f, 0x05, 0x28,
0xc4, 0x20, 0x3a, 0x83, 0xdc, 0x30, 0x18, 0x25, 0xd9, 0xfd, 0xf4, 0xe1, 0xb6, 0xe4, 0x6f, 0x33,
0x18, 0x71, 0xa2, 0x74, 0xd1, 0x6f, 0x61, 0x43, 0x36, 0x95, 0xcf, 0x3d, 0x3a, 0x9b, 0x8e, 0xd8,
0x3c, 0xa1, 0x66, 0x6a, 0x77, 0x53, 0x2b, 0x0c, 0x94, 0x9c, 0x54, 0x87, 0xe9, 0x25, 0xda, 0x87,
0xd2, 0x58, 0x78, 0x43, 0x9d, 0x89, 0x9c, 0x2a, 0xe8, 0xa2, 0x04, 0x54, 0x0e, 0xea, 0x50, 0x0d,
0x7c, 0x37, 0xf0, 0x69, 0x34, 0x66, 0xf4, 0xec, 0x9b, 0x6f, 0x15, 0x5f, 0x54, 0x48, 0x59, 0x81,
0xbd, 0x31, 0x3b, 0xfb, 0xe6, 0x5b, 0xf4, 0x0c, 0xca, 0xaa, 0x6b, 0xf9, 0x87, 0xa9, 0x1b, 0xde,
0x29, 0xa2, 0xa8, 0x12, 0xd5, 0xc8, 0x58, 0x21, 0xb2, 0x35, 0xae, 0x3c, 0x76, 0x1d, 0x29, 0x72,
0xa8, 0x12, 0xbd, 0x40, 0x5f, 0xc3, 0x4e, 0x1c, 0x03, 0x1a, 0x05, 0xb3, 0x70, 0xc8, 0xa9, 0xeb,
0x8f, 0xf8, 0x07, 0x45, 0x0d, 0x55, 0x82, 0x62, 0x59, 0x4f, 0x89, 0x2c, 0x29, 0x41, 0xbb, 0xb0,
0x3e, 0xe6, 0xee, 0xf5, 0x58, 0x53, 0x43, 0x95, 0xc4, 0xab, 0xfa, 0x5f, 0xf3, 0x50, 0x4e, 0x05,
0x06, 0x55, 0xa0, 0x48, 0x70, 0x0f, 0x93, 0xb7, 0xb8, 0x65, 0x7c, 0x82, 0x8e, 0xe1, 0x0b, 0xcb,
0x6e, 0x3a, 0x84, 0xe0, 0x66, 0x9f, 0x3a, 0x84, 0x0e, 0xec, 0x37, 0xb6, 0xf3, 0xa3, 0x4d, 0x2f,
0x1a, 0xef, 0xba, 0xd8, 0xee, 0xd3, 0x16, 0xee, 0x37, 0xac, 0x4e, 0xcf, 0xc8, 0xa0, 0x03, 0x30,
0x17, 0x9a, 0x89, 0xb8, 0xd1, 0x75, 0x06, 0x76, 0xdf, 0x58, 0x43, 0xcf, 0x60, 0xbf, 0x6d, 0xd9,
0x8d, 0x0e, 0x5d, 0xe8, 0x34, 0x3b, 0xfd, 0xb7, 0x14, 0xff, 0x74, 0x61, 0x91, 0x77, 0x46, 0x76,
0x95, 0xc2, 0x79, 0xbf, 0xd3, 0x4c, 0x2c, 0xe4, 0xd0, 0x53, 0x78, 0xa2, 0x15, 0xf4, 0x16, 0xda,
0x77, 0x1c, 0xda, 0x73, 0x1c, 0xdb, 0xc8, 0xa3, 0x2d, 0xa8, 0x5a, 0xf6, 0xdb, 0x46, 0xc7, 0x6a,
0x51, 0x82, 0x1b, 0x9d, 0xae, 0xb1, 0x8e, 0xb6, 0x61, 0xf3, 0xbe, 0x5e, 0x41, 0x9a, 0x48, 0xf4,
0x1c, 0xdb, 0x72, 0x6c, 0xfa, 0x16, 0x93, 0x9e, 0xe5, 0xd8, 0x46, 0x11, 0xed, 0x02, 0x5a, 0x16,
0x9d, 0x77, 0x1b, 0x4d, 0xa3, 0x84, 0x9e, 0xc0, 0xd6, 0x32, 0xfe, 0x06, 0xbf, 0x33, 0x00, 0x99,
0xb0, 0xa3, 0x1d, 0xa3, 0xaf, 0x70, 0xc7, 0xf9, 0x91, 0x76, 0x2d, 0xdb, 0xea, 0x0e, 0xba, 0x46,
0x19, 0xed, 0x80, 0xd1, 0xc6, 0x98, 0x5a, 0x76, 0x6f, 0xd0, 0x6e, 0x5b, 0x4d, 0x0b, 0xdb, 0x7d,
0xa3, 0xa2, 0x4f, 0x5e, 0x75, 0xf1, 0xaa, 0xdc, 0xd0, 0x3c, 0x6f, 0xd8, 0x36, 0xee, 0xd0, 0x96,
0xd5, 0x6b, 0xbc, 0xea, 0xe0, 0x96, 0xb1, 0x81, 0x0e, 0xe1, 0x69, 0x1f, 0x77, 0x2f, 0x1c, 0xd2,
0x20, 0xef, 0x68, 0x22, 0x6f, 0x37, 0xac, 0xce, 0x80, 0x60, 0x63, 0x13, 0x7d, 0x06, 0x87, 0x04,
0xff, 0x30, 0xb0, 0x08, 0x6e, 0x51, 0xdb, 0x69, 0x61, 0xda, 0xc6, 0x8d, 0xfe, 0x80, 0x60, 0xda,
0xb5, 0x7a, 0x3d, 0xcb, 0xfe, 0xde, 0x30, 0xd0, 0x17, 0x70, 0x34, 0x57, 0x99, 0x1b, 0xb8, 0xa7,
0xb5, 0x25, 0xef, 0x97, 0xa4, 0xd4, 0xc6, 0x3f, 0xf5, 0xe9, 0x05, 0xc6, 0xc4, 0x40, 0xa8, 0x06,
0xbb, 0x8b, 0xe3, 0xf5, 0x01, 0xf1, 0xd9, 0xdb, 0x52, 0x76, 0x81, 0x49, 0xb7, 0x61, 0xcb, 0x04,
0x2f, 0xc9, 0x76, 0xa4, 0xdb, 0x0b, 0xd9, 0x7d, 0xb7, 0x9f, 0xa0, 0x1d, 0xd8, 0x4c, 0x4e, 0x4b,
0xc0, 0x7f, 0x15, 0xd0, 0x1e, 0xa0, 0x81, 0x4d, 0x70, 0xa3, 0x25, 0x2f, 0x3f, 0x17, 0xfc, 0xbb,
0xf0, 0x3a, 0x57, 0x5c, 0x33, 0xb2, 0xf5, 0xbf, 0x67, 0xa1, 0xba, 0xd4, 0x83, 0xe8, 0x00, 0x4a,
0x91, 0x7b, 0xed, 0x33, 0x21, 0x59, 0x42, 0x13, 0xc8, 0x02, 0x50, 0xef, 0xe0, 0x98, 0xb9, 0xbe,
0x66, 0x2e, 0xcd, 0xdc, 0x25, 0x85, 0x28, 0xde, 0xda, 0x83, 0x42, 0xf2, 0x8e, 0x66, 0x55, 0xbf,
0xae, 0x0f, 0xf5, 0xfb, 0x79, 0x00, 0x25, 0x49, 0x8d, 0x91, 0x60, 0x93, 0xa9, 0x6a, 0xe5, 0x2a,
0x59, 0x00, 0xe8, 0x73, 0xa8, 0x4e, 0x78, 0x14, 0xb1, 0x6b, 0x4e, 0x75, 0x3b, 0x82, 0xd2, 0xa8,
0xc4, 0x60, 0x5b, 0x75, 0xe5, 0xe7, 0x90, 0xd0, 0x43, 0xac, 0x94, 0xd7, 0x4a, 0x31, 0xa8, 0x95,
0xee, 0x33, 0xb3, 0x60, 0x71, 0xd7, 0xa7, 0x99, 0x59, 0x30, 0xf4, 0x02, 0xb6, 0x34, 0xb5, 0xb8,
0xbe, 0x3b, 0x99, 0x4d, 0x34, 0xc5, 0x14, 0x94, 0xcb, 0x9b, 0x8a, 0x62, 0x34, 0xae, 0x98, 0xe6,
0x29, 0x14, 0x2f, 0x59, 0xc4, 0xe5, 0xa3, 0x10, 0x53, 0x40, 0x41, 0xae, 0xdb, 0x9c, 0x4b, 0x91,
0x7c, 0x2a, 0x42, 0x49, 0x6e, 0xba, 0xf3, 0x0b, 0x57, 0x9c, 0x13, 0x19, 0xc7, 0xf9, 0x09, 0xec,
0xc3, 0xe2, 0x84, 0x72, 0xea, 0x04, 0x8d, 0xab, 0x13, 0x5e, 0xc0, 0x16, 0xff, 0x20, 0x42, 0x46,
0x83, 0x29, 0x7b, 0x3f, 0xe3, 0x74, 0xc4, 0x04, 0x33, 0x2b, 0x2a, 0xb8, 0x9b, 0x4a, 0xe0, 0x28,
0xbc, 0xc5, 0x04, 0xab, 0x1f, 0x40, 0x8d, 0xf0, 0x88, 0x8b, 0xae, 0x1b, 0x45, 0x6e, 0xe0, 0x37,
0x03, 0x5f, 0x84, 0x81, 0x17, 0xbf, 0x2d, 0xf5, 0x43, 0xd8, 0x5f, 0x29, 0xd5, 0x8f, 0x83, 0xdc,
0xfc, 0xc3, 0x8c, 0x87, 0x77, 0xab, 0x37, 0xdf, 0xc1, 0xfe, 0x4a, 0x69, 0xfc, 0xb2, 0x7c, 0x05,
0x79, 0x3f, 0x18, 0xf1, 0xc8, 0xcc, 0xa8, 0x69, 0x65, 0x37, 0x45, 0xe3, 0x76, 0x30, 0xe2, 0xe7,
0x6e, 0x24, 0x82, 0xf0, 0x8e, 0x68, 0x25, 0xa9, 0x3d, 0x65, 0x6e, 0x18, 0x99, 0x6b, 0x0f, 0xb4,
0x2f, 0x98, 0x1b, 0xce, 0xb5, 0x95, 0x52, 0xfd, 0xcf, 0x19, 0x28, 0xa7, 0x8c, 0x48, 0x42, 0x9d,
0xce, 0x2e, 0x93, 0x41, 0xa6, 0x42, 0xe2, 0x15, 0x7a, 0x0e, 0x1b, 0x1e, 0x8b, 0x04, 0x95, 0x1c,
0x4c, 0x65, 0x4a, 0xe3, 0x87, 0xf7, 0x1e, 0x8a, 0x4e, 0x00, 0x05, 0x62, 0xcc, 0x43, 0x1a, 0xcd,
0x86, 0x43, 0x1e, 0x45, 0x74, 0x1a, 0x06, 0x97, 0xaa, 0x26, 0xd7, 0xc8, 0x0a, 0xc9, 0xeb, 0x5c,
0x31, 0x67, 0xe4, 0xeb, 0xff, 0xc9, 0x40, 0x39, 0xe5, 0x9c, 0xac, 0x5a, 0x79, 0x19, 0x7a, 0x15,
0x06, 0x93, 0xa4, 0x17, 0xe6, 0x00, 0x32, 0xa1, 0xa0, 0x16, 0x22, 0x88, 0x1b, 0x21, 0x59, 0x2e,
0x57, 0x7b, 0x56, 0x39, 0x98, 0xaa, 0xf6, 0x33, 0xd8, 0x99, 0xb8, 0x3e, 0x9d, 0x72, 0x9f, 0x79,
0xee, 0x1f, 0x39, 0x4d, 0x26, 0x94, 0x9c, 0x52, 0x5c, 0x29, 0x43, 0x75, 0xa8, 0x2c, 0xdd, 0x24,
0xaf, 0x6e, 0xb2, 0x84, 0xa1, 0x97, 0xb0, 0xa7, 0xa2, 0xc0, 0x84, 0xe0, 0x93, 0xa9, 0x48, 0x2e,
0x78, 0x35, 0xf3, 0x54, 0x0f, 0x14, 0xc9, 0x63, 0xe2, 0xfa, 0xdf, 0x32, 0xb0, 0xf5, 0x6a, 0xe6,
0x7a, 0xa3, 0xa5, 0x39, 0xe5, 0x29, 0x14, 0xe5, 0xf1, 0xa9, 0x39, 0x48, 0x0e, 0x53, 0xaa, 0x60,
0x57, 0x0d, 0xf6, 0x6b, 0x2b, 0x07, 0xfb, 0x55, 0x23, 0x76, 0x76, 0xe5, 0x88, 0xfd, 0x0c, 0xca,
0xe3, 0x60, 0x4a, 0x75, 0xa2, 0x23, 0x33, 0x77, 0x94, 0x3d, 0xae, 0x10, 0x18, 0x07, 0xd3, 0x0b,
0x8d, 0xd4, 0x5f, 0x02, 0x4a, 0x3b, 0x19, 0x57, 0xe5, 0x7c, 0x54, 0xca, 0x3c, 0x3a, 0x2a, 0xbd,
0xf8, 0x4b, 0x06, 0x2a, 0xe9, 0x29, 0x14, 0x55, 0xa1, 0x64, 0xd9, 0xb4, 0xdd, 0xb1, 0xbe, 0x3f,
0xef, 0x1b, 0x9f, 0xc8, 0x65, 0x6f, 0xd0, 0x6c, 0x62, 0xdc, 0xc2, 0x2d, 0x23, 0x83, 0x10, 0x6c,
0x48, 0xa2, 0xc4, 0x2d, 0xda, 0xb7, 0xba, 0xd8, 0x19, 0xc8, 0x37, 0x76, 0x1b, 0x36, 0x63, 0xcc,
0x76, 0x28, 0x71, 0x06, 0x7d, 0x6c, 0x64, 0x91, 0x01, 0x95, 0x18, 0xc4, 0x84, 0x38, 0xc4, 0xc8,
0xc9, 0x87, 0x21, 0x46, 0x1e, 0xbe, 0xd7, 0xc9, 0x73, 0x9e, 0x3f, 0xfb, 0x47, 0x0e, 0xd6, 0x95,
0x83, 0x21, 0x3a, 0x87, 0x72, 0x6a, 0xd4, 0x47, 0x87, 0x1f, 0xfd, 0x17, 0xa0, 0x66, 0xae, 0x1e,
0xab, 0x67, 0xd1, 0xd7, 0x19, 0xf4, 0x1a, 0x2a, 0xe9, 0x61, 0x1e, 0xa5, 0x87, 0xb4, 0x15, 0x53,
0xfe, 0x47, 0x6d, 0xbd, 0x01, 0x03, 0x47, 0xc2, 0x9d, 0xc8, 0xa1, 0x2c, 0x1e, 0x93, 0x51, 0x2d,
0xa5, 0x7f, 0x6f, 0xf6, 0xae, 0xed, 0xaf, 0x94, 0xc5, 0x19, 0xea, 0xe8, 0x2b, 0xc6, 0x83, 0xea,
0x83, 0x2b, 0x2e, 0x4f, 0xc7, 0xb5, 0x4f, 0x1f, 0x13, 0xc7, 0xd6, 0x46, 0xb0, 0xbd, 0x82, 0xe1,
0xd0, 0x2f, 0xd2, 0x1e, 0x3c, 0xca, 0x8f, 0xb5, 0xe7, 0xff, 0x4f, 0x6d, 0x71, 0xca, 0x0a, 0x2a,
0x5c, 0x3a, 0xe5, 0x71, 0x22, 0x5d, 0x3a, 0xe5, 0x63, 0x8c, 0x6a, 0x01, 0x2c, 0x2a, 0x1a, 0x1d,
0xa4, 0x76, 0x3d, 0xe8, 0xc6, 0xda, 0xe1, 0x23, 0x52, 0x6d, 0xea, 0xd5, 0xaf, 0x7e, 0x77, 0x7a,
0xed, 0x8a, 0xf1, 0xec, 0xf2, 0x64, 0x18, 0x4c, 0x4e, 0x3d, 0x39, 0x7d, 0xfa, 0xae, 0x7f, 0xed,
0x73, 0xf1, 0x87, 0x20, 0xbc, 0x39, 0xf5, 0xfc, 0xd1, 0xa9, 0x6a, 0x8c, 0xd3, 0xb9, 0x95, 0xcb,
0x75, 0xf5, 0x23, 0xc0, 0xaf, 0xff, 0x17, 0x00, 0x00, 0xff, 0xff, 0x85, 0xe2, 0x3f, 0x31, 0x34,
0x10, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1373,6 +1497,11 @@ type RouterClient interface {
//QueryMissionControl exposes the internal mission control state to callers.
//It is a development feature.
QueryMissionControl(ctx context.Context, in *QueryMissionControlRequest, opts ...grpc.CallOption) (*QueryMissionControlResponse, error)
//*
//BuildRoute builds a fully specified route based on a list of hop public
//keys. It retrieves the relevant channel policies from the graph in order to
//calculate the correct fees and time locks.
BuildRoute(ctx context.Context, in *BuildRouteRequest, opts ...grpc.CallOption) (*BuildRouteResponse, error)
}
type routerClient struct {
@ -1483,6 +1612,15 @@ func (c *routerClient) QueryMissionControl(ctx context.Context, in *QueryMission
return out, nil
}
func (c *routerClient) BuildRoute(ctx context.Context, in *BuildRouteRequest, opts ...grpc.CallOption) (*BuildRouteResponse, error) {
out := new(BuildRouteResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/BuildRoute", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RouterServer is the server API for Router service.
type RouterServer interface {
//*
@ -1511,6 +1649,11 @@ type RouterServer interface {
//QueryMissionControl exposes the internal mission control state to callers.
//It is a development feature.
QueryMissionControl(context.Context, *QueryMissionControlRequest) (*QueryMissionControlResponse, error)
//*
//BuildRoute builds a fully specified route based on a list of hop public
//keys. It retrieves the relevant channel policies from the graph in order to
//calculate the correct fees and time locks.
BuildRoute(context.Context, *BuildRouteRequest) (*BuildRouteResponse, error)
}
func RegisterRouterServer(s *grpc.Server, srv RouterServer) {
@ -1631,6 +1774,24 @@ func _Router_QueryMissionControl_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _Router_BuildRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BuildRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).BuildRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/BuildRoute",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).BuildRoute(ctx, req.(*BuildRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Router_serviceDesc = grpc.ServiceDesc{
ServiceName: "routerrpc.Router",
HandlerType: (*RouterServer)(nil),
@ -1651,6 +1812,10 @@ var _Router_serviceDesc = grpc.ServiceDesc{
MethodName: "QueryMissionControl",
Handler: _Router_QueryMissionControl_Handler,
},
{
MethodName: "BuildRoute",
Handler: _Router_BuildRoute_Handler,
},
},
Streams: []grpc.StreamDesc{
{

@ -380,6 +380,39 @@ message PairHistory {
bool last_attempt_successful = 6 [json_name = "last_attempt_successful"];
}
message BuildRouteRequest {
/**
The amount to send expressed in msat. If set to zero, the minimum routable
amount is used.
*/
int64 amt_msat = 1;
/**
CLTV delta from the current height that should be used for the timelock
of the final hop
*/
int32 final_cltv_delta = 2;
/**
The channel id of the channel that must be taken to the first hop. If zero,
any channel may be used.
*/
uint64 outgoing_chan_id = 3;
/**
A list of hops that defines the route. This does not include the source hop
pubkey.
*/
repeated bytes hop_pubkeys = 4;
}
message BuildRouteResponse {
/**
Fully specified route that can be used to execute the payment.
*/
lnrpc.Route route = 1;
}
service Router {
/**
SendPayment attempts to route a payment described by the passed
@ -419,4 +452,11 @@ service Router {
It is a development feature.
*/
rpc QueryMissionControl(QueryMissionControlRequest) returns (QueryMissionControlResponse);
/**
BuildRoute builds a fully specified route based on a list of hop public
keys. It retrieves the relevant channel policies from the graph in order to
calculate the correct fees and time locks.
*/
rpc BuildRoute(BuildRouteRequest) returns (BuildRouteResponse);
}

@ -72,6 +72,10 @@ var (
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/BuildRoute": {{
Entity: "offchain",
Action: "read",
}},
}
// DefaultRouterMacFilename is the default name of the router macaroon
@ -612,3 +616,49 @@ func marshallFailureReason(reason channeldb.FailureReason) (
return 0, errors.New("unknown failure reason")
}
// BuildRoute builds a route from a list of hop addresses.
func (s *Server) BuildRoute(ctx context.Context,
req *BuildRouteRequest) (*BuildRouteResponse, error) {
// Unmarshall hop list.
hops := make([]route.Vertex, len(req.HopPubkeys))
for i, pubkeyBytes := range req.HopPubkeys {
pubkey, err := route.NewVertexFromBytes(pubkeyBytes)
if err != nil {
return nil, err
}
hops[i] = pubkey
}
// Prepare BuildRoute call parameters from rpc request.
var amt *lnwire.MilliSatoshi
if req.AmtMsat != 0 {
rpcAmt := lnwire.MilliSatoshi(req.AmtMsat)
amt = &rpcAmt
}
var outgoingChan *uint64
if req.OutgoingChanId != 0 {
outgoingChan = &req.OutgoingChanId
}
// Build the route and return it to the caller.
route, err := s.cfg.Router.BuildRoute(
amt, hops, outgoingChan, req.FinalCltvDelta,
)
if err != nil {
return nil, err
}
rpcRoute, err := s.cfg.RouterBackend.MarshallRoute(route)
if err != nil {
return nil, err
}
routeResp := &BuildRouteResponse{
Route: rpcRoute,
}
return routeResp, nil
}

@ -66,15 +66,6 @@ type edgePolicyWithSource struct {
edge *channeldb.ChannelEdgePolicy
}
// 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 computeFee(amt lnwire.MilliSatoshi,
edge *channeldb.ChannelEdgePolicy) lnwire.MilliSatoshi {
return edge.FeeBaseMSat + (amt*edge.FeeProportionalMillionths)/1000000
}
// newRoute returns a fully valid route between the source and target that's
// capable of supporting a payment of `amtToSend` after fees are fully
// computed. If the route is too long, or the selected path cannot support the
@ -129,7 +120,7 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
// and its policy for the outgoing channel. This policy
// is stored as part of the incoming channel of
// the next hop.
fee = computeFee(amtToForward, pathEdges[i+1])
fee = pathEdges[i+1].ComputeFee(amtToForward)
}
// If this is the last hop, then for verification purposes, the
@ -482,7 +473,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
var fee lnwire.MilliSatoshi
var timeLockDelta uint16
if fromVertex != source {
fee = computeFee(amountToSend, edge)
fee = edge.ComputeFee(amountToSend)
timeLockDelta = edge.TimeLockDelta
}

@ -3,6 +3,7 @@ package route
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"strconv"
@ -47,6 +48,22 @@ func NewVertexFromBytes(b []byte) (Vertex, error) {
return v, nil
}
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
func NewVertexFromStr(v string) (Vertex, error) {
// Return error if hex string is of incorrect length.
if len(v) != VertexSize*2 {
return Vertex{}, fmt.Errorf("invalid vertex string length of "+
"%v, want %v", len(v), VertexSize*2)
}
vertex, err := hex.DecodeString(v)
if err != nil {
return Vertex{}, err
}
return NewVertexFromBytes(vertex)
}
// String returns a human readable version of the Vertex which is the
// hex-encoding of the serialized compressed public key.
func (v Vertex) String() string {

@ -2253,3 +2253,297 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode,
return bandwidthHints, nil
}
// runningAmounts keeps running amounts while the route is traversed.
type runningAmounts struct {
// amt is the intended amount to send via the route.
amt lnwire.MilliSatoshi
// max is the running maximum that the route can carry.
max lnwire.MilliSatoshi
}
// prependChannel returns a new set of running amounts that would result from
// prepending the given channel to the route. If canIncreaseAmt is set, the
// amount may be increased if it is too small to satisfy the channel's minimum
// htlc amount.
func (r *runningAmounts) prependChannel(policy *channeldb.ChannelEdgePolicy,
capacity btcutil.Amount, localChan bool, canIncreaseAmt bool) (
runningAmounts, error) {
// Determine max htlc value.
maxHtlc := lnwire.NewMSatFromSatoshis(capacity)
if policy.MessageFlags.HasMaxHtlc() {
maxHtlc = policy.MaxHTLC
}
amt := r.amt
// If we have a specific amount for which we are building the route,
// validate it against the channel constraints and return the new
// running amount.
if !canIncreaseAmt {
if amt < policy.MinHTLC || amt > maxHtlc {
return runningAmounts{}, fmt.Errorf("channel htlc "+
"constraints [%v - %v] violated with amt %v",
policy.MinHTLC, maxHtlc, amt)
}
// Update running amount by adding the fee for non-local
// channels.
if !localChan {
amt += policy.ComputeFee(amt)
}
return runningAmounts{
amt: amt,
}, nil
}
// Adapt the minimum amount to what this channel allows.
if policy.MinHTLC > r.amt {
amt = policy.MinHTLC
}
// Update the maximum amount too to be able to detect incompatible
// channels.
max := r.max
if maxHtlc < r.max {
max = maxHtlc
}
// If we get in the situation that the minimum amount exceeds the
// maximum amount (enforced further down stream), we have incompatible
// channel policies.
//
// There is possibility with pubkey addressing that we should have
// selected a different channel downstream, but we don't backtrack to
// try to fix that. It would complicate path finding while we expect
// this situation to be rare. The spec recommends to keep all policies
// towards a peer identical. If that is the case, there isn't a better
// channel that we should have selected.
if amt > max {
return runningAmounts{},
fmt.Errorf("incompatible channel policies: %v "+
"exceeds %v", amt, max)
}
// Add fees to the running amounts. Skip the source node fees as
// those do not need to be paid.
if !localChan {
amt += policy.ComputeFee(amt)
max += policy.ComputeFee(max)
}
return runningAmounts{amt: amt, max: max}, nil
}
// ErrNoChannel is returned when a route cannot be built because there are no
// channels that satisfy all requirements.
type ErrNoChannel struct {
position int
fromNode route.Vertex
}
// Error returns a human readable string describing the error.
func (e ErrNoChannel) Error() string {
return fmt.Sprintf("no matching outgoing channel available for "+
"node %v (%v)", e.position, e.fromNode)
}
// BuildRoute returns a fully specified route based on a list of pubkeys. If
// amount is nil, the minimum routable amount is used. To force a specific
// outgoing channel, use the outgoingChan parameter.
func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
hops []route.Vertex, outgoingChan *uint64,
finalCltvDelta int32) (*route.Route, error) {
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v",
len(hops), amt)
// If no amount is specified, we need to build a route for the minimum
// amount that this route can carry.
useMinAmt := amt == nil
// We'll attempt to obtain a set of bandwidth hints that helps us select
// the best outgoing channel to use in case no outgoing channel is set.
bandwidthHints, err := generateBandwidthHints(
r.selfNode, r.cfg.QueryBandwidth,
)
if err != nil {
return nil, err
}
// Allocate a list that will contain the selected channels for this
// route.
edges := make([]*channeldb.ChannelEdgePolicy, len(hops))
// Keep a running amount and the maximum for this route.
amts := runningAmounts{
max: lnwire.MilliSatoshi(^uint64(0)),
}
if useMinAmt {
// For minimum amount routes, aim to deliver at least 1 msat to
// the destination. There are nodes in the wild that have a
// min_htlc channel policy of zero, which could lead to a zero
// amount payment being made.
amts.amt = 1
} else {
// If an amount is specified, we need to build a route that
// delivers exactly this amount to the final destination.
amts.amt = *amt
}
// Traverse hops backwards to accumulate fees in the running amounts.
source := r.selfNode.PubKeyBytes
for i := len(hops) - 1; i >= 0; i-- {
toNode := hops[i]
var fromNode route.Vertex
if i == 0 {
fromNode = source
} else {
fromNode = hops[i-1]
}
localChan := i == 0
// Iterate over candidate channels to select the channel
// to use for the final route.
var (
bestEdge *channeldb.ChannelEdgePolicy
bestAmts *runningAmounts
bestBandwidth lnwire.MilliSatoshi
)
cb := func(tx *bbolt.Tx,
edgeInfo *channeldb.ChannelEdgeInfo,
_, inEdge *channeldb.ChannelEdgePolicy) error {
chanID := edgeInfo.ChannelID
// Apply outgoing channel restriction is active.
if localChan && outgoingChan != nil &&
chanID != *outgoingChan {
return nil
}
// No unknown policy channels.
if inEdge == nil {
return nil
}
// Before we can process the edge, we'll need to
// fetch the node on the _other_ end of this
// channel as we may later need to iterate over
// the incoming edges of this node if we explore
// it further.
chanFromNode, err := edgeInfo.FetchOtherNode(
tx, toNode[:],
)
if err != nil {
return err
}
// Continue searching if this channel doesn't
// connect with the previous hop.
if chanFromNode.PubKeyBytes != fromNode {
return nil
}
// Validate whether this channel's policy is satisfied
// and obtain the new running amounts if this channel
// was to be selected.
newAmts, err := amts.prependChannel(
inEdge, edgeInfo.Capacity, localChan,
useMinAmt,
)
if err != nil {
log.Tracef("Skipping chan %v: %v",
inEdge.ChannelID, err)
return nil
}
// If we already have a best edge, check whether this
// edge is better.
bandwidth := bandwidthHints[chanID]
if bestEdge != nil {
if localChan {
// For local channels, better is defined
// as having more bandwidth. We try to
// maximize the chance that the returned
// route succeeds.
if bandwidth < bestBandwidth {
return nil
}
} else {
// For other channels, better is defined
// as lower fees for the amount to send.
// Normally all channels between two
// nodes should have the same policy,
// but in case not we minimize our cost
// here. Regular path finding would do
// the same.
if newAmts.amt > bestAmts.amt {
return nil
}
}
}
// If we get here, the current edge is better. Replace
// the best.
bestEdge = inEdge
bestAmts = &newAmts
bestBandwidth = bandwidth
return nil
}
err := r.cfg.Graph.ForEachNodeChannel(nil, toNode[:], cb)
if err != nil {
return nil, err
}
// There is no matching channel. Stop building the route here.
if bestEdge == nil {
return nil, ErrNoChannel{
fromNode: fromNode,
position: i,
}
}
log.Tracef("Select channel %v at position %v", bestEdge.ChannelID, i)
edges[i] = bestEdge
amts = *bestAmts
}
_, height, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
var receiverAmt lnwire.MilliSatoshi
if useMinAmt {
// We've calculated the minimum amount for the htlc that the
// source node hands out. The newRoute call below expects the
// amount that must reach the receiver after subtraction of fees
// along the way. Iterate over all edges to calculate the
// receiver amount.
receiverAmt = amts.amt
for _, edge := range edges[1:] {
receiverAmt -= edge.ComputeFeeFromIncoming(receiverAmt)
}
} else {
// Deliver the specified amount to the receiver.
receiverAmt = *amt
}
// Build and return the final route.
return newRoute(
receiverAmt, source, edges, uint32(height),
uint16(finalCltvDelta), nil,
)
}

@ -3330,3 +3330,146 @@ func TestSendToRouteStructuredError(t *testing.T) {
t.Fatalf("initPayment not called")
}
}
// TestBuildRoute tests whether correct routes are built.
func TestBuildRoute(t *testing.T) {
// Setup a three node network.
chanCapSat := btcutil.Amount(100000)
testChannels := []*testChannel{
// Create two local channels from a. The bandwidth is estimated
// in this test as the channel capacity. For building routes, we
// expected the channel with the largest estimated bandwidth to
// be selected.
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 20000,
MinHTLC: lnwire.NewMSatFromSatoshis(5),
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 1),
symmetricTestChannel("a", "b", chanCapSat/2, &testChannelPolicy{
Expiry: 144,
FeeRate: 20000,
MinHTLC: lnwire.NewMSatFromSatoshis(5),
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat / 2),
}, 6),
// Create two channels from b to c. For building routes, we
// expect the lowest cost channel to be selected. Note that this
// isn't a situation that we are expecting in reality. Routing
// nodes are recommended to keep their channel policies towards
// the same peer identical.
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 50000,
MinHTLC: lnwire.NewMSatFromSatoshis(20),
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
}, 2),
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 60000,
MinHTLC: lnwire.NewMSatFromSatoshis(20),
MaxHTLC: lnwire.NewMSatFromSatoshis(120),
}, 7),
symmetricTestChannel("a", "e", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 80000,
MinHTLC: lnwire.NewMSatFromSatoshis(5),
MaxHTLC: lnwire.NewMSatFromSatoshis(10),
}, 5),
symmetricTestChannel("e", "c", chanCapSat, &testChannelPolicy{
Expiry: 144,
FeeRate: 100000,
MinHTLC: lnwire.NewMSatFromSatoshis(20),
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
}, 4),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
defer testGraph.cleanUp()
const startingBlockHeight = 101
ctx, cleanUp, err := createTestCtxFromGraphInstance(
startingBlockHeight, testGraph,
)
if err != nil {
t.Fatalf("unable to create router: %v", err)
}
defer cleanUp()
checkHops := func(rt *route.Route, expected []uint64) {
if len(rt.Hops) != len(expected) {
t.Fatal("hop count mismatch")
}
for i, hop := range rt.Hops {
if hop.ChannelID != expected[i] {
t.Fatalf("expected channel %v at pos %v, but "+
"got channel %v",
expected[i], i, hop.ChannelID)
}
}
}
// Create hop list from the route node pubkeys.
hops := []route.Vertex{
ctx.aliases["b"], ctx.aliases["c"],
}
amt := lnwire.NewMSatFromSatoshis(100)
// Build the route for the given amount.
rt, err := ctx.router.BuildRoute(
&amt, hops, nil, 40,
)
if err != nil {
t.Fatal(err)
}
// Check that we get the expected route back. The total amount should be
// the amount to deliver to hop c (100 sats) plus the fee for hop b (5
// sats).
checkHops(rt, []uint64{1, 2})
if rt.TotalAmount != 105000 {
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
}
// Build the route for the minimum amount.
rt, err = ctx.router.BuildRoute(
nil, hops, nil, 40,
)
if err != nil {
t.Fatal(err)
}
// Check that we get the expected route back. The minimum that we can
// send from b to c is 20 sats. Hop b charges 1 sat for the forwarding.
// The channel between hop a and b can carry amounts in the range [5,
// 100], so 21 sats is the minimum amount for this route.
checkHops(rt, []uint64{1, 2})
if rt.TotalAmount != 21000 {
t.Fatalf("unexpected total amount %v", rt.TotalAmount)
}
// Test a route that contains incompatible channel htlc constraints.
// There is no amount that can pass through both channel 5 and 4.
hops = []route.Vertex{
ctx.aliases["e"], ctx.aliases["c"],
}
_, err = ctx.router.BuildRoute(
nil, hops, nil, 40,
)
errNoChannel, ok := err.(ErrNoChannel)
if !ok {
t.Fatalf("expected incompatible policies error, but got %v",
err)
}
if errNoChannel.position != 0 {
t.Fatalf("unexpected no channel error position")
}
if errNoChannel.fromNode != ctx.aliases["a"] {
t.Fatalf("unexpected no channel error node")
}
}