Merge pull request #2083 from Roasbeef/ln-router-service

rpc+lnd: add new sub RPC server, Router service
This commit is contained in:
Olaoluwa Osuntokun 2019-02-20 16:49:39 -08:00 committed by GitHub
commit e1382bd4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 986 additions and 2 deletions

@ -0,0 +1,43 @@
// +build routerrpc
package routerrpc
import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
)
// Config is the main configuration file for the router RPC server. It contains
// all the items required for the router RPC server to carry out its duties.
// The fields with struct tags are meant to be parsed as normal configuration
// options, while if able to be populated, the latter fields MUST also be
// specified.
type Config struct {
// RouterMacPath is the path for the router macaroon. If unspecified
// then we assume that the macaroon will be found under the network
// directory, named DefaultRouterMacFilename.
RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"`
// NetworkDir is the main network directory wherein the router rpc
// server will find the macaroon named DefaultRouterMacFilename.
NetworkDir string
// ActiveNetParams are the network parameters of the primary network
// that the route is operating on. This is necessary so we can ensure
// that we receive payment requests that send to destinations on our
// network.
ActiveNetParams *chaincfg.Params
// MacService is the main macaroon service that we'll use to handle
// authentication for the Router rpc server.
MacService *macaroons.Service
// Router is the main channel router instance that backs this RPC
// server.
//
// TODO(roasbeef): make into pkg lvl interface?
//
// TODO(roasbeef): assumes router handles saving payment state
Router *routing.ChannelRouter
}

@ -0,0 +1,7 @@
// +build !routerrpc
package routerrpc
// Config is the default config for the package. When the build tag isn't
// specified, then we output a blank config.
type Config struct{}

62
lnrpc/routerrpc/driver.go Normal file

@ -0,0 +1,62 @@
// +build routerrpc
package routerrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
// createNewSubServer is a helper method that will create the new router sub
// server given the main config dispatcher method. If we're unable to find the
// config that is meant for us in the config dispatcher, then we'll exit with
// an error.
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
// We'll attempt to look up the config that we expect, according to our
// subServerName name. If we can't find this, then we'll exit with an
// error, as we're unable to properly initialize ourselves without this
// config.
routeServerConf, ok := configRegistry.FetchConfig(subServerName)
if !ok {
return nil, nil, fmt.Errorf("unable to find config for "+
"subserver type %s", subServerName)
}
// Now that we've found an object mapping to our service name, we'll
// ensure that it's the type we need.
config, ok := routeServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, routeServerConf)
}
// Before we try to make the new router service instance, we'll perform
// some sanity checks on the arguments to ensure that they're useable.
switch {
case config.Router == nil:
return nil, nil, fmt.Errorf("Router must be set to create " +
"Routerpc")
}
return New(config)
}
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
return createNewSubServer(c)
},
}
// If the build tag is active, then we'll register ourselves as a
// sub-RPC server within the global lnrpc package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver '%s': %v",
subServerName, err))
}
}

45
lnrpc/routerrpc/log.go Normal file

@ -0,0 +1,45 @@
package routerrpc
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("RRPC", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// logClosure is used to provide a closure over expensive logging operations so
// don't have to be performed when the logging level doesn't warrant it.
type logClosure func() string
// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

@ -0,0 +1,441 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: routerrpc/router.proto
package routerrpc
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type PaymentRequest struct {
// *
// A serialized BOLT-11 payment request that contains all information
// required to dispatch the payment. If the pay req is invalid, or expired,
// an error will be returned.
PayReq string `protobuf:"bytes,1,opt,name=pay_req,json=payReq,proto3" json:"pay_req,omitempty"`
// *
// An absolute limit on the highest fee we should pay when looking for a route
// to the destination. Routes with fees higher than this will be ignored, if
// there are no routes with a fee below this amount, an error will be
// returned.
FeeLimitSat int64 `protobuf:"varint,2,opt,name=fee_limit_sat,json=feeLimitSat,proto3" json:"fee_limit_sat,omitempty"`
// *
// An absolute limit on the cumulative CLTV value along the route for this
// payment. Routes with total CLTV values higher than this will be ignored,
// if there are no routes with a CLTV value below this amount, an error will
// be returned.
CltvLimit int32 `protobuf:"varint,3,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
// *
// An upper limit on the amount of time we should spend when attempting to
// fulfill the payment. This is expressed in seconds. If we cannot make a
// successful payment within this time frame, an error will be returned.
TimeoutSeconds int32 `protobuf:"varint,4,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
// *
// The channel id of the channel that must be taken to the first hop. If zero,
// any channel may be used.
OutgoingChannelId int64 `protobuf:"varint,5,opt,name=outgoing_channel_id,json=outgoingChannelId,proto3" json:"outgoing_channel_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PaymentRequest) Reset() { *m = PaymentRequest{} }
func (m *PaymentRequest) String() string { return proto.CompactTextString(m) }
func (*PaymentRequest) ProtoMessage() {}
func (*PaymentRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_router_e218e7c710fe8172, []int{0}
}
func (m *PaymentRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PaymentRequest.Unmarshal(m, b)
}
func (m *PaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PaymentRequest.Marshal(b, m, deterministic)
}
func (dst *PaymentRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_PaymentRequest.Merge(dst, src)
}
func (m *PaymentRequest) XXX_Size() int {
return xxx_messageInfo_PaymentRequest.Size(m)
}
func (m *PaymentRequest) XXX_DiscardUnknown() {
xxx_messageInfo_PaymentRequest.DiscardUnknown(m)
}
var xxx_messageInfo_PaymentRequest proto.InternalMessageInfo
func (m *PaymentRequest) GetPayReq() string {
if m != nil {
return m.PayReq
}
return ""
}
func (m *PaymentRequest) GetFeeLimitSat() int64 {
if m != nil {
return m.FeeLimitSat
}
return 0
}
func (m *PaymentRequest) GetCltvLimit() int32 {
if m != nil {
return m.CltvLimit
}
return 0
}
func (m *PaymentRequest) GetTimeoutSeconds() int32 {
if m != nil {
return m.TimeoutSeconds
}
return 0
}
func (m *PaymentRequest) GetOutgoingChannelId() int64 {
if m != nil {
return m.OutgoingChannelId
}
return 0
}
type PaymentResponse struct {
// *
// The payment hash that we paid to. Provided so callers are able to map
// responses (which may be streaming) back to their original requests.
PayHash []byte `protobuf:"bytes,1,opt,name=pay_hash,json=payHash,proto3" json:"pay_hash,omitempty"`
// *
// The pre-image of the payment successfully completed.
PreImage []byte `protobuf:"bytes,2,opt,name=pre_image,json=preImage,proto3" json:"pre_image,omitempty"`
// *
// If not an empty string, then a string representation of the payment error.
PaymentErr string `protobuf:"bytes,3,opt,name=payment_err,json=paymentErr,proto3" json:"payment_err,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PaymentResponse) Reset() { *m = PaymentResponse{} }
func (m *PaymentResponse) String() string { return proto.CompactTextString(m) }
func (*PaymentResponse) ProtoMessage() {}
func (*PaymentResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_router_e218e7c710fe8172, []int{1}
}
func (m *PaymentResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PaymentResponse.Unmarshal(m, b)
}
func (m *PaymentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PaymentResponse.Marshal(b, m, deterministic)
}
func (dst *PaymentResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_PaymentResponse.Merge(dst, src)
}
func (m *PaymentResponse) XXX_Size() int {
return xxx_messageInfo_PaymentResponse.Size(m)
}
func (m *PaymentResponse) XXX_DiscardUnknown() {
xxx_messageInfo_PaymentResponse.DiscardUnknown(m)
}
var xxx_messageInfo_PaymentResponse proto.InternalMessageInfo
func (m *PaymentResponse) GetPayHash() []byte {
if m != nil {
return m.PayHash
}
return nil
}
func (m *PaymentResponse) GetPreImage() []byte {
if m != nil {
return m.PreImage
}
return nil
}
func (m *PaymentResponse) GetPaymentErr() string {
if m != nil {
return m.PaymentErr
}
return ""
}
type RouteFeeRequest struct {
// *
// The destination once wishes to obtain a routing fee quote to.
Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"`
// *
// The amount one wishes to send to the target destination.
AmtSat int64 `protobuf:"varint,2,opt,name=amt_sat,json=amtSat,proto3" json:"amt_sat,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RouteFeeRequest) Reset() { *m = RouteFeeRequest{} }
func (m *RouteFeeRequest) String() string { return proto.CompactTextString(m) }
func (*RouteFeeRequest) ProtoMessage() {}
func (*RouteFeeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_router_e218e7c710fe8172, []int{2}
}
func (m *RouteFeeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RouteFeeRequest.Unmarshal(m, b)
}
func (m *RouteFeeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RouteFeeRequest.Marshal(b, m, deterministic)
}
func (dst *RouteFeeRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RouteFeeRequest.Merge(dst, src)
}
func (m *RouteFeeRequest) XXX_Size() int {
return xxx_messageInfo_RouteFeeRequest.Size(m)
}
func (m *RouteFeeRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RouteFeeRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RouteFeeRequest proto.InternalMessageInfo
func (m *RouteFeeRequest) GetDest() []byte {
if m != nil {
return m.Dest
}
return nil
}
func (m *RouteFeeRequest) GetAmtSat() int64 {
if m != nil {
return m.AmtSat
}
return 0
}
type RouteFeeResponse struct {
// *
// A lower bound of the estimated fee to the target destination within the
// network, expressed in milli-satoshis.
RoutingFeeMsat int64 `protobuf:"varint,1,opt,name=routing_fee_msat,json=routingFeeMsat,proto3" json:"routing_fee_msat,omitempty"`
// *
// An estimate of the worst case time delay that can occur. Note that callers
// will still need to factor in the final CLTV delta of the last hop into this
// value.
TimeLockDelay int64 `protobuf:"varint,2,opt,name=time_lock_delay,json=timeLockDelay,proto3" json:"time_lock_delay,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RouteFeeResponse) Reset() { *m = RouteFeeResponse{} }
func (m *RouteFeeResponse) String() string { return proto.CompactTextString(m) }
func (*RouteFeeResponse) ProtoMessage() {}
func (*RouteFeeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_router_e218e7c710fe8172, []int{3}
}
func (m *RouteFeeResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RouteFeeResponse.Unmarshal(m, b)
}
func (m *RouteFeeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RouteFeeResponse.Marshal(b, m, deterministic)
}
func (dst *RouteFeeResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RouteFeeResponse.Merge(dst, src)
}
func (m *RouteFeeResponse) XXX_Size() int {
return xxx_messageInfo_RouteFeeResponse.Size(m)
}
func (m *RouteFeeResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RouteFeeResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RouteFeeResponse proto.InternalMessageInfo
func (m *RouteFeeResponse) GetRoutingFeeMsat() int64 {
if m != nil {
return m.RoutingFeeMsat
}
return 0
}
func (m *RouteFeeResponse) GetTimeLockDelay() int64 {
if m != nil {
return m.TimeLockDelay
}
return 0
}
func init() {
proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest")
proto.RegisterType((*PaymentResponse)(nil), "routerrpc.PaymentResponse")
proto.RegisterType((*RouteFeeRequest)(nil), "routerrpc.RouteFeeRequest")
proto.RegisterType((*RouteFeeResponse)(nil), "routerrpc.RouteFeeResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// RouterClient is the client API for Router service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RouterClient interface {
// *
// SendPayment attempts to route a payment described by the passed
// PaymentRequest to the final destination. If we are unable to route the
// payment, or cannot find a route that satisfies the constraints in the
// PaymentRequest, then an error will be returned. Otherwise, the payment
// pre-image, along with the final route will be returned.
SendPayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error)
// *
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
// may cost to send an HTLC to the target end destination.
EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error)
}
type routerClient struct {
cc *grpc.ClientConn
}
func NewRouterClient(cc *grpc.ClientConn) RouterClient {
return &routerClient{cc}
}
func (c *routerClient) SendPayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) {
out := new(PaymentResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/SendPayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error) {
out := new(RouteFeeResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/EstimateRouteFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RouterServer is the server API for Router service.
type RouterServer interface {
// *
// SendPayment attempts to route a payment described by the passed
// PaymentRequest to the final destination. If we are unable to route the
// payment, or cannot find a route that satisfies the constraints in the
// PaymentRequest, then an error will be returned. Otherwise, the payment
// pre-image, along with the final route will be returned.
SendPayment(context.Context, *PaymentRequest) (*PaymentResponse, error)
// *
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
// may cost to send an HTLC to the target end destination.
EstimateRouteFee(context.Context, *RouteFeeRequest) (*RouteFeeResponse, error)
}
func RegisterRouterServer(s *grpc.Server, srv RouterServer) {
s.RegisterService(&_Router_serviceDesc, srv)
}
func _Router_SendPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).SendPayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/SendPayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).SendPayment(ctx, req.(*PaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_EstimateRouteFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RouteFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).EstimateRouteFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/EstimateRouteFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).EstimateRouteFee(ctx, req.(*RouteFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Router_serviceDesc = grpc.ServiceDesc{
ServiceName: "routerrpc.Router",
HandlerType: (*RouterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SendPayment",
Handler: _Router_SendPayment_Handler,
},
{
MethodName: "EstimateRouteFee",
Handler: _Router_EstimateRouteFee_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "routerrpc/router.proto",
}
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_router_e218e7c710fe8172) }
var fileDescriptor_router_e218e7c710fe8172 = []byte{
// 409 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xdf, 0x6e, 0xd3, 0x30,
0x14, 0xc6, 0x15, 0xb6, 0x85, 0xe5, 0x74, 0x6b, 0x8b, 0x91, 0x20, 0xeb, 0x84, 0xa8, 0x72, 0x01,
0xb9, 0x0a, 0x12, 0xdc, 0x73, 0xc3, 0x36, 0x31, 0x31, 0x24, 0xe4, 0x3e, 0x80, 0x65, 0x92, 0xb3,
0x26, 0x34, 0x8e, 0x5d, 0xdb, 0x41, 0xca, 0xb3, 0xf0, 0x3c, 0xbc, 0x17, 0xb2, 0xe3, 0x96, 0x82,
0x7a, 0xe7, 0x7c, 0xe7, 0xef, 0xf7, 0x3b, 0x81, 0x17, 0x5a, 0xf6, 0x16, 0xb5, 0x56, 0xe5, 0xbb,
0xf1, 0x55, 0x28, 0x2d, 0xad, 0x24, 0xc9, 0x5e, 0xcf, 0x7e, 0x47, 0x30, 0xfd, 0xc6, 0x07, 0x81,
0x9d, 0xa5, 0xb8, 0xed, 0xd1, 0x58, 0xf2, 0x12, 0x9e, 0x2a, 0x3e, 0x30, 0x8d, 0xdb, 0x34, 0x5a,
0x46, 0x79, 0x42, 0x63, 0xc5, 0x07, 0x8a, 0x5b, 0x92, 0xc1, 0xe5, 0x23, 0x22, 0x6b, 0x1b, 0xd1,
0x58, 0x66, 0xb8, 0x4d, 0x9f, 0x2c, 0xa3, 0xfc, 0x84, 0x4e, 0x1e, 0x11, 0x1f, 0x9c, 0xb6, 0xe2,
0x96, 0xbc, 0x02, 0x28, 0x5b, 0xfb, 0x73, 0x4c, 0x4a, 0x4f, 0x96, 0x51, 0x7e, 0x46, 0x13, 0xa7,
0xf8, 0x0c, 0xf2, 0x16, 0x66, 0xb6, 0x11, 0x28, 0x7b, 0xcb, 0x0c, 0x96, 0xb2, 0xab, 0x4c, 0x7a,
0xea, 0x73, 0xa6, 0x41, 0x5e, 0x8d, 0x2a, 0x29, 0xe0, 0xb9, 0xec, 0xed, 0x5a, 0x36, 0xdd, 0x9a,
0x95, 0x35, 0xef, 0x3a, 0x6c, 0x59, 0x53, 0xa5, 0x67, 0x7e, 0xe2, 0xb3, 0x5d, 0xe8, 0xd3, 0x18,
0xb9, 0xaf, 0xb2, 0x1f, 0x30, 0xdb, 0xdb, 0x30, 0x4a, 0x76, 0x06, 0xc9, 0x15, 0x9c, 0x3b, 0x1f,
0x35, 0x37, 0xb5, 0x37, 0x72, 0x41, 0x9d, 0xaf, 0xcf, 0xdc, 0xd4, 0xe4, 0x1a, 0x12, 0xa5, 0x91,
0x35, 0x82, 0xaf, 0xd1, 0xbb, 0xb8, 0xa0, 0xe7, 0x4a, 0xe3, 0xbd, 0xfb, 0x26, 0xaf, 0x61, 0xa2,
0xc6, 0x56, 0x0c, 0xb5, 0xf6, 0x1e, 0x12, 0x0a, 0x41, 0xba, 0xd5, 0x3a, 0xfb, 0x08, 0x33, 0xea,
0x00, 0xde, 0x21, 0xee, 0x98, 0x11, 0x38, 0xad, 0xd0, 0xd8, 0x30, 0xc7, 0xbf, 0x1d, 0x47, 0x2e,
0x0e, 0x41, 0xc5, 0x5c, 0x38, 0x46, 0x59, 0x05, 0xf3, 0xbf, 0xf5, 0x61, 0xd9, 0x1c, 0xe6, 0xee,
0x28, 0xce, 0xae, 0x63, 0x2c, 0x5c, 0x55, 0xe4, 0xab, 0xa6, 0x41, 0xbf, 0x43, 0xfc, 0x6a, 0xb8,
0x25, 0x6f, 0x46, 0x84, 0xac, 0x95, 0xe5, 0x86, 0x55, 0xd8, 0xf2, 0x21, 0xb4, 0xbf, 0x74, 0xf2,
0x83, 0x2c, 0x37, 0x37, 0x4e, 0x7c, 0xff, 0x2b, 0x82, 0xd8, 0x8f, 0xd1, 0xe4, 0x06, 0x26, 0x2b,
0xec, 0xaa, 0x00, 0x88, 0x5c, 0x15, 0xfb, 0xfb, 0x17, 0xff, 0xde, 0x7e, 0xb1, 0x38, 0x16, 0x0a,
0x2b, 0x7e, 0x81, 0xf9, 0xad, 0xb1, 0x8d, 0xe0, 0x16, 0x77, 0xeb, 0x93, 0xc3, 0xfc, 0xff, 0x98,
0x2c, 0xae, 0x8f, 0xc6, 0xc6, 0x66, 0xdf, 0x63, 0xff, 0x27, 0x7e, 0xf8, 0x13, 0x00, 0x00, 0xff,
0xff, 0x95, 0xc2, 0xf8, 0xec, 0xa3, 0x02, 0x00, 0x00,
}

@ -0,0 +1,103 @@
syntax = "proto3";
package routerrpc;
message PaymentRequest {
/**
A serialized BOLT-11 payment request that contains all information
required to dispatch the payment. If the pay req is invalid, or expired,
an error will be returned.
*/
string pay_req = 1;
/**
An absolute limit on the highest fee we should pay when looking for a route
to the destination. Routes with fees higher than this will be ignored, if
there are no routes with a fee below this amount, an error will be
returned.
*/
int64 fee_limit_sat = 2;
/**
An absolute limit on the cumulative CLTV value along the route for this
payment. Routes with total CLTV values higher than this will be ignored,
if there are no routes with a CLTV value below this amount, an error will
be returned.
*/
int32 cltv_limit = 3;
/**
An upper limit on the amount of time we should spend when attempting to
fulfill the payment. This is expressed in seconds. If we cannot make a
successful payment within this time frame, an error will be returned.
*/
int32 timeout_seconds = 4;
/**
The channel id of the channel that must be taken to the first hop. If zero,
any channel may be used.
*/
int64 outgoing_channel_id = 5;
}
message PaymentResponse {
/**
The payment hash that we paid to. Provided so callers are able to map
responses (which may be streaming) back to their original requests.
*/
bytes pay_hash = 1;
/**
The pre-image of the payment successfully completed.
*/
bytes pre_image = 2;
/**
If not an empty string, then a string representation of the payment error.
*/
string payment_err = 3;
}
message RouteFeeRequest {
/**
The destination once wishes to obtain a routing fee quote to.
*/
bytes dest = 1;
/**
The amount one wishes to send to the target destination.
*/
int64 amt_sat = 2;
}
message RouteFeeResponse {
/**
A lower bound of the estimated fee to the target destination within the
network, expressed in milli-satoshis.
*/
int64 routing_fee_msat = 1;
/**
An estimate of the worst case time delay that can occur. Note that callers
will still need to factor in the final CLTV delta of the last hop into this
value.
*/
int64 time_lock_delay = 2;
}
service Router {
/**
SendPayment attempts to route a payment described by the passed
PaymentRequest to the final destination. If we are unable to route the
payment, or cannot find a route that satisfies the constraints in the
PaymentRequest, then an error will be returned. Otherwise, the payment
pre-image, along with the final route will be returned.
*/
rpc SendPayment(PaymentRequest) returns (PaymentResponse);
/**
EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
may cost to send an HTLC to the target end destination.
*/
rpc EstimateRouteFee(RouteFeeRequest) returns (RouteFeeResponse);
}

@ -0,0 +1,258 @@
// +build routerrpc
package routerrpc
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// subServerName is the name of the sub rpc server. We'll use this name
// to register ourselves, and we also require that the main
// SubServerConfigDispatcher instance recognize as the name of our
subServerName = "RouterRPC"
)
var (
// macaroonOps are the set of capabilities that our minted macaroon (if
// it doesn't already exist) will have.
macaroonOps = []bakery.Op{
{
Entity: "offchain",
Action: "write",
},
}
// macPermissions maps RPC calls to the permissions they require.
macPermissions = map[string][]bakery.Op{
"/routerpc.Router/SendPayment": {{
Entity: "offchain",
Action: "write",
}},
"/routerpc.Router/EstimateRouteFee": {{
Entity: "offchain",
Action: "read",
}},
}
// DefaultRouterMacFilename is the default name of the router macaroon
// that we expect to find via a file handle within the main
// configuration file in this package.
DefaultRouterMacFilename = "router.macaroon"
)
// Server is a stand alone sub RPC server which exposes functionality that
// allows clients to route arbitrary payment through the Lightning Network.
type Server struct {
cfg *Config
}
// A compile time check to ensure that Server fully implements the RouterServer
// gRPC service.
var _ RouterServer = (*Server)(nil)
// fileExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// New creates a new instance of the RouterServer given a configuration struct
// that contains all external dependencies. If the target macaroon exists, and
// we're unable to create it, then an error will be returned. We also return
// the set of permissions that we require as a server. At the time of writing
// of this documentation, this is the same macaroon as as the admin macaroon.
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
// If the path of the router macaroon wasn't generated, then we'll
// assume that it's found at the default network directory.
if cfg.RouterMacPath == "" {
cfg.RouterMacPath = filepath.Join(
cfg.NetworkDir, DefaultRouterMacFilename,
)
}
// Now that we know the full path of the router macaroon, we can check
// to see if we need to create it or not.
macFilePath := cfg.RouterMacPath
if !fileExists(macFilePath) && cfg.MacService != nil {
log.Infof("Making macaroons for Router RPC Server at: %v",
macFilePath)
// At this point, we know that the router macaroon doesn't yet,
// exist, so we need to create it with the help of the main
// macaroon service.
routerMac, err := cfg.MacService.Oven.NewMacaroon(
context.Background(), bakery.LatestVersion, nil,
macaroonOps...,
)
if err != nil {
return nil, nil, err
}
routerMacBytes, err := routerMac.M().MarshalBinary()
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(macFilePath, routerMacBytes, 0644)
if err != nil {
os.Remove(macFilePath)
return nil, nil, err
}
}
routerServer := &Server{
cfg: cfg,
}
return routerServer, macPermissions, nil
}
// Start launches any helper goroutines required for the rpcServer to function.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Start() error {
return nil
}
// Stop signals any active goroutines for a graceful closure.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Stop() error {
return nil
}
// Name returns a unique string representation of the sub-server. This can be
// used to identify the sub-server and also de-duplicate them.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Name() string {
return subServerName
}
// RegisterWithRootServer will be called by the root gRPC server to direct a
// sub RPC server to register itself with the main gRPC root server. Until this
// is called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
// We make sure that we register it with the main gRPC server to ensure
// all our methods are routed properly.
RegisterRouterServer(grpcServer, s)
log.Debugf("Router RPC server successfully register with root gRPC " +
"server")
return nil
}
// SendPayment attempts to route a payment described by the passed
// PaymentRequest to the final destination. If we are unable to route the
// payment, or cannot find a route that satisfies the constraints in the
// PaymentRequest, then an error will be returned. Otherwise, the payment
// pre-image, along with the final route will be returned.
func (s *Server) SendPayment(ctx context.Context,
req *PaymentRequest) (*PaymentResponse, error) {
switch {
// If the payment request isn't populated, then we won't be able to
// even attempt a payment.
case req.PayReq == "":
return nil, fmt.Errorf("a valid payment request MUST be specified")
}
// Now that we know the payment request is present, we'll attempt to
// decode it in order to parse out all the parameters for the route.
payReq, err := zpay32.Decode(req.PayReq, s.cfg.ActiveNetParams)
if err != nil {
return nil, err
}
// Atm, this service does not support invoices that don't have their
// value fully specified.
if payReq.MilliSat == nil {
return nil, fmt.Errorf("zero value invoices are not supported")
}
// Now that all the information we need has been parsed, we'll map this
// proto request into a proper request that our backing router can
// understand.
finalDelta := uint16(payReq.MinFinalCLTVExpiry())
payment := routing.LightningPayment{
Target: payReq.Destination,
Amount: *payReq.MilliSat,
FeeLimit: lnwire.MilliSatoshi(req.FeeLimitSat),
PaymentHash: *payReq.PaymentHash,
FinalCLTVDelta: &finalDelta,
PayAttemptTimeout: time.Second * time.Duration(req.TimeoutSeconds),
RouteHints: payReq.RouteHints,
}
// Pin to an outgoing channel if specified.
if req.OutgoingChannelId != 0 {
chanID := uint64(req.OutgoingChannelId)
payment.OutgoingChannelID = &chanID
}
preImage, _, err := s.cfg.Router.SendPayment(&payment)
if err != nil {
return nil, err
}
return &PaymentResponse{
PayHash: (*payReq.PaymentHash)[:],
PreImage: preImage[:],
}, nil
}
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
// may cost to send an HTLC to the target end destination.
func (s *Server) EstimateRouteFee(ctx context.Context,
req *RouteFeeRequest) (*RouteFeeResponse, error) {
// First we'll parse out the raw public key into a value that we can
// utilize.
destNode, err := btcec.ParsePubKey(req.Dest, btcec.S256())
if err != nil {
return nil, err
}
// Next, we'll convert the amount in satoshis to mSAT, which are the
// native unit of LN.
amtMsat := lnwire.NewMSatFromSatoshis(btcutil.Amount(req.AmtSat))
// Finally, we'll query for a route to the destination that can carry
// that target amount, we'll only request a single route.
routes, err := s.cfg.Router.FindRoutes(
destNode, amtMsat,
lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin), 1,
)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, fmt.Errorf("unable to find route to dest: %v", err)
}
return &RouteFeeResponse{
RoutingFeeMsat: int64(routes[0].TotalFees),
TimeLockDelay: int64(routes[0].TotalTimeLock),
}, nil
}

@ -416,7 +416,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
// server configuration struct.
err := subServerCgs.PopulateDependencies(
s.cc, networkDir, macService, atpl, invoiceRegistry,
activeNetParams.Params,
activeNetParams.Params, s.chanRouter,
)
if err != nil {
return nil, err

@ -10,9 +10,11 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
)
// subRPCServerConfigs is special sub-config in the main configuration that
@ -44,6 +46,12 @@ type subRPCServerConfigs struct {
// InvoicesRPC is a sub-RPC server that exposes invoice related methods
// as a gRPC service.
InvoicesRPC *invoicesrpc.Config `group:"invoicesrpc" namespace:"invoicesrpc"`
// RouterRPC is a sub-RPC server the exposes functionality that allows
// clients to send payments on the network, and perform Lightning
// payment related queries such as requests for estimates of off-chain
// fees.
RouterRPC *routerrpc.Config `group:"routerrpc" namespace:"routerrpc"`
}
// PopulateDependencies attempts to iterate through all the sub-server configs
@ -56,7 +64,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
networkDir string, macService *macaroons.Service,
atpl *autopilot.Manager,
invoiceRegistry *invoices.InvoiceRegistry,
activeNetParams *chaincfg.Params) error {
activeNetParams *chaincfg.Params,
chanRouter *routing.ChannelRouter) error {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
@ -150,6 +159,22 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
reflect.ValueOf(activeNetParams),
)
case *routerrpc.Config:
subCfgValue := extractReflectValue(cfg)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("ActiveNetParams").Set(
reflect.ValueOf(activeNetParams),
)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("Router").Set(
reflect.ValueOf(chanRouter),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,
cfg)