mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
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:
parent
9a58df81c9
commit
bb0b82aaee
4 changed files with 185 additions and 8 deletions
135
chain/client.go
Normal file
135
chain/client.go
Normal 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()
|
||||||
|
}
|
||||||
28
config.go
28
config.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
faraday.go
25
faraday.go
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue