mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
faraday+frdrpcserver: move server infrastructure to faraday package
Move the gRPC/REST server lifecycle, macaroon service management, and TLS setup from frdrpcserver.RPCServer to a new Faraday struct in the faraday package. This leaves RPCServer as a pure RPC request handler with only business logic methods. The Faraday struct embeds *frdrpcserver.RPCServer and takes ownership of all infrastructure concerns: gRPC/REST server creation, macaroon service setup, TLS configuration, and start/stop lifecycle. The frdrpcserver.Config is slimmed down to just Lnd and BitcoinClient. The macaroons.go file is also moved from frdrpcserver to the faraday package, as the macaroon constants are now used by the Faraday struct directly.
This commit is contained in:
parent
0170cc37fc
commit
d98e8ef59e
3 changed files with 412 additions and 405 deletions
428
faraday.go
428
faraday.go
|
|
@ -2,18 +2,60 @@
|
|||
package faraday
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/lightninglabs/faraday/chain"
|
||||
"github.com/lightninglabs/faraday/frdrpc"
|
||||
"github.com/lightninglabs/faraday/frdrpcserver"
|
||||
"github.com/lightninglabs/faraday/frdrpcserver/perms"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
var (
|
||||
// customMarshalerOption is the configuration we use for the JSON
|
||||
// marshaler of the REST proxy. The default JSON marshaler only sets
|
||||
// OrigName to true, which instructs it to use the same field names as
|
||||
// specified in the proto file and not switch to camel case. What we
|
||||
// also want is that the marshaler prints all values, even if they are
|
||||
// falsey.
|
||||
customMarshalerOption = proxy.WithMarshalerOption(
|
||||
proxy.MIMEWildcard, &proxy.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitUnpopulated: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// maxMsgRecvSize is the largest message our REST proxy will receive. We
|
||||
// set this to 600MiB atm.
|
||||
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(600 * 1024 * 1024)
|
||||
|
||||
// errServerAlreadyStarted is the error that is returned if the server
|
||||
// is requested to start while it's already been started.
|
||||
errServerAlreadyStarted = fmt.Errorf("server can only be started once")
|
||||
)
|
||||
|
||||
// MinLndVersion is the minimum lnd version required. Note that apis that are
|
||||
|
|
@ -26,6 +68,366 @@ var MinLndVersion = &verrpc.Version{
|
|||
AppPatch: 4,
|
||||
}
|
||||
|
||||
// Faraday is a struct that houses the faraday daemon and its dependencies.
|
||||
type Faraday struct {
|
||||
*frdrpcserver.RPCServer
|
||||
|
||||
// cfg is the faraday config.
|
||||
cfg *Config
|
||||
|
||||
// To be used atomically.
|
||||
started int32
|
||||
|
||||
// To be used atomically.
|
||||
stopped int32
|
||||
|
||||
lnd *lndclient.GrpcLndServices
|
||||
|
||||
// bitcoinClient is set if the client opted to connect to a bitcoin
|
||||
// backend, if not, it will be nil.
|
||||
bitcoinClient chain.BitcoinClient
|
||||
|
||||
macaroonService *lndclient.MacaroonService
|
||||
macaroonDB kvdb.Backend
|
||||
|
||||
// grpcServer is the main gRPC server that this service will register
|
||||
// itself with and accept client requests from.
|
||||
grpcServer *grpc.Server
|
||||
|
||||
// rpcListener is the listener to use when starting the gRPC server.
|
||||
rpcListener net.Listener
|
||||
|
||||
// restServer is the REST proxy server.
|
||||
restServer *http.Server
|
||||
restCancel func()
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New creates a new Faraday instance with the given configuration.
|
||||
func New(cfg *Config) *Faraday {
|
||||
return &Faraday{cfg: cfg}
|
||||
}
|
||||
|
||||
// Start starts the listener and server.
|
||||
func (f *Faraday) Start() error {
|
||||
if atomic.AddInt32(&f.started, 1) != 1 {
|
||||
return errServerAlreadyStarted
|
||||
}
|
||||
|
||||
cfg := &frdrpcserver.Config{
|
||||
Lnd: f.lnd.LndServices,
|
||||
BitcoinClient: f.bitcoinClient,
|
||||
}
|
||||
|
||||
// Create the RPC server.
|
||||
f.RPCServer = frdrpcserver.NewRPCServer(cfg)
|
||||
|
||||
// Prepare the RPC server.
|
||||
serverTLSCfg, restClientCreds, err := getTLSConfig(f.cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading TLS config: %v", err)
|
||||
}
|
||||
|
||||
// Depending on how far we got in initializing the server, we might need
|
||||
// to clean up certain services that were already started. Keep track of
|
||||
// them with this map of service name to shutdown function.
|
||||
shutdownFuncs := make(map[string]func() error)
|
||||
defer func() {
|
||||
for serviceName, shutdownFn := range shutdownFuncs {
|
||||
if err := shutdownFn(); err != nil {
|
||||
log.Errorf("Error shutting down %s service: %v",
|
||||
serviceName, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set up the macaroon service.
|
||||
rks, db, err := lndclient.NewBoltMacaroonStore(
|
||||
f.cfg.FaradayDir, lncfg.MacaroonDBName, macDatabaseOpenTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shutdownFuncs["macaroondb"] = db.Close
|
||||
|
||||
f.macaroonDB = db
|
||||
f.macaroonService, err = lndclient.NewMacaroonService(
|
||||
&lndclient.MacaroonServiceConfig{
|
||||
RootKeyStore: rks,
|
||||
MacaroonLocation: faradayMacaroonLocation,
|
||||
MacaroonPath: f.cfg.MacaroonPath,
|
||||
Checkers: []macaroons.Checker{
|
||||
macaroons.IPLockChecker,
|
||||
},
|
||||
RequiredPerms: perms.RequiredPermissions,
|
||||
DBPassword: macDbDefaultPw,
|
||||
LndClient: &f.lnd.LndServices,
|
||||
EphemeralKey: lndclient.SharedKeyNUMS,
|
||||
KeyLocator: lndclient.SharedKeyLocator,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating macaroon service: %v", err)
|
||||
}
|
||||
|
||||
// Start the macaroon service and let it create its default macaroon in
|
||||
// case it doesn't exist yet.
|
||||
if err := f.macaroonService.Start(); err != nil {
|
||||
return fmt.Errorf("error starting macaroon service: %v", err)
|
||||
}
|
||||
shutdownFuncs["macaroon"] = f.macaroonService.Stop
|
||||
|
||||
// First we add the security interceptor to our gRPC server options that
|
||||
// checks the macaroons for validity.
|
||||
unaryInterceptor, streamInterceptor, err :=
|
||||
f.macaroonService.Interceptors()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with macaroon interceptor: %v", err)
|
||||
}
|
||||
|
||||
// Add our TLS configuration and then create our server instance. It's
|
||||
// important that we let gRPC create the TLS listener and we don't just
|
||||
// use tls.NewListener(). Otherwise we run into the ALPN error with non-
|
||||
// golang clients.
|
||||
tlsCredentials := credentials.NewTLS(serverTLSCfg)
|
||||
f.grpcServer = grpc.NewServer(
|
||||
grpc.UnaryInterceptor(unaryInterceptor),
|
||||
grpc.StreamInterceptor(streamInterceptor),
|
||||
grpc.Creds(tlsCredentials),
|
||||
)
|
||||
|
||||
// Start the gRPC RPCServer listening for HTTP/2 connections.
|
||||
log.Info("Starting gRPC listener")
|
||||
f.rpcListener, err = net.Listen("tcp", f.cfg.RPCListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gRPC server unable to listen on %v",
|
||||
f.cfg.RPCListen)
|
||||
}
|
||||
shutdownFuncs["gRPC listener"] = f.rpcListener.Close
|
||||
log.Infof("gRPC server listening on %s", f.rpcListener.Addr())
|
||||
|
||||
frdrpc.RegisterFaradayServerServer(f.grpcServer, f)
|
||||
|
||||
// We'll also create and start an accompanying proxy to serve clients
|
||||
// through REST. An empty address indicates REST is disabled.
|
||||
if f.cfg.RESTListen != "" {
|
||||
log.Infof("Starting REST proxy listener ")
|
||||
restListener, err := net.Listen("tcp", f.cfg.RESTListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("REST server unable to listen on "+
|
||||
"%v: %v", f.cfg.RESTListen, err)
|
||||
}
|
||||
restListener = tls.NewListener(
|
||||
restListener, serverTLSCfg,
|
||||
)
|
||||
shutdownFuncs["REST listener"] = restListener.Close
|
||||
log.Infof("REST server listening on %s", restListener.Addr())
|
||||
|
||||
// We'll dial into the local gRPC server so we need to set some
|
||||
// gRPC dial options and CORS settings.
|
||||
var restCtx context.Context
|
||||
restCtx, f.restCancel = context.WithCancel(context.Background())
|
||||
mux := proxy.NewServeMux(customMarshalerOption)
|
||||
var restHandler http.Handler = mux
|
||||
if f.cfg.CORSOrigin != "" {
|
||||
restHandler = allowCORS(restHandler, f.cfg.CORSOrigin)
|
||||
}
|
||||
proxyOpts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(*restClientCreds),
|
||||
grpc.WithDefaultCallOptions(maxMsgRecvSize),
|
||||
}
|
||||
|
||||
// With TLS enabled by default, we cannot call 0.0.0.0
|
||||
// internally from the REST proxy as that IP address isn't in
|
||||
// the cert. We need to rewrite it to the loopback address.
|
||||
restProxyDest := f.cfg.RPCListen
|
||||
switch {
|
||||
case strings.Contains(restProxyDest, "0.0.0.0"):
|
||||
restProxyDest = strings.Replace(
|
||||
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
|
||||
)
|
||||
|
||||
case strings.Contains(restProxyDest, "[::]"):
|
||||
restProxyDest = strings.Replace(
|
||||
restProxyDest, "[::]", "[::1]", 1,
|
||||
)
|
||||
}
|
||||
err = frdrpc.RegisterFaradayServerHandlerFromEndpoint(
|
||||
restCtx, mux, restProxyDest, proxyOpts,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.restServer = &http.Server{
|
||||
Handler: restHandler,
|
||||
ReadHeaderTimeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
defer f.wg.Done()
|
||||
err := f.restServer.Serve(restListener)
|
||||
// ErrServerClosed is always returned when the proxy is
|
||||
// shut down, so don't log it.
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
log.Infof("REST proxy disabled")
|
||||
}
|
||||
|
||||
f.wg.Add(1)
|
||||
go func() {
|
||||
defer f.wg.Done()
|
||||
if err := f.grpcServer.Serve(f.rpcListener); err != nil {
|
||||
log.Errorf("could not serve grpc server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// If we got here successfully, there's no need to shutdown anything
|
||||
// anymore.
|
||||
shutdownFuncs = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartAsSubserver is an alternative to Start where the RPC server does not
|
||||
// create its own gRPC server but registers to an existing one. The same goes
|
||||
// for REST (if enabled), instead of creating an own mux and HTTP server, we
|
||||
// register to an existing one.
|
||||
func (f *Faraday) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
|
||||
withMacaroonService bool) error {
|
||||
|
||||
log.Infof("Starting Faraday subserver version %s", Version())
|
||||
|
||||
// There should be no reason to start the daemon twice. Therefore,
|
||||
// return an error if that's tried. This is mostly to guard against
|
||||
// Start and StartAsSubserver both being called.
|
||||
if atomic.AddInt32(&f.started, 1) != 1 {
|
||||
return errServerAlreadyStarted
|
||||
}
|
||||
|
||||
// When starting as a subserver, we get passed in an already established
|
||||
// connection to lnd that might be shared among other subservers.
|
||||
f.lnd = lndGrpc
|
||||
|
||||
if withMacaroonService {
|
||||
// Set up the macaroon service.
|
||||
rks, db, err := lndclient.NewBoltMacaroonStore(
|
||||
f.cfg.FaradayDir, lncfg.MacaroonDBName,
|
||||
macDatabaseOpenTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.macaroonDB = db
|
||||
f.macaroonService, err = lndclient.NewMacaroonService(
|
||||
&lndclient.MacaroonServiceConfig{
|
||||
RootKeyStore: rks,
|
||||
MacaroonLocation: faradayMacaroonLocation,
|
||||
MacaroonPath: f.cfg.MacaroonPath,
|
||||
Checkers: []macaroons.Checker{
|
||||
macaroons.IPLockChecker,
|
||||
},
|
||||
RequiredPerms: perms.RequiredPermissions,
|
||||
DBPassword: macDbDefaultPw,
|
||||
LndClient: &lndGrpc.LndServices,
|
||||
EphemeralKey: lndclient.SharedKeyNUMS,
|
||||
KeyLocator: lndclient.SharedKeyLocator,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating macaroon service: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Start the macaroon service and let it create its default
|
||||
// macaroon in case it doesn't exist yet.
|
||||
if err := f.macaroonService.Start(); err != nil {
|
||||
return fmt.Errorf("error starting macaroon service: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &frdrpcserver.Config{
|
||||
Lnd: lndGrpc.LndServices,
|
||||
BitcoinClient: f.bitcoinClient,
|
||||
}
|
||||
|
||||
// Create the RPC server.
|
||||
f.RPCServer = frdrpcserver.NewRPCServer(cfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
|
||||
// checks its signature, makes sure all specified permissions for the called
|
||||
// method are contained within and finally ensures all caveat conditions are
|
||||
// met. A non-nil error is returned if any of the checks fail. This method is
|
||||
// needed to enable faraday running as an external subserver in the same process
|
||||
// as lnd but still validate its own macaroons.
|
||||
func (f *Faraday) ValidateMacaroon(ctx context.Context,
|
||||
requiredPermissions []bakery.Op, fullMethod string) error {
|
||||
|
||||
if f.macaroonService == nil {
|
||||
return fmt.Errorf("macaroon service not yet initialised")
|
||||
}
|
||||
|
||||
// Delegate the call to faraday's own macaroon validator service.
|
||||
return f.macaroonService.ValidateMacaroon(
|
||||
ctx, requiredPermissions, fullMethod,
|
||||
)
|
||||
}
|
||||
|
||||
// Stop stops the grpc listener and server.
|
||||
func (f *Faraday) Stop() error {
|
||||
if atomic.AddInt32(&f.stopped, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.restServer != nil {
|
||||
f.restCancel()
|
||||
err := f.restServer.Close()
|
||||
if err != nil {
|
||||
log.Errorf("unable to close REST listener: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if f.macaroonService != nil {
|
||||
if err := f.macaroonService.Stop(); err != nil {
|
||||
log.Errorf("Error stopping macaroon service: %v", err)
|
||||
}
|
||||
}
|
||||
if f.macaroonDB != nil {
|
||||
if err := f.macaroonDB.Close(); err != nil {
|
||||
log.Errorf("Error closing macaroon DB: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the grpc server and wait for all go routines to terminate.
|
||||
if f.grpcServer != nil {
|
||||
f.grpcServer.Stop()
|
||||
}
|
||||
f.wg.Wait()
|
||||
|
||||
f.lnd.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// allowCORS wraps the given http.Handler with a function that adds the
|
||||
// Access-Control-Allow-Origin header to the response.
|
||||
func allowCORS(handler http.Handler, origin string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Main is the real entry point for faraday. It is required to ensure that
|
||||
// defers are properly executed when os.Exit() is called.
|
||||
func Main() error {
|
||||
|
|
@ -66,14 +468,11 @@ func Main() error {
|
|||
return fmt.Errorf("error validating config: %v", err)
|
||||
}
|
||||
|
||||
serverTLSCfg, restClientCreds, err := getTLSConfig(&config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading TLS config: %v", err)
|
||||
}
|
||||
server := New(&config)
|
||||
|
||||
// Connect to the full suite of lightning services offered by lnd's
|
||||
// subservers.
|
||||
client, err := lndclient.NewLndServices(&lndclient.LndServicesConfig{
|
||||
server.lnd, err = lndclient.NewLndServices(&lndclient.LndServicesConfig{
|
||||
LndAddress: config.Lnd.RPCServer,
|
||||
Network: lndclient.Network(config.Network),
|
||||
CustomMacaroonPath: config.Lnd.MacaroonPath,
|
||||
|
|
@ -85,30 +484,17 @@ func Main() error {
|
|||
return fmt.Errorf("cannot connect to lightning services: %v",
|
||||
err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Instantiate the faraday gRPC server.
|
||||
cfg := &frdrpcserver.Config{
|
||||
Lnd: client.LndServices,
|
||||
RPCListen: config.RPCListen,
|
||||
RESTListen: config.RESTListen,
|
||||
CORSOrigin: config.CORSOrigin,
|
||||
TLSServerConfig: serverTLSCfg,
|
||||
RestClientConfig: restClientCreds,
|
||||
FaradayDir: config.FaradayDir,
|
||||
MacaroonPath: config.MacaroonPath,
|
||||
}
|
||||
|
||||
// If the client chose to connect to a bitcoin client, get one now.
|
||||
if config.ChainConn {
|
||||
cfg.BitcoinClient, err = chain.NewBitcoinClient(config.Bitcoin)
|
||||
server.bitcoinClient, err = chain.NewBitcoinClient(
|
||||
config.Bitcoin,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
server := frdrpcserver.NewRPCServer(cfg)
|
||||
|
||||
// Start the server.
|
||||
if err := server.Start(); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -11,55 +11,19 @@ package frdrpcserver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/lightninglabs/faraday/accounting"
|
||||
"github.com/lightninglabs/faraday/chain"
|
||||
"github.com/lightninglabs/faraday/fiat"
|
||||
"github.com/lightninglabs/faraday/frdrpc"
|
||||
"github.com/lightninglabs/faraday/frdrpcserver/perms"
|
||||
"github.com/lightninglabs/faraday/recommend"
|
||||
"github.com/lightninglabs/faraday/resolutions"
|
||||
"github.com/lightninglabs/faraday/revenue"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
var (
|
||||
// customMarshalerOption is the configuratino we use for the JSON
|
||||
// marshaler of the REST proxy. The default JSON marshaler only sets
|
||||
// OrigName to true, which instructs it to use the same field names as
|
||||
// specified in the proto file and not switch to camel case. What we
|
||||
// also want is that the marshaler prints all values, even if they are
|
||||
// falsey.
|
||||
customMarshalerOption = proxy.WithMarshalerOption(
|
||||
proxy.MIMEWildcard, &proxy.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitUnpopulated: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// maxMsgRecvSize is the largest message our REST proxy will receive. We
|
||||
// set this to 600MiB atm.
|
||||
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(600 * 1024 * 1024)
|
||||
|
||||
// maxInvoiceQueries is the maximum number of invoices we request from
|
||||
// lnd at a time.
|
||||
maxInvoiceQueries = 1000
|
||||
|
|
@ -74,10 +38,6 @@ var (
|
|||
// because forwards have less data.
|
||||
maxForwardQueries = 2000
|
||||
|
||||
// errServerAlreadyStarted is the error that is returned if the server
|
||||
// is requested to start while it's already been started.
|
||||
errServerAlreadyStarted = fmt.Errorf("server can only be started once")
|
||||
|
||||
// ErrBitcoinNodeRequired is required when an endpoint which requires
|
||||
// a bitcoin node backend is hit and we are not connected to one.
|
||||
ErrBitcoinNodeRequired = errors.New("bitcoin node required")
|
||||
|
|
@ -85,35 +45,11 @@ var (
|
|||
|
||||
// RPCServer implements the faraday service, serving requests over grpc.
|
||||
type RPCServer struct {
|
||||
// To be used atomically.
|
||||
started int32
|
||||
|
||||
// To be used atomically.
|
||||
stopped int32
|
||||
|
||||
// Required by the grpc-gateway/v2 library for forward compatibility.
|
||||
// Must be after the atomically used variables to not break struct
|
||||
// alignment.
|
||||
frdrpc.UnimplementedFaradayServerServer
|
||||
|
||||
// cfg contains closures and settings required for operation.
|
||||
cfg *Config
|
||||
|
||||
// grpcServer is the main gRPC RPCServer that this RPC server will
|
||||
// register itself with and accept client requests from.
|
||||
grpcServer *grpc.Server
|
||||
|
||||
// rpcListener is the listener to use when starting the gRPC server.
|
||||
rpcListener net.Listener
|
||||
|
||||
// restServer is the REST proxy server.
|
||||
restServer *http.Server
|
||||
|
||||
macaroonService *lndclient.MacaroonService
|
||||
macaroonDB kvdb.Backend
|
||||
|
||||
restCancel func()
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Config provides closures and settings required to run the rpc server.
|
||||
|
|
@ -121,325 +57,19 @@ type Config struct {
|
|||
// Lnd is a client which can be used to query lnd.
|
||||
Lnd lndclient.LndServices
|
||||
|
||||
// RPCListen is the address:port that the gRPC server should listen on.
|
||||
RPCListen string
|
||||
|
||||
// RESTListen is the address:port that the REST server should listen on.
|
||||
RESTListen string
|
||||
|
||||
// CORSOrigin specifies the CORS header that should be set on REST
|
||||
// responses. No header is added if the value is empty.
|
||||
CORSOrigin string
|
||||
|
||||
// BitcoinClient is set if the client opted to connect to a bitcoin
|
||||
// backend, if not, it will be nil.
|
||||
// BitcoinClient is an optional client which can be used to query
|
||||
// on-chain data from a connected bitcoin node. If nil, faraday will
|
||||
// not be able to serve endpoints which require on-chain data.
|
||||
BitcoinClient chain.BitcoinClient
|
||||
|
||||
// TLSServerConfig is the configuration to serve a secure connection
|
||||
// over TLS.
|
||||
TLSServerConfig *tls.Config
|
||||
|
||||
// RestClientConfig is the client configuration to connect to a TLS
|
||||
// server started with the TLS config above. This is used for the REST
|
||||
// proxy that connects internally to the gRPC server and therefore is a
|
||||
// TLS client.
|
||||
RestClientConfig *credentials.TransportCredentials
|
||||
|
||||
// FaradayDir is the main directory faraday uses. The macaroon database
|
||||
// will be created there.
|
||||
FaradayDir string
|
||||
|
||||
// MacaroonPath is the full path to the default faraday macaroon file
|
||||
// that is created automatically. This path normally is within
|
||||
// FaradayDir unless otherwise specified by the user.
|
||||
MacaroonPath string
|
||||
}
|
||||
|
||||
// NewRPCServer returns a server which will listen for rpc requests on the
|
||||
// rpc listen address provided. Note that the server returned is not running,
|
||||
// and should be started using Start().
|
||||
// NewRPCServer returns a new RPCServer backed by the given config.
|
||||
func NewRPCServer(cfg *Config) *RPCServer {
|
||||
return &RPCServer{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the listener and server.
|
||||
func (s *RPCServer) Start() error {
|
||||
if atomic.AddInt32(&s.started, 1) != 1 {
|
||||
return errServerAlreadyStarted
|
||||
}
|
||||
|
||||
// Depending on how far we got in initializing the server, we might need
|
||||
// to clean up certain services that were already started. Keep track of
|
||||
// them with this map of service name to shutdown function.
|
||||
shutdownFuncs := make(map[string]func() error)
|
||||
defer func() {
|
||||
for serviceName, shutdownFn := range shutdownFuncs {
|
||||
if err := shutdownFn(); err != nil {
|
||||
log.Errorf("Error shutting down %s service: %v",
|
||||
serviceName, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set up the macaroon service.
|
||||
rks, db, err := lndclient.NewBoltMacaroonStore(
|
||||
s.cfg.FaradayDir, lncfg.MacaroonDBName, macDatabaseOpenTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shutdownFuncs["macaroondb"] = db.Close
|
||||
|
||||
s.macaroonDB = db
|
||||
s.macaroonService, err = lndclient.NewMacaroonService(
|
||||
&lndclient.MacaroonServiceConfig{
|
||||
RootKeyStore: rks,
|
||||
MacaroonLocation: faradayMacaroonLocation,
|
||||
MacaroonPath: s.cfg.MacaroonPath,
|
||||
Checkers: []macaroons.Checker{
|
||||
macaroons.IPLockChecker,
|
||||
},
|
||||
RequiredPerms: perms.RequiredPermissions,
|
||||
DBPassword: macDbDefaultPw,
|
||||
LndClient: &s.cfg.Lnd,
|
||||
EphemeralKey: lndclient.SharedKeyNUMS,
|
||||
KeyLocator: lndclient.SharedKeyLocator,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating macroon service: %v", err)
|
||||
}
|
||||
|
||||
// Start the macaroon service and let it create its default macaroon in
|
||||
// case it doesn't exist yet.
|
||||
if err := s.macaroonService.Start(); err != nil {
|
||||
return fmt.Errorf("error starting macaroon service: %v", err)
|
||||
}
|
||||
shutdownFuncs["macaroon"] = s.macaroonService.Stop
|
||||
|
||||
// First we add the security interceptor to our gRPC server options that
|
||||
// checks the macaroons for validity.
|
||||
unaryInterceptor, streamInterceptor, err := s.macaroonService.Interceptors()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with macaroon interceptor: %v", err)
|
||||
}
|
||||
|
||||
// Add our TLS configuration and then create our server instance. It's
|
||||
// important that we let gRPC create the TLS listener and we don't just
|
||||
// use tls.NewListener(). Otherwise we run into the ALPN error with non-
|
||||
// golang clients.
|
||||
tlsCredentials := credentials.NewTLS(s.cfg.TLSServerConfig)
|
||||
s.grpcServer = grpc.NewServer(
|
||||
grpc.UnaryInterceptor(unaryInterceptor),
|
||||
grpc.StreamInterceptor(streamInterceptor),
|
||||
grpc.Creds(tlsCredentials),
|
||||
)
|
||||
|
||||
// Start the gRPC RPCServer listening for HTTP/2 connections.
|
||||
log.Info("Starting gRPC listener")
|
||||
s.rpcListener, err = net.Listen("tcp", s.cfg.RPCListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("RPC RPCServer unable to listen on %v",
|
||||
s.cfg.RPCListen)
|
||||
}
|
||||
shutdownFuncs["gRPC listener"] = s.rpcListener.Close
|
||||
log.Infof("gRPC server listening on %s", s.rpcListener.Addr())
|
||||
|
||||
frdrpc.RegisterFaradayServerServer(s.grpcServer, s)
|
||||
|
||||
// We'll also create and start an accompanying proxy to serve clients
|
||||
// through REST. An empty address indicates REST is disabled.
|
||||
if s.cfg.RESTListen != "" {
|
||||
log.Infof("Starting REST proxy listener ")
|
||||
restListener, err := net.Listen("tcp", s.cfg.RESTListen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("REST server unable to listen on "+
|
||||
"%v: %v", s.cfg.RESTListen, err)
|
||||
}
|
||||
restListener = tls.NewListener(
|
||||
restListener, s.cfg.TLSServerConfig,
|
||||
)
|
||||
shutdownFuncs["REST listener"] = restListener.Close
|
||||
log.Infof("REST server listening on %s", restListener.Addr())
|
||||
|
||||
// We'll dial into the local gRPC server so we need to set some
|
||||
// gRPC dial options and CORS settings.
|
||||
var restCtx context.Context
|
||||
restCtx, s.restCancel = context.WithCancel(context.Background())
|
||||
mux := proxy.NewServeMux(customMarshalerOption)
|
||||
var restHandler http.Handler = mux
|
||||
if s.cfg.CORSOrigin != "" {
|
||||
restHandler = allowCORS(restHandler, s.cfg.CORSOrigin)
|
||||
}
|
||||
proxyOpts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(*s.cfg.RestClientConfig),
|
||||
grpc.WithDefaultCallOptions(maxMsgRecvSize),
|
||||
}
|
||||
|
||||
// With TLS enabled by default, we cannot call 0.0.0.0
|
||||
// internally from the REST proxy as that IP address isn't in
|
||||
// the cert. We need to rewrite it to the loopback address.
|
||||
restProxyDest := s.cfg.RPCListen
|
||||
switch {
|
||||
case strings.Contains(restProxyDest, "0.0.0.0"):
|
||||
restProxyDest = strings.Replace(
|
||||
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
|
||||
)
|
||||
|
||||
case strings.Contains(restProxyDest, "[::]"):
|
||||
restProxyDest = strings.Replace(
|
||||
restProxyDest, "[::]", "[::1]", 1,
|
||||
)
|
||||
}
|
||||
err = frdrpc.RegisterFaradayServerHandlerFromEndpoint(
|
||||
restCtx, mux, restProxyDest, proxyOpts,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.restServer = &http.Server{
|
||||
Handler: restHandler,
|
||||
ReadHeaderTimeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
err := s.restServer.Serve(restListener)
|
||||
// ErrServerClosed is always returned when the proxy is
|
||||
// shut down, so don't log it.
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
log.Infof("REST proxy disabled")
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
if err := s.grpcServer.Serve(s.rpcListener); err != nil {
|
||||
log.Errorf("could not serve grpc server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// If we got here successfully, there's no need to shutdown anything
|
||||
// anymore.
|
||||
shutdownFuncs = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartAsSubserver is an alternative to Start where the RPC server does not
|
||||
// create its own gRPC server but registers to an existing one. The same goes
|
||||
// for REST (if enabled), instead of creating an own mux and HTTP server, we
|
||||
// register to an existing one.
|
||||
func (s *RPCServer) StartAsSubserver(lndClient lndclient.LndServices,
|
||||
withMacaroonService bool) error {
|
||||
|
||||
if atomic.AddInt32(&s.started, 1) != 1 {
|
||||
return errServerAlreadyStarted
|
||||
}
|
||||
|
||||
if withMacaroonService {
|
||||
// Set up the macaroon service.
|
||||
rks, db, err := lndclient.NewBoltMacaroonStore(
|
||||
s.cfg.FaradayDir, lncfg.MacaroonDBName,
|
||||
macDatabaseOpenTimeout,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.macaroonDB = db
|
||||
s.macaroonService, err = lndclient.NewMacaroonService(
|
||||
&lndclient.MacaroonServiceConfig{
|
||||
RootKeyStore: rks,
|
||||
MacaroonLocation: faradayMacaroonLocation,
|
||||
MacaroonPath: s.cfg.MacaroonPath,
|
||||
Checkers: []macaroons.Checker{
|
||||
macaroons.IPLockChecker,
|
||||
},
|
||||
RequiredPerms: perms.RequiredPermissions,
|
||||
DBPassword: macDbDefaultPw,
|
||||
LndClient: &lndClient,
|
||||
EphemeralKey: lndclient.SharedKeyNUMS,
|
||||
KeyLocator: lndclient.SharedKeyLocator,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating macroon service: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Start the macaroon service and let it create its default
|
||||
// macaroon in case it doesn't exist yet.
|
||||
if err := s.macaroonService.Start(); err != nil {
|
||||
return fmt.Errorf("error starting macaroon service: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
s.cfg.Lnd = lndClient
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
|
||||
// checks its signature, makes sure all specified permissions for the called
|
||||
// method are contained within and finally ensures all caveat conditions are
|
||||
// met. A non-nil error is returned if any of the checks fail. This method is
|
||||
// needed to enable faraday running as an external subserver in the same process
|
||||
// as lnd but still validate its own macaroons.
|
||||
func (s *RPCServer) ValidateMacaroon(ctx context.Context,
|
||||
requiredPermissions []bakery.Op, fullMethod string) error {
|
||||
|
||||
if s.macaroonService == nil {
|
||||
return fmt.Errorf("macaroon service not yet initialised")
|
||||
}
|
||||
|
||||
// Delegate the call to faraday's own macaroon validator service.
|
||||
return s.macaroonService.ValidateMacaroon(
|
||||
ctx, requiredPermissions, fullMethod,
|
||||
)
|
||||
}
|
||||
|
||||
// Stop stops the grpc listener and server.
|
||||
func (s *RPCServer) Stop() error {
|
||||
if atomic.AddInt32(&s.stopped, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.restServer != nil {
|
||||
s.restCancel()
|
||||
err := s.restServer.Close()
|
||||
if err != nil {
|
||||
log.Errorf("unable to close REST listener: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.macaroonService != nil {
|
||||
if err := s.macaroonService.Stop(); err != nil {
|
||||
log.Errorf("Error stopping macaroon service: %v", err)
|
||||
}
|
||||
}
|
||||
if s.macaroonDB != nil {
|
||||
if err := s.macaroonDB.Close(); err != nil {
|
||||
log.Errorf("Error closing macaroon DB: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the grpc server and wait for all go routines to terminate.
|
||||
if s.grpcServer != nil {
|
||||
s.grpcServer.Stop()
|
||||
}
|
||||
s.wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutlierRecommendations provides a set of close recommendations for the
|
||||
// current set of open channels based on whether they are outliers.
|
||||
func (s *RPCServer) OutlierRecommendations(ctx context.Context,
|
||||
|
|
@ -594,12 +224,3 @@ func (s *RPCServer) requireNode() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// allowCORS wraps the given http.Handler with a function that adds the
|
||||
// Access-Control-Allow-Origin header to the response.
|
||||
func allowCORS(handler http.Handler, origin string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package frdrpcserver
|
||||
package faraday
|
||||
|
||||
import (
|
||||
"time"
|
||||
Loading…
Add table
Add a link
Reference in a new issue