trader: prepare to be used as library

This commit is contained in:
Oliver Gugger 2020-01-09 14:28:13 +01:00
parent 17a71068ad
commit 976df85bb1
7 changed files with 267 additions and 223 deletions

214
agorad.go Normal file
View file

@ -0,0 +1,214 @@
package client
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightninglabs/agora/client/auctioneer"
"github.com/lightninglabs/agora/client/clmrpc"
"github.com/lightninglabs/agora/client/trader"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/build"
"google.golang.org/grpc"
)
// Start runs agorad in daemon mode. It will listen for grpc connections,
// execute commands and pass back auction status information.
func Start(config *Config) error {
// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
if config.ShowVersion {
fmt.Println(appName, "version", Version())
os.Exit(0)
}
// Special show command to list supported subsystems and exit.
if config.DebugLevel == "show" {
fmt.Printf("Supported subsystems: %v\n",
logWriter.SupportedSubsystems())
os.Exit(0)
}
// Append the network type to the log directory so it is
// "namespaced" per network in the same fashion as the data directory.
config.LogDir = filepath.Join(config.LogDir, config.Network)
// Initialize logging at the default logging level.
err := logWriter.InitLogRotator(
filepath.Join(config.LogDir, DefaultLogFilename),
config.MaxLogFileSize, config.MaxLogFiles,
)
if err != nil {
return err
}
err = build.ParseAndSetDebugLevels(config.DebugLevel, logWriter)
if err != nil {
return err
}
// Print the version before executing either primary directive.
log.Infof("Version: %v", Version())
lnd, err := getLnd(config.Network, config.Lnd)
if err != nil {
return err
}
defer lnd.Close()
// If no auction server is specified, use the default addresses for
// mainnet and testnet.
if config.AuctionServer == "" && len(config.AuctioneerDialOpts) == 0 {
switch config.Network {
case "mainnet":
config.AuctionServer = MainnetServer
case "testnet":
config.AuctionServer = TestnetServer
default:
return errors.New("no auction server address specified")
}
}
log.Infof("Auction server address: %v", config.AuctionServer)
// Create an instance of the auctioneer client library.
auctioneerClient, cleanup, err := auctioneer.NewClient(
config.AuctionServer, config.Insecure, config.TLSPathAuctSrv,
lnd.WalletKit, config.AuctioneerDialOpts...,
)
if err != nil {
return err
}
defer cleanup()
// Instantiate the agorad gRPC server.
networkDir := filepath.Join(config.BaseDir, config.Network)
traderServer, err := trader.NewServer(
&lnd.LndServices, auctioneerClient, networkDir,
)
if err != nil {
return err
}
serverOpts := []grpc.ServerOption{}
grpcServer := grpc.NewServer(serverOpts...)
clmrpc.RegisterChannelAuctioneerClientServer(grpcServer, traderServer)
// Next, start the gRPC server listening for HTTP/2 connections.
// If the provided grpcListener is not nil, it means agorad is being
// used as a library and the listener might not be a real network
// connection (but maybe a UNIX socket or bufconn). So we don't spin up
// a REST listener in that case.
log.Infof("Starting gRPC listener")
var (
wg sync.WaitGroup
grpcListener = config.RPCListener
restListener net.Listener
restProxy *http.Server
)
if grpcListener == nil {
grpcListener, err = net.Listen("tcp", config.RPCListen)
if err != nil {
return fmt.Errorf("RPC server unable to listen on %s",
config.RPCListen)
}
defer closeOrLog(grpcListener)
// We'll also create and start an accompanying proxy to serve
// clients through REST.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mux := proxy.NewServeMux()
proxyOpts := []grpc.DialOption{grpc.WithInsecure()}
err = clmrpc.RegisterChannelAuctioneerClientHandlerFromEndpoint(
ctx, mux, config.RPCListen, proxyOpts,
)
if err != nil {
return err
}
log.Infof("Starting REST proxy listener")
restListener, err = net.Listen("tcp", config.RESTListen)
if err != nil {
return fmt.Errorf("REST proxy unable to listen on %s",
config.RESTListen)
}
defer closeOrLog(restListener)
restProxy = &http.Server{Handler: mux}
wg.Add(1)
go func() {
defer wg.Done()
err := restProxy.Serve(restListener)
if err != nil && err != http.ErrServerClosed {
log.Errorf("could not start rest listener: %v",
err)
}
}()
}
// Start the trader server itself.
err = traderServer.Start()
if err != nil {
return err
}
// Start the grpc server.
wg.Add(1)
go func() {
defer wg.Done()
log.Infof("RPC server listening on %s", grpcListener.Addr())
if restListener != nil {
log.Infof("REST proxy listening on %s",
restListener.Addr())
}
err = grpcServer.Serve(grpcListener)
if err != nil {
log.Error(err)
}
}()
// Run until the user terminates agorad.
<-config.ShutdownChannel
log.Info("Received shutdown signal, stopping server")
grpcServer.GracefulStop()
if restProxy != nil {
err := restProxy.Shutdown(context.Background())
if err != nil {
log.Errorf("error shutting down REST proxy: %v", err)
}
}
err = traderServer.Stop()
if err != nil {
log.Errorf("error shutting down server: %v", err)
}
wg.Wait()
return nil
}
// getLnd returns an instance of the lnd services proxy.
func getLnd(network string, cfg *LndConfig) (*lndclient.GrpcLndServices, error) {
return lndclient.NewLndServices(
cfg.Host, network, cfg.MacaroonDir, cfg.TLSPath,
)
}
func closeOrLog(c io.Closer) {
err := c.Close()
if err != nil {
log.Errorf("could not close: %v", err)
}
}

View file

@ -22,10 +22,11 @@ type Client struct {
// NewClient returns a new instance to initiate auctions with.
func NewClient(serverAddress string, insecure bool, tlsPathServer string,
wallet lndclient.WalletKitClient) (*Client, func(), error) {
wallet lndclient.WalletKitClient, dialOpts ...grpc.DialOption) (
*Client, func(), error) {
serverConn, err := getAuctionServerConn(
serverAddress, insecure, tlsPathServer,
serverAddress, insecure, tlsPathServer, dialOpts...,
)
if err != nil {
return nil, nil, err
@ -42,11 +43,11 @@ func NewClient(serverAddress string, insecure bool, tlsPathServer string,
}
// getAuctionServerConn returns a connection to the auction server.
func getAuctionServerConn(address string, insecure bool, tlsPath string) (
*grpc.ClientConn, error) {
func getAuctionServerConn(address string, insecure bool, tlsPath string,
dialOpts ...grpc.DialOption) (*grpc.ClientConn, error) {
// Create a dial options array.
opts := []grpc.DialOption{}
// Create a copy of the dial options array.
opts := dialOpts
// There are three options to connect to a auction server, either
// insecure, using a self-signed certificate or with a certificate

View file

@ -1,148 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"sync"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightninglabs/agora/client/auctioneer"
"github.com/lightninglabs/agora/client/clmrpc"
"github.com/lightninglabs/agora/client/trader"
"github.com/lightningnetwork/lnd/signal"
"google.golang.org/grpc"
)
// daemon runs agorad in daemon mode. It will listen for grpc connections,
// execute commands and pass back auction status information.
func daemon(config *config) error {
lnd, err := getLnd(config.Network, config.Lnd)
if err != nil {
return err
}
defer lnd.Close()
// If no auction server is specified, use the default addresses for
// mainnet and testnet.
if config.AuctionServer == "" {
switch config.Network {
case "mainnet":
config.AuctionServer = mainnetServer
case "testnet":
config.AuctionServer = testnetServer
default:
return errors.New("no auction server address specified")
}
}
log.Infof("Auction server address: %v", config.AuctionServer)
// Create an instance of the auctioneer client library.
auctioneerClient, cleanup, err := auctioneer.NewClient(
config.AuctionServer, config.Insecure, config.TLSPathAuctSrv,
lnd.WalletKit,
)
if err != nil {
return err
}
defer cleanup()
// Instantiate the agorad gRPC server.
traderServer, err := trader.NewServer(
&lnd.LndServices, auctioneerClient, config.serverDir,
)
if err != nil {
return err
}
serverOpts := []grpc.ServerOption{}
grpcServer := grpc.NewServer(serverOpts...)
clmrpc.RegisterChannelAuctioneerClientServer(grpcServer, traderServer)
// Next, start the gRPC server listening for HTTP/2 connections.
log.Infof("Starting gRPC listener")
grpcListener, err := net.Listen("tcp", config.RPCListen)
if err != nil {
return fmt.Errorf("RPC server unable to listen on %s",
config.RPCListen)
}
defer closeOrLog(grpcListener)
// We'll also create and start an accompanying proxy to serve clients
// through REST.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mux := proxy.NewServeMux()
proxyOpts := []grpc.DialOption{grpc.WithInsecure()}
err = clmrpc.RegisterChannelAuctioneerClientHandlerFromEndpoint(
ctx, mux, config.RPCListen, proxyOpts,
)
if err != nil {
return err
}
log.Infof("Starting REST proxy listener")
restListener, err := net.Listen("tcp", config.RESTListen)
if err != nil {
return fmt.Errorf("REST proxy unable to listen on %s",
config.RESTListen)
}
defer closeOrLog(restListener)
restProxy := &http.Server{Handler: mux}
go func() {
err := restProxy.Serve(restListener)
if err != nil && err != http.ErrServerClosed {
log.Errorf("could not start rest listener: %v", err)
}
}()
// Start the trader server itself.
err = traderServer.Start()
if err != nil {
return err
}
// Start the grpc server.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
log.Infof("RPC server listening on %s", grpcListener.Addr())
log.Infof("REST proxy listening on %s", restListener.Addr())
err = grpcServer.Serve(grpcListener)
if err != nil {
log.Error(err)
}
}()
// Run until the user terminates agorad.
signal.Intercept()
<-signal.ShutdownChannel()
log.Info("Received shutdown signal, stopping server")
grpcServer.GracefulStop()
err = restProxy.Shutdown(context.Background())
if err != nil {
log.Errorf("error shutting down REST proxy: %v", err)
}
err = traderServer.Stop()
if err != nil {
log.Errorf("error shutting down server: %v", err)
}
wg.Wait()
return nil
}
func closeOrLog(c io.Closer) {
err := c.Close()
if err != nil {
log.Errorf("could not close: %v", err)
}
}

View file

@ -4,11 +4,10 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/jessevdk/go-flags"
"github.com/lightninglabs/agora/client"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/signal"
)
var (
@ -23,7 +22,7 @@ func main() {
}
func start() error {
config := defaultConfig
config := client.DefaultConfig
// Parse command line flags.
parser := flags.NewParser(&config, flags.Default)
@ -38,12 +37,12 @@ func start() error {
}
// Parse ini file.
config.serverDir = filepath.Join(agoraDirBase, config.Network)
if err := os.MkdirAll(config.serverDir, os.ModePerm); err != nil {
networkDir := filepath.Join(config.BaseDir, config.Network)
if err := os.MkdirAll(networkDir, os.ModePerm); err != nil {
return err
}
configFile := filepath.Join(config.serverDir, defaultConfigFilename)
configFile := filepath.Join(networkDir, defaultConfigFilename)
if err := flags.IniParse(configFile, &config); err != nil {
// If it's a parsing related error, then we'll return
// immediately, otherwise we can proceed as possibly the config
@ -60,44 +59,11 @@ func start() error {
return err
}
// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
if config.ShowVersion {
fmt.Println(appName, "version", client.Version())
os.Exit(0)
}
// Special show command to list supported subsystems and exit.
if config.DebugLevel == "show" {
fmt.Printf("Supported subsystems: %v\n",
logWriter.SupportedSubsystems())
os.Exit(0)
}
// Append the network type to the log directory so it is
// "namespaced" per network in the same fashion as the data directory.
config.LogDir = filepath.Join(config.LogDir, config.Network)
// Initialize logging at the default logging level.
err = logWriter.InitLogRotator(
filepath.Join(config.LogDir, defaultLogFilename),
config.MaxLogFileSize, config.MaxLogFiles,
)
if err != nil {
return err
}
err = build.ParseAndSetDebugLevels(config.DebugLevel, logWriter)
if err != nil {
return err
}
// Print the version before executing either primary directive.
log.Infof("Version: %v", client.Version())
// Execute command.
if parser.Active == nil {
return daemon(&config)
signal.Intercept()
config.ShutdownChannel = signal.ShutdownChannel()
return client.Start(&config)
}
return fmt.Errorf("unimplemented command %v", parser.Active.Name)

View file

@ -1,12 +0,0 @@
package main
import (
"github.com/lightninglabs/loop/lndclient"
)
// getLnd returns an instance of the lnd services proxy.
func getLnd(network string, cfg *lndConfig) (*lndclient.GrpcLndServices, error) {
return lndclient.NewLndServices(
cfg.Host, network, cfg.MacaroonDir, cfg.TLSPath,
)
}

View file

@ -1,30 +1,39 @@
package main
package client
import (
"net"
"path/filepath"
"github.com/btcsuite/btcutil"
"google.golang.org/grpc"
)
var (
agoraDirBase = btcutil.AppDataDir("agora", false)
// DefaultBaseDir is the default root data directory where agora will
// store all its data. On UNIX like systems this will resolve to
// ~/.agora. Below this directory the logs and network directory will be
// created.
DefaultBaseDir = btcutil.AppDataDir("agora", false)
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "agorad.log"
defaultLogDir = filepath.Join(agoraDirBase, defaultLogDirname)
// DefaultLogFilename is the default name that is given to the agora log
// file.
DefaultLogFilename = "agorad.log"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogDir = filepath.Join(DefaultBaseDir, defaultLogDirname)
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
)
type lndConfig struct {
type LndConfig struct {
Host string `long:"host" description:"lnd instance rpc address"`
MacaroonDir string `long:"macaroondir" description:"Path to the directory containing all the required lnd macaroons"`
TLSPath string `long:"tlspath" description:"Path to lnd tls certificate"`
}
type config struct {
type Config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Insecure bool `long:"insecure" description:"disable tls"`
Network string `long:"network" description:"network to run on" choice:"regtest" choice:"testnet" choice:"mainnet" choice:"simnet"`
@ -32,6 +41,7 @@ type config struct {
TLSPathAuctSrv string `long:"tlspathauctserver" description:"Path to auction server tls certificate"`
RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients"`
RESTListen string `long:"restlisten" description:"Address to listen on for REST clients"`
BaseDir string `long:"basedir" description:"The base directory where agora stores all its data"`
LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
@ -39,26 +49,39 @@ type config struct {
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
Lnd *lndConfig `group:"lnd" namespace:"lnd"`
Lnd *LndConfig `group:"lnd" namespace:"lnd"`
serverDir string
// RPCListener is a network listener that can be set if agorad should be
// used as a library and listen on the given listener instead of what is
// configured in the --rpclisten parameter. Setting this will also
// disable REST.
RPCListener net.Listener
// AuctioneerDialOpts is a list of dial options that should be used when
// dialing the auctioneer server.
AuctioneerDialOpts []grpc.DialOption
// ShutdownChannel is the channel that must be provided where agorad
// listens for a shutdown signal.
ShutdownChannel <-chan struct{}
}
const (
mainnetServer = "auction.lightning.today:12009"
testnetServer = "test.auction.lightning.today:12009"
MainnetServer = "auction.lightning.today:12009"
TestnetServer = "test.auction.lightning.today:12009"
)
var defaultConfig = config{
var DefaultConfig = Config{
Network: "mainnet",
RPCListen: "localhost:12010",
RESTListen: "localhost:8281",
Insecure: false,
BaseDir: DefaultBaseDir,
LogDir: defaultLogDir,
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
DebugLevel: defaultLogLevel,
Lnd: &lndConfig{
Lnd: &LndConfig{
Host: "localhost:10009",
},
}

View file

@ -1,6 +1,6 @@
// As this file is very similar in every package, ignore the linter here.
// nolint:dupl
package main
package client
import (
"github.com/btcsuite/btclog"