main/lntest: factor out node config options, add options for bitcoind

This commit factors out the btcd and ltcd options into their own sections
similar to neutrino, and adds a bitcoind section as well. Now, you specify
node options similarly to:

--ltcd.rpchost=...
or
--btcd.rpcuser=...
or
--bitcoind.zmqpath=...

For Bitcoin, you specify an alternate back-end to btcd as follows:

--bitcoin.node=bitcoind
or
--bitcoin.node=neutrino

You can also specify the default option:
--bitcoin.node=btcd

For Litecoin, only `btcd` mode is valid, and corresponds to the `ltcd`
section. For example:

--litecoin.node=btcd
--ltcd.rpchost=...

The new code also attempts to read the correct options and auth info
from bitcoin.conf just as it does from btcd.conf/ltcd.conf.
This commit is contained in:
Alex 2017-12-21 22:23:24 -07:00 committed by Olaoluwa Osuntokun
parent 187f59556a
commit 9a02884d0b
7 changed files with 398 additions and 115 deletions

@ -4,14 +4,17 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
@ -134,15 +137,17 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
} }
var ( var (
err error err error
cleanUp func() cleanUp func()
rawRPCConn *chain.RPCClient btcdConn *chain.RPCClient
bitcoindConn *chain.BitcoindClient
) )
// If spv mode is active, then we'll be using a distinct set of // If spv mode is active, then we'll be using a distinct set of
// chainControl interfaces that interface directly with the p2p network // chainControl interfaces that interface directly with the p2p network
// of the selected chain. // of the selected chain.
if cfg.NeutrinoMode.Active { switch homeChainConfig.Node {
case "neutrino":
// First we'll open the database file for neutrino, creating // First we'll open the database file for neutrino, creating
// the database if needed. // the database if needed.
dbName := filepath.Join(cfg.DataDir, "neutrino.db") dbName := filepath.Join(cfg.DataDir, "neutrino.db")
@ -189,21 +194,121 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
cleanUp = func() { cleanUp = func() {
defer nodeDatabase.Close() defer nodeDatabase.Close()
} }
} else { case "bitcoind":
// Otherwise, we'll be speaking directly via RPC and ZMQ to a
// bitcoind node. If the specified host for the btcd/ltcd RPC
// server already has a port specified, then we use that
// directly. Otherwise, we assume the default port according to
// the selected chain parameters.
var bitcoindHost string
if strings.Contains(cfg.BitcoindMode.RPCHost, ":") {
bitcoindHost = cfg.BitcoindMode.RPCHost
} else {
// The RPC ports specified in chainparams.go assume
// btcd, which picks a different port so that btcwallet
// can use the same RPC port as bitcoind. We convert
// this back to the btcwallet/bitcoind port.
rpcPort, err := strconv.Atoi(activeNetParams.rpcPort)
if err != nil {
return nil, nil, err
}
rpcPort -= 2
bitcoindHost = fmt.Sprintf("%v:%d",
cfg.BitcoindMode.RPCHost, rpcPort)
if cfg.Bitcoin.RegTest {
conn, err := net.Dial("tcp", bitcoindHost)
if err != nil || conn == nil {
rpcPort = 18443
bitcoindHost = fmt.Sprintf("%v:%d",
cfg.BitcoindMode.RPCHost,
rpcPort)
} else {
conn.Close()
}
}
}
bitcoindUser := cfg.BitcoindMode.RPCUser
bitcoindPass := cfg.BitcoindMode.RPCPass
rpcConfig := &rpcclient.ConnConfig{
Host: bitcoindHost,
User: bitcoindUser,
Pass: bitcoindPass,
DisableConnectOnNew: true,
DisableAutoReconnect: false,
DisableTLS: true,
HTTPPostMode: true,
}
cc.chainNotifier, err = bitcoindnotify.New(rpcConfig,
cfg.BitcoindMode.ZMQPath, *activeNetParams.Params)
if err != nil {
return nil, nil, err
}
// Next, we'll create an instance of the bitcoind chain view to
// be used within the routing layer.
cc.chainView, err = chainview.NewBitcoindFilteredChainView(
*rpcConfig, cfg.BitcoindMode.ZMQPath,
*activeNetParams.Params)
if err != nil {
srvrLog.Errorf("unable to create chain view: %v", err)
return nil, nil, err
}
// Create a special rpc+ZMQ client for bitcoind which will be
// used by the wallet for notifications, calls, etc.
bitcoindConn, err = chain.NewBitcoindClient(
activeNetParams.Params, bitcoindHost, bitcoindUser,
bitcoindPass, cfg.BitcoindMode.ZMQPath,
time.Millisecond*100)
if err != nil {
return nil, nil, err
}
walletConfig.ChainSource = bitcoindConn
// If we're not in regtest mode, then we'll attempt to use a
// proper fee estimator for testnet.
if !cfg.Bitcoin.RegTest {
ltndLog.Infof("Initializing bitcoind backed fee estimator")
// Finally, we'll re-initialize the fee estimator, as
// if we're using bitcoind as a backend, then we can
// use live fee estimates, rather than a statically
// coded value.
fallBackFeeRate := btcutil.Amount(25)
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
*rpcConfig, fallBackFeeRate,
)
if err != nil {
return nil, nil, err
}
if err := cc.feeEstimator.Start(); err != nil {
return nil, nil, err
}
}
case "btcd":
// Otherwise, we'll be speaking directly via RPC to a node. // Otherwise, we'll be speaking directly via RPC to a node.
// //
// So first we'll load btcd/ltcd's TLS cert for the RPC // So first we'll load btcd/ltcd's TLS cert for the RPC
// connection. If a raw cert was specified in the config, then // connection. If a raw cert was specified in the config, then
// we'll set that directly. Otherwise, we attempt to read the // we'll set that directly. Otherwise, we attempt to read the
// cert from the path specified in the config. // cert from the path specified in the config.
var btcdMode *btcdConfig
switch {
case cfg.Bitcoin.Active:
btcdMode = cfg.BtcdMode
case cfg.Litecoin.Active:
btcdMode = cfg.LtcdMode
}
var rpcCert []byte var rpcCert []byte
if homeChainConfig.RawRPCCert != "" { if btcdMode.RawRPCCert != "" {
rpcCert, err = hex.DecodeString(homeChainConfig.RawRPCCert) rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
} else { } else {
certFile, err := os.Open(homeChainConfig.RPCCert) certFile, err := os.Open(btcdMode.RPCCert)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -221,15 +326,15 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// we assume the default port according to the selected chain // we assume the default port according to the selected chain
// parameters. // parameters.
var btcdHost string var btcdHost string
if strings.Contains(homeChainConfig.RPCHost, ":") { if strings.Contains(btcdMode.RPCHost, ":") {
btcdHost = homeChainConfig.RPCHost btcdHost = btcdMode.RPCHost
} else { } else {
btcdHost = fmt.Sprintf("%v:%v", homeChainConfig.RPCHost, btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
activeNetParams.rpcPort) activeNetParams.rpcPort)
} }
btcdUser := homeChainConfig.RPCUser btcdUser := btcdMode.RPCUser
btcdPass := homeChainConfig.RPCPass btcdPass := btcdMode.RPCPass
rpcConfig := &rpcclient.ConnConfig{ rpcConfig := &rpcclient.ConnConfig{
Host: btcdHost, Host: btcdHost,
Endpoint: "ws", Endpoint: "ws",
@ -262,7 +367,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
} }
walletConfig.ChainSource = chainRPC walletConfig.ChainSource = chainRPC
rawRPCConn = chainRPC btcdConn = chainRPC
// If we're not in simnet or regtest mode, then we'll attempt // If we're not in simnet or regtest mode, then we'll attempt
// to use a proper fee estimator for testnet. // to use a proper fee estimator for testnet.
@ -286,6 +391,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
return nil, nil, err return nil, nil, err
} }
} }
default:
return nil, nil, fmt.Errorf("unknown node type: %s",
homeChainConfig.Node)
} }
wc, err := btcwallet.New(*walletConfig) wc, err := btcwallet.New(*walletConfig)
@ -327,7 +435,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
// As a final check, if we're using the RPC backend, we'll ensure that // As a final check, if we're using the RPC backend, we'll ensure that
// the btcd node has the txindex set. Atm, this is required in order to // the btcd node has the txindex set. Atm, this is required in order to
// properly perform historical confirmation+spend dispatches. // properly perform historical confirmation+spend dispatches.
if !cfg.NeutrinoMode.Active { if homeChainConfig.Node != "neutrino" {
// In order to check to see if we have the txindex up to date // In order to check to see if we have the txindex up to date
// and active, we'll try to fetch the first transaction in the // and active, we'll try to fetch the first transaction in the
// latest block via the index. If this doesn't succeed, then we // latest block via the index. If this doesn't succeed, then we
@ -344,12 +452,18 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
} }
firstTxHash := bestBlock.Transactions[0].TxHash() firstTxHash := bestBlock.Transactions[0].TxHash()
_, err = rawRPCConn.GetRawTransaction(&firstTxHash) switch homeChainConfig.Node {
case "btcd":
_, err = btcdConn.GetRawTransaction(&firstTxHash)
case "bitcoind":
_, err = bitcoindConn.GetRawTransactionVerbose(&firstTxHash)
}
if err != nil { if err != nil {
// If the node doesn't have the txindex set, then we'll // If the node doesn't have the txindex set, then we'll
// halt startup, as we can't proceed in this state. // halt startup, as we can't proceed in this state.
return nil, nil, fmt.Errorf("btcd detected to not " + return nil, nil, fmt.Errorf("%s detected to not "+
"have --txindex active, cannot proceed") "have --txindex active, cannot proceed",
homeChainConfig.Node)
} }
} }

328
config.go

@ -9,6 +9,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "sort"
@ -16,7 +17,7 @@ import (
"strings" "strings"
"time" "time"
flags "github.com/btcsuite/go-flags" flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
@ -72,17 +73,15 @@ var (
ltcdHomeDir = btcutil.AppDataDir("ltcd", false) ltcdHomeDir = btcutil.AppDataDir("ltcd", false)
defaultLtcdRPCCertFile = filepath.Join(ltcdHomeDir, "rpc.cert") defaultLtcdRPCCertFile = filepath.Join(ltcdHomeDir, "rpc.cert")
bitcoindHomeDir = btcutil.AppDataDir("bitcoin", false)
) )
type chainConfig struct { type chainConfig struct {
Active bool `long:"active" description:"If the chain should be active or not."` Active bool `long:"active" description:"If the chain should be active or not."`
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."` ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."` Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino"`
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
RPCCert string `long:"rpccert" description:"File containing the daemon's certificate file"`
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of the daemon's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"`
@ -97,7 +96,6 @@ type chainConfig struct {
} }
type neutrinoConfig struct { type neutrinoConfig struct {
Active bool `long:"active" description:"If SPV mode should be active or not."`
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"` AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"` ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"` MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
@ -105,6 +103,21 @@ type neutrinoConfig struct {
BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."` BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."`
} }
type btcdConfig struct {
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
RPCCert string `long:"rpccert" description:"File containing the daemon's certificate file"`
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of the daemon's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
}
type bitcoindConfig struct {
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
ZMQPath string `long:"zmqpath" description:"The path to the ZMQ socket providing at least raw blocks. Raw transactions can be handled as well."`
}
type autoPilotConfig struct { type autoPilotConfig struct {
// TODO(roasbeef): add // TODO(roasbeef): add
Active bool `long:"active" description:"If the autopilot agent should be active or not."` Active bool `long:"active" description:"If the autopilot agent should be active or not."`
@ -144,11 +157,14 @@ type config struct {
HodlHTLC bool `long:"hodlhtlc" description:"Activate the hodl HTLC mode. With hodl HTLC mode, all incoming HTLCs will be accepted by the receiving node, but no attempt will be made to settle the payment with the sender."` HodlHTLC bool `long:"hodlhtlc" description:"Activate the hodl HTLC mode. With hodl HTLC mode, all incoming HTLCs will be accepted by the receiving node, but no attempt will be made to settle the payment with the sender."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."` MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"` Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"`
Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"` BtcdMode *btcdConfig `group:"btcd" namespace:"btcd"`
BitcoindMode *bitcoindConfig `group:"bitcoind" namespace:"bitcoind"`
NeutrinoMode *neutrinoConfig `group:"neutrino" namespace:"neutrino"` NeutrinoMode *neutrinoConfig `group:"neutrino" namespace:"neutrino"`
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"` Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"`
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."` NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
@ -168,35 +184,44 @@ type config struct {
// 4) Parse CLI options and overwrite/add any specified options // 4) Parse CLI options and overwrite/add any specified options
func loadConfig() (*config, error) { func loadConfig() (*config, error) {
defaultCfg := config{ defaultCfg := config{
ConfigFile: defaultConfigFile, ConfigFile: defaultConfigFile,
DataDir: defaultDataDir, DataDir: defaultDataDir,
DebugLevel: defaultLogLevel, DebugLevel: defaultLogLevel,
TLSCertPath: defaultTLSCertPath, TLSCertPath: defaultTLSCertPath,
TLSKeyPath: defaultTLSKeyPath, TLSKeyPath: defaultTLSKeyPath,
AdminMacPath: defaultAdminMacPath, AdminMacPath: defaultAdminMacPath,
ReadMacPath: defaultReadMacPath, ReadMacPath: defaultReadMacPath,
LogDir: defaultLogDir, LogDir: defaultLogDir,
PeerPort: defaultPeerPort, PeerPort: defaultPeerPort,
RPCPort: defaultRPCPort, RPCPort: defaultRPCPort,
RESTPort: defaultRESTPort, RESTPort: defaultRESTPort,
MaxPendingChannels: defaultMaxPendingChannels,
NoEncryptWallet: defaultNoEncryptWallet,
Bitcoin: &chainConfig{ Bitcoin: &chainConfig{
RPCHost: defaultRPCHost,
RPCCert: defaultBtcdRPCCertFile,
MinHTLC: defaultBitcoinMinHTLCMSat, MinHTLC: defaultBitcoinMinHTLCMSat,
BaseFee: defaultBitcoinBaseFeeMSat, BaseFee: defaultBitcoinBaseFeeMSat,
FeeRate: defaultBitcoinFeeRate, FeeRate: defaultBitcoinFeeRate,
TimeLockDelta: defaultBitcoinTimeLockDelta, TimeLockDelta: defaultBitcoinTimeLockDelta,
Node: "btcd",
},
BtcdMode: &btcdConfig{
RPCHost: defaultRPCHost,
RPCCert: defaultBtcdRPCCertFile,
},
BitcoindMode: &bitcoindConfig{
RPCHost: defaultRPCHost,
}, },
Litecoin: &chainConfig{ Litecoin: &chainConfig{
RPCHost: defaultRPCHost,
RPCCert: defaultLtcdRPCCertFile,
MinHTLC: defaultLitecoinMinHTLCMSat, MinHTLC: defaultLitecoinMinHTLCMSat,
BaseFee: defaultLitecoinBaseFeeMSat, BaseFee: defaultLitecoinBaseFeeMSat,
FeeRate: defaultLitecoinFeeRate, FeeRate: defaultLitecoinFeeRate,
TimeLockDelta: defaultLitecoinTimeLockDelta, TimeLockDelta: defaultLitecoinTimeLockDelta,
Node: "btcd",
}, },
LtcdMode: &btcdConfig{
RPCHost: defaultRPCHost,
RPCCert: defaultLtcdRPCCertFile,
},
MaxPendingChannels: defaultMaxPendingChannels,
NoEncryptWallet: defaultNoEncryptWallet,
Autopilot: &autoPilotConfig{ Autopilot: &autoPilotConfig{
MaxChannels: 5, MaxChannels: 5,
Allocation: 0.6, Allocation: 0.6,
@ -252,28 +277,18 @@ func loadConfig() (*config, error) {
return nil, err return nil, err
} }
switch {
// At this moment, multiple active chains are not supported. // At this moment, multiple active chains are not supported.
if cfg.Litecoin.Active && cfg.Bitcoin.Active { case cfg.Litecoin.Active && cfg.Bitcoin.Active:
str := "%s: Currently both Bitcoin and Litecoin cannot be " + str := "%s: Currently both Bitcoin and Litecoin cannot be " +
"active together" "active together"
err := fmt.Errorf(str, funcName) return nil, fmt.Errorf(str, funcName)
return nil, err
}
switch {
// The SPV mode implemented currently doesn't support Litecoin, so the
// two modes are incompatible.
case cfg.NeutrinoMode.Active && cfg.Litecoin.Active:
str := "%s: The light client mode currently supported does " +
"not yet support execution on the Litecoin network"
err := fmt.Errorf(str, funcName)
return nil, err
// Either Bitcoin must be active, or Litecoin must be active. // Either Bitcoin must be active, or Litecoin must be active.
// Otherwise, we don't know which chain we're on. // Otherwise, we don't know which chain we're on.
case !cfg.Bitcoin.Active && !cfg.Litecoin.Active: case !cfg.Bitcoin.Active && !cfg.Litecoin.Active:
return nil, fmt.Errorf("either bitcoin.active or " + return nil, fmt.Errorf("%s: either bitcoin.active or "+
"litecoin.active must be set to 1 (true)") "litecoin.active must be set to 1 (true)", funcName)
case cfg.Litecoin.Active: case cfg.Litecoin.Active:
if cfg.Litecoin.SimNet { if cfg.Litecoin.SimNet {
@ -286,25 +301,26 @@ func loadConfig() (*config, error) {
minTimeLockDelta) minTimeLockDelta)
} }
if cfg.Litecoin.Node != "btcd" {
str := "%s: only ltcd (`btcd`) mode supported for litecoin at this time"
return nil, fmt.Errorf(str, funcName)
}
// The litecoin chain is the current active chain. However // The litecoin chain is the current active chain. However
// throughout the codebase we required chiancfg.Params. So as a // throughout the codebase we required chaincfg.Params. So as a
// temporary hack, we'll mutate the default net params for // temporary hack, we'll mutate the default net params for
// bitcoin with the litecoin specific information. // bitcoin with the litecoin specific information.
paramCopy := bitcoinTestNetParams paramCopy := bitcoinTestNetParams
applyLitecoinParams(&paramCopy) applyLitecoinParams(&paramCopy)
activeNetParams = paramCopy activeNetParams = paramCopy
if !cfg.NeutrinoMode.Active { err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode, litecoinChain,
// Attempt to parse out the RPC credentials for the funcName)
// litecoin chain if the information wasn't specified if err != nil {
err := parseRPCParams(cfg.Litecoin, litecoinChain, funcName) err := fmt.Errorf("unable to load RPC credentials for "+
if err != nil { "ltcd: %v", err)
err := fmt.Errorf("unable to load RPC credentials for "+ return nil, err
"ltcd: %v", err)
return nil, err
}
} }
cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir, litecoinChain.String()) cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir, litecoinChain.String())
// Finally we'll register the litecoin chain as our current // Finally we'll register the litecoin chain as our current
@ -340,15 +356,29 @@ func loadConfig() (*config, error) {
minTimeLockDelta) minTimeLockDelta)
} }
if !cfg.NeutrinoMode.Active { switch cfg.Bitcoin.Node {
// If needed, we'll attempt to automatically configure case "btcd":
// the RPC control plan for the target btcd node. err := parseRPCParams(cfg.Bitcoin, cfg.BtcdMode,
err := parseRPCParams(cfg.Bitcoin, bitcoinChain, funcName) bitcoinChain, funcName)
if err != nil { if err != nil {
err := fmt.Errorf("unable to load RPC credentials for "+ err := fmt.Errorf("unable to load RPC "+
"btcd: %v", err) "credentials for btcd: %v", err)
return nil, err return nil, err
} }
case "bitcoind":
if cfg.Bitcoin.SimNet {
return nil, fmt.Errorf("%s: bitcoind does not "+
"support simnet", funcName)
}
err := parseRPCParams(cfg.Bitcoin, cfg.BitcoindMode,
bitcoinChain, funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for bitcoind: %v", err)
return nil, err
}
case "neutrino":
// No need to get RPC parameters.
} }
cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir, bitcoinChain.String()) cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir, bitcoinChain.String())
@ -539,12 +569,20 @@ func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) {
} }
} }
func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error { func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
// If the rpcuser and rpcpass parameters aren't set, then we'll attempt funcName string) error {
// to automatically obtain the proper credentials for btcd and set // If the configuration has already set the RPCUser and RPCPass, and
// them within the configuration. // if we're either not using bitcoind mode or the ZMQ path is already
if cConfig.RPCUser != "" || cConfig.RPCPass != "" { // specified, we can return.
return nil switch conf := nodeConfig.(type) {
case *btcdConfig:
if conf.RPCUser != "" || conf.RPCPass != "" {
return nil
}
case *bitcoindConfig:
if conf.RPCUser != "" || conf.RPCPass != "" || conf.ZMQPath != "" {
return nil
}
} }
// If we're in simnet mode, then the running btcd instance won't read // If we're in simnet mode, then the running btcd instance won't read
@ -556,33 +594,62 @@ func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error
return fmt.Errorf(str, funcName) return fmt.Errorf(str, funcName)
} }
daemonName := "btcd" var daemonName, homeDir, confFile string
if net == litecoinChain { switch net {
daemonName = "ltcd" case bitcoinChain:
switch cConfig.Node {
case "btcd":
daemonName = "btcd"
homeDir = btcdHomeDir
confFile = "btcd"
case "bitcoind":
daemonName = "bitcoind"
homeDir = bitcoindHomeDir
confFile = "bitcoin"
}
case litecoinChain:
switch cConfig.Node {
case "btcd":
daemonName = "ltcd"
homeDir = ltcdHomeDir
confFile = "ltcd"
case "bitcoind":
return fmt.Errorf("bitcoind mode doesn't work with Litecoin yet")
}
} }
fmt.Println("Attempting automatic RPC configuration to " + daemonName) fmt.Println("Attempting automatic RPC configuration to " + daemonName)
homeDir := btcdHomeDir confFile = filepath.Join(homeDir, fmt.Sprintf("%v.conf", confFile))
if net == litecoinChain { switch cConfig.Node {
homeDir = ltcdHomeDir case "btcd":
} nConf := nodeConfig.(*btcdConfig)
confFile := filepath.Join(homeDir, fmt.Sprintf("%v.conf", daemonName)) rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
rpcUser, rpcPass, err := extractRPCParams(confFile) if err != nil {
if err != nil { return fmt.Errorf("unable to extract RPC credentials:"+
return fmt.Errorf("unable to extract RPC "+ " %v, cannot start w/o RPC connection",
"credentials: %v, cannot start w/o RPC connection", err)
err) }
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
case "bitcoind":
nConf := nodeConfig.(*bitcoindConfig)
rpcUser, rpcPass, zmqPath, err := extractBitcoindRPCParams(confFile)
if err != nil {
return fmt.Errorf("unable to extract RPC credentials:"+
" %v, cannot start w/o RPC connection",
err)
}
nConf.RPCUser, nConf.RPCPass, nConf.ZMQPath = rpcUser, rpcPass, zmqPath
} }
fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName) fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
cConfig.RPCUser, cConfig.RPCPass = rpcUser, rpcPass
return nil return nil
} }
// extractRPCParams attempts to extract the RPC credentials for an existing // extractBtcdRPCParams attempts to extract the RPC credentials for an existing
// btcd instance. The passed path is expected to be the location of btcd's // btcd instance. The passed path is expected to be the location of btcd's
// application data directory on the target system. // application data directory on the target system.
func extractRPCParams(btcdConfigPath string) (string, string, error) { func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
// First, we'll open up the btcd configuration file found at the target // First, we'll open up the btcd configuration file found at the target
// destination. // destination.
btcdConfigFile, err := os.Open(btcdConfigPath) btcdConfigFile, err := os.Open(btcdConfigPath)
@ -592,7 +659,7 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) {
defer btcdConfigFile.Close() defer btcdConfigFile.Close()
// With the file open extract the contents of the configuration file so // With the file open extract the contents of the configuration file so
// we can attempt o locate the RPC credentials. // we can attempt to locate the RPC credentials.
configContents, err := ioutil.ReadAll(btcdConfigFile) configContents, err := ioutil.ReadAll(btcdConfigFile)
if err != nil { if err != nil {
return "", "", err return "", "", err
@ -624,3 +691,100 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) {
return string(userSubmatches[1]), string(passSubmatches[1]), nil return string(userSubmatches[1]), string(passSubmatches[1]), nil
} }
// extractBitcoindParams attempts to extract the RPC credentials for an
// existing bitcoind node instance. The passed path is expected to be the
// location of bitcoind's bitcoin.conf on the target system. The routine looks
// for a cookie first, optionally following the datadir configuration option in
// the bitcoin.conf. If it doesn't find one, it looks for rpcuser/rpcpassword.
func extractBitcoindRPCParams(bitcoindConfigPath string) (string, string, string, error) {
// First, we'll open up the bitcoind configuration file found at the
// target destination.
bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
if err != nil {
return "", "", "", err
}
defer bitcoindConfigFile.Close()
// With the file open extract the contents of the configuration file so
// we can attempt to locate the RPC credentials.
configContents, err := ioutil.ReadAll(bitcoindConfigFile)
if err != nil {
return "", "", "", err
}
// First, we look for the ZMQ path for raw blocks. If raw transactions
// are sent over this interface, we can also get unconfirmed txs.
zmqPathRE, err := regexp.Compile(`(?m)^\s*zmqpubrawblock=([^\s]+)`)
if err != nil {
return "", "", "", err
}
zmqPathSubmatches := zmqPathRE.FindSubmatch(configContents)
if len(zmqPathSubmatches) < 2 {
return "", "", "", fmt.Errorf("unable to find zmqpubrawblock in config")
}
// Next, we'll try to find an auth cookie. We need to detect the chain
// by seeing if one is specified in the configuration file.
dataDir := path.Dir(bitcoindConfigPath)
dataDirRE, err := regexp.Compile(`(?m)^\s*datadir=([^\s]+)`)
if err != nil {
return "", "", "", err
}
dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
if dataDirSubmatches != nil {
dataDir = string(dataDirSubmatches[1])
}
chainDir := "/"
netRE, err := regexp.Compile(`(?m)^\s*(testnet|regtest)=[\d]+`)
if err != nil {
return "", "", "", err
}
netSubmatches := netRE.FindSubmatch(configContents)
if netSubmatches != nil {
switch string(netSubmatches[1]) {
case "testnet":
chainDir = "/testnet3/"
case "regtest":
chainDir = "/regtest/"
}
}
cookie, err := ioutil.ReadFile(dataDir + chainDir + ".cookie")
if err == nil {
splitCookie := strings.Split(string(cookie), ":")
if len(splitCookie) == 2 {
return splitCookie[0], splitCookie[1],
string(zmqPathSubmatches[1]), nil
}
}
// We didn't find a cookie, so we attempt to locate the RPC user using
// a regular expression. If we don't have a match for our regular
// expression then we'll exit with an error.
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser=([^\s]+)`)
if err != nil {
return "", "", "", err
}
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
if userSubmatches == nil {
return "", "", "", fmt.Errorf("unable to find rpcuser in config")
}
// Similarly, we'll use another regular expression to find the set
// rpcpass (if any). If we can't find the pass, then we'll exit with an
// error.
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword=([^\s]+)`)
if err != nil {
return "", "", "", err
}
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
if passSubmatches == nil {
return "", "", "", fmt.Errorf("unable to find rpcpassword in config")
}
return string(userSubmatches[1]), string(passSubmatches[1]),
string(zmqPathSubmatches[1]), nil
}

14
glide.lock generated

@ -1,5 +1,5 @@
hash: d145c16f2f9cfdf4937eb8b7cdd65919a8c351593a179acc23f2cbca5b42f34b hash: 59d73259b4445fdb5982f2ec2538f53805e6e789c26b98b0fdfef43b7d0676a4
updated: 2017-12-22T23:45:25.148488338-07:00 updated: 2018-01-12T11:04:32.444091731-07:00
imports: imports:
- name: github.com/aead/chacha20 - name: github.com/aead/chacha20
version: d31a916ded42d1640b9d89a26f8abd53cc96790c version: d31a916ded42d1640b9d89a26f8abd53cc96790c
@ -21,8 +21,8 @@ imports:
version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a
- name: github.com/btcsuite/fastsha256 - name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/btcsuite/go-flags - name: github.com/jessevdk/go-flags
version: 6c288d648c1cc1befcb90cb5511dcacf64ae8e61 version: f88afde2fa19a30cf50ba4b05b3d13bc6bae3079
- name: github.com/btcsuite/go-socks - name: github.com/btcsuite/go-socks
version: 4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f version: 4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f
subpackages: subpackages:
@ -86,8 +86,8 @@ imports:
version: 946bd9fbed05568b0f3cd188353d8aa28f38b688 version: 946bd9fbed05568b0f3cd188353d8aa28f38b688
subpackages: subpackages:
- internal/socket - internal/socket
- name: github.com/pebbe/zmq4 - name: github.com/lightninglabs/gozmq
version: 90d69e412a09549f2e90bac70fbb449081f1e5c1 version: b0bbb30a99d00b25c3304994d20aac68607b75c0
- name: github.com/roasbeef/btcd - name: github.com/roasbeef/btcd
version: 9978b939c33973be19b932fa7b936079bb7ba38d version: 9978b939c33973be19b932fa7b936079bb7ba38d
subpackages: subpackages:
@ -118,7 +118,7 @@ imports:
- hdkeychain - hdkeychain
- txsort - txsort
- name: github.com/roasbeef/btcwallet - name: github.com/roasbeef/btcwallet
version: 3037a033935cca719ba3c472870e7d080443a82f version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb
subpackages: subpackages:
- chain - chain
- internal/helpers - internal/helpers

@ -4,7 +4,7 @@ import:
version: ^1.2.1 version: ^1.2.1
- package: github.com/btcsuite/btclog - package: github.com/btcsuite/btclog
version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a
- package: github.com/btcsuite/go-flags - package: github.com/jessevdk/go-flags
- package: github.com/jrick/logrotate - package: github.com/jrick/logrotate
version: a93b200c26cbae3bb09dd0dc2c7c7fe1468a034a version: a93b200c26cbae3bb09dd0dc2c7c7fe1468a034a
- package: github.com/davecgh/go-spew - package: github.com/davecgh/go-spew
@ -24,7 +24,7 @@ import:
- txscript - txscript
- wire - wire
- connmgr - connmgr
- package: github.com/pebbe/zmq4 - package: github.com/lightninglabs/gozmq
- package: github.com/roasbeef/btcrpcclient - package: github.com/roasbeef/btcrpcclient
version: d0f4db8b4dad0ca3d569b804f21247c3dd96acbb version: d0f4db8b4dad0ca3d569b804f21247c3dd96acbb
- package: github.com/roasbeef/btcutil - package: github.com/roasbeef/btcutil
@ -35,7 +35,7 @@ import:
- hdkeychain - hdkeychain
- txsort - txsort
- package: github.com/roasbeef/btcwallet - package: github.com/roasbeef/btcwallet
version: 3037a033935cca719ba3c472870e7d080443a82f version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb
subpackages: subpackages:
- chain - chain
- waddrmgr - waddrmgr

2
lnd.go

@ -31,8 +31,8 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
flags "github.com/btcsuite/go-flags"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"

@ -124,10 +124,10 @@ func (cfg nodeConfig) genArgs() []string {
args = append(args, "--debuglevel=debug") args = append(args, "--debuglevel=debug")
args = append(args, "--bitcoin.defaultchanconfs=1") args = append(args, "--bitcoin.defaultchanconfs=1")
args = append(args, "--bitcoin.defaultremotedelay=4") args = append(args, "--bitcoin.defaultremotedelay=4")
args = append(args, fmt.Sprintf("--bitcoin.rpchost=%v", cfg.RPCConfig.Host)) args = append(args, fmt.Sprintf("--btcd.rpchost=%v", cfg.RPCConfig.Host))
args = append(args, fmt.Sprintf("--bitcoin.rpcuser=%v", cfg.RPCConfig.User)) args = append(args, fmt.Sprintf("--btcd.rpcuser=%v", cfg.RPCConfig.User))
args = append(args, fmt.Sprintf("--bitcoin.rpcpass=%v", cfg.RPCConfig.Pass)) args = append(args, fmt.Sprintf("--btcd.rpcpass=%v", cfg.RPCConfig.Pass))
args = append(args, fmt.Sprintf("--bitcoin.rawrpccert=%v", encodedCert)) args = append(args, fmt.Sprintf("--btcd.rawrpccert=%v", encodedCert))
args = append(args, fmt.Sprintf("--rpcport=%v", cfg.RPCPort)) args = append(args, fmt.Sprintf("--rpcport=%v", cfg.RPCPort))
args = append(args, fmt.Sprintf("--peerport=%v", cfg.P2PPort)) args = append(args, fmt.Sprintf("--peerport=%v", cfg.P2PPort))
args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir)) args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir))

@ -183,6 +183,11 @@ type mockWalletController struct {
publishedTransactions chan *wire.MsgTx publishedTransactions chan *wire.MsgTx
} }
// BackEnd returns "mock" to signify a mock wallet controller.
func (*mockWalletController) BackEnd() string {
return "mock"
}
// FetchInputInfo will be called to get info about the inputs to the funding // FetchInputInfo will be called to get info about the inputs to the funding
// transaction. // transaction.
func (*mockWalletController) FetchInputInfo( func (*mockWalletController) FetchInputInfo(