multi: optionally connect faraday to a bitcoin node

Original chain code copied from:
e3c34449484d7dc8d505a98ea275e510651d7c3d

Co-authored-by: Oliver Gugger gugger@gmail.com
This commit is contained in:
carla 2020-06-27 14:36:04 +02:00
parent 9a58df81c9
commit bb0b82aaee
No known key found for this signature in database
GPG key ID: 4CA7FE54A6213C91
4 changed files with 185 additions and 8 deletions

135
chain/client.go Normal file
View file

@ -0,0 +1,135 @@
package chain
import (
"io/ioutil"
"os"
"sync"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
)
// BitcoinClient is an interface which represents a connection to a bitcoin
// client.
type BitcoinClient interface {
// GetTxDetail looks up a transaction.
GetTxDetail(txHash *chainhash.Hash) (*btcjson.TxRawResult, error)
}
// BitcoinConfig defines exported config options for the connection to the
// btcd/bitcoind backend.
type BitcoinConfig struct {
Host string `long:"host" description:"host:port of the bitcoind/btcd instance address"`
User string `long:"user" description:"bitcoind/btcd user name"`
Password string `long:"password" description:"bitcoind/btcd password"`
HTTPPostMode bool `long:"httppostmode" description:"Use HTTP POST mode? bitcoind only supports this mode"`
UseTLS bool `long:"usetls" description:"Use TLS to connect? bitcoind only supports non-TLS connections"`
TLSPath string `long:"tlspath" description:"Path to btcd tls certificate, bitcoind only supports non-TLS connections"`
}
// DefaultConfig is the default config that we use to
var DefaultConfig = &BitcoinConfig{
Host: "localhost:8332",
UseTLS: false,
HTTPPostMode: true,
}
// bitcoinClient is a wrapper around the RPC connection to the chain backend
// and allows transactions to be queried.
type bitcoinClient struct {
sync.Mutex
rpcClient *rpcclient.Client
// txDetailCache holds a cache of transactions we have previously looked
// up.
txDetailCache map[string]*btcjson.TxRawResult
}
// GetTxDetail fetches a single transaction from the chain and returns it
// in a format that contains more details, like the block hash it was included
// in for example.
func (c *bitcoinClient) GetTxDetail(txHash *chainhash.Hash) (
*btcjson.TxRawResult, error) {
c.Lock()
cachedTx, ok := c.txDetailCache[txHash.String()]
c.Unlock()
if ok {
return cachedTx, nil
}
tx, err := c.rpcClient.GetRawTransactionVerbose(txHash)
if err != nil {
return nil, err
}
// Do not cache the transaction if it has not confirmed yet. If we do,
// we won't ever lookup the confirmed transaction because it is already
// cached.
if tx.BlockHash == "" {
return tx, nil
}
c.Lock()
c.txDetailCache[txHash.String()] = tx
c.Unlock()
return tx, nil
}
// NewBitcoinClient attempts to connect to a bitcoin rpcclient with the config
// provided and returns a BitcoinClient wrapper which can be used to access the
// chain connection.
func NewBitcoinClient(cfg *BitcoinConfig) (BitcoinClient, error) {
client, err := getBitcoinConn(cfg)
if err != nil {
return nil, err
}
return &bitcoinClient{
rpcClient: client,
txDetailCache: make(map[string]*btcjson.TxRawResult),
}, nil
}
// getBitcoinConn gets a bitcoin rpc client from the config details provided.
func getBitcoinConn(cfg *BitcoinConfig) (*rpcclient.Client, error) {
// In case we use TLS and a certificate argument is provided, we need to
// read that file and provide it to the RPC connection as byte slice.
var rpcCert []byte
if cfg.UseTLS && cfg.TLSPath != "" {
certFile, err := os.Open(cfg.TLSPath)
if err != nil {
return nil, err
}
rpcCert, err = ioutil.ReadAll(certFile)
if err != nil {
return nil, err
}
if err := certFile.Close(); err != nil {
return nil, err
}
}
// Connect to bitcoin core RPC server using HTTP POST mode.
connCfg := &rpcclient.ConnConfig{
Host: cfg.Host,
User: cfg.User,
Pass: cfg.Password,
HTTPPostMode: cfg.HTTPPostMode,
DisableTLS: !cfg.UseTLS,
Certificates: rpcCert,
}
// Notice the notification parameter is nil since notifications are
// not supported in HTTP POST mode.
return rpcclient.New(connCfg, nil)
}
// Stop closes the connection to the chain backend and should always be
// called on cleanup.
func (c *bitcoinClient) Stop() {
c.rpcClient.Shutdown()
}

View file

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
"github.com/lightninglabs/faraday/chain"
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
) )
@ -15,6 +16,10 @@ const (
defaultMinimumMonitor = time.Hour * 24 * 7 * 4 // four weeks in hours defaultMinimumMonitor = time.Hour * 24 * 7 * 4 // four weeks in hours
defaultDebugLevel = "info" defaultDebugLevel = "info"
defaultRPCListen = "localhost:8465" defaultRPCListen = "localhost:8465"
// By default we do not require connecting to a bitcoin node so that
// we can serve basic functionality by default.
defaultChainConn = false
) )
type Config struct { type Config struct {
@ -36,6 +41,9 @@ type Config struct {
// Simnet is set to true when using bitcoind's regtest. // Simnet is set to true when using bitcoind's regtest.
Regtest bool `long:"regtest" description:"Use regtest"` Regtest bool `long:"regtest" description:"Use regtest"`
// ChainConn specifies whether to attempt connecting to a bitcoin backend.
ChainConn bool `long:"connect_bitcoin" description:"Whether to attempt to connect to a backing bitcoin node. Some endpoints will not be available if this option is not enabled."`
// MinimumMonitored is the minimum amount of time that a channel must be monitored for before we consider it for termination. // MinimumMonitored is the minimum amount of time that a channel must be monitored for before we consider it for termination.
MinimumMonitored time.Duration `long:"min_monitored" description:"The minimum amount of time that a channel must be monitored for before recommending termination. Valid time units are {s, m, h}."` MinimumMonitored time.Duration `long:"min_monitored" description:"The minimum amount of time that a channel must be monitored for before recommending termination. Valid time units are {s, m, h}."`
@ -54,6 +62,9 @@ type Config struct {
// CORSOrigin specifies the CORS header that should be set on REST responses. No header is added if the value is empty. // CORSOrigin specifies the CORS header that should be set on REST responses. No header is added if the value is empty.
CORSOrigin string `long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty."` CORSOrigin string `long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty."`
// Bitcoin is the configuration required to connect to a bitcoin node.
Bitcoin *chain.BitcoinConfig `group:"bitcoin" namespace:"bitcoin"`
} }
// DefaultConfig returns all default values for the Config struct. // DefaultConfig returns all default values for the Config struct.
@ -64,6 +75,8 @@ func DefaultConfig() Config {
MinimumMonitored: defaultMinimumMonitor, MinimumMonitored: defaultMinimumMonitor,
DebugLevel: defaultDebugLevel, DebugLevel: defaultDebugLevel,
RPCListen: defaultRPCListen, RPCListen: defaultRPCListen,
ChainConn: defaultChainConn,
Bitcoin: chain.DefaultConfig,
} }
} }
@ -98,6 +111,21 @@ func LoadConfig() (*Config, error) {
return nil, fmt.Errorf("do not specify more than one network flag") return nil, fmt.Errorf("do not specify more than one network flag")
} }
// If the user has opted into connecting to a bitcoin backend, check
// that we have a rpc user and password, and that tls path is set if
// required.
if config.ChainConn {
if config.Bitcoin.User == "" || config.Bitcoin.Password == "" {
return nil, fmt.Errorf("rpc user and password " +
"required when chainconn is set")
}
if config.Bitcoin.UseTLS && config.Bitcoin.TLSPath == "" {
return nil, fmt.Errorf("bitcoin.tlspath required " +
"when chainconn is set")
}
}
if err := build.ParseAndSetDebugLevels(config.DebugLevel, logWriter); err != nil { if err := build.ParseAndSetDebugLevels(config.DebugLevel, logWriter); err != nil {
return nil, err return nil, err
} }

View file

@ -4,6 +4,7 @@ package faraday
import ( import (
"fmt" "fmt"
"github.com/lightninglabs/faraday/chain"
"github.com/lightninglabs/faraday/frdrpc" "github.com/lightninglabs/faraday/frdrpc"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/signal"
@ -35,14 +36,22 @@ func Main() error {
defer client.Close() defer client.Close()
// Instantiate the faraday gRPC server. // Instantiate the faraday gRPC server.
server := frdrpc.NewRPCServer( cfg := &frdrpc.Config{
&frdrpc.Config{ Lnd: client.LndServices,
Lnd: client.LndServices, RPCListen: config.RPCListen,
RPCListen: config.RPCListen, RESTListen: config.RESTListen,
RESTListen: config.RESTListen, CORSOrigin: config.CORSOrigin,
CORSOrigin: config.CORSOrigin, }
},
) // If the client chose to connect to a bitcoin client, get one now.
if config.ChainConn {
cfg.BitcoinClient, err = chain.NewBitcoinClient(config.Bitcoin)
if err != nil {
return err
}
}
server := frdrpc.NewRPCServer(cfg)
// Catch intercept signals, then start the server. // Catch intercept signals, then start the server.
signal.Intercept() signal.Intercept()

View file

@ -19,6 +19,7 @@ import (
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightninglabs/faraday/accounting" "github.com/lightninglabs/faraday/accounting"
"github.com/lightninglabs/faraday/chain"
"github.com/lightninglabs/faraday/fiat" "github.com/lightninglabs/faraday/fiat"
"github.com/lightninglabs/faraday/recommend" "github.com/lightninglabs/faraday/recommend"
"github.com/lightninglabs/faraday/revenue" "github.com/lightninglabs/faraday/revenue"
@ -102,6 +103,10 @@ type Config struct {
// CORSOrigin specifies the CORS header that should be set on REST // CORSOrigin specifies the CORS header that should be set on REST
// responses. No header is added if the value is empty. // responses. No header is added if the value is empty.
CORSOrigin string CORSOrigin string
// BitcoinClient is set if the client opted to connect to a bitcoin
// backend, if not, it will be nil.
BitcoinClient chain.BitcoinClient
} }
// NewRPCServer returns a server which will listen for rpc requests on the // NewRPCServer returns a server which will listen for rpc requests on the