From 9a02884d0bd16bc36ad79b0e576a3a4604ebfc79 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 21 Dec 2017 22:23:24 -0700 Subject: [PATCH] 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. --- chainregistry.go | 150 +++++++++++++++++++--- config.go | 328 +++++++++++++++++++++++++++++++++++------------ glide.lock | 14 +- glide.yaml | 6 +- lnd.go | 2 +- lntest/node.go | 8 +- mock.go | 5 + 7 files changed, 398 insertions(+), 115 deletions(-) diff --git a/chainregistry.go b/chainregistry.go index 675ad5cb..160d91f2 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -4,14 +4,17 @@ import ( "encoding/hex" "fmt" "io/ioutil" + "net" "os" "path/filepath" + "strconv" "strings" "sync" "time" "github.com/lightninglabs/neutrino" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" "github.com/lightningnetwork/lnd/channeldb" @@ -134,15 +137,17 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, } var ( - err error - cleanUp func() - rawRPCConn *chain.RPCClient + err error + cleanUp func() + btcdConn *chain.RPCClient + bitcoindConn *chain.BitcoindClient ) // If spv mode is active, then we'll be using a distinct set of // chainControl interfaces that interface directly with the p2p network // of the selected chain. - if cfg.NeutrinoMode.Active { + switch homeChainConfig.Node { + case "neutrino": // First we'll open the database file for neutrino, creating // the database if needed. dbName := filepath.Join(cfg.DataDir, "neutrino.db") @@ -189,21 +194,121 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, cleanUp = func() { 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. // // So first we'll load btcd/ltcd's TLS cert for the RPC // connection. If a raw cert was specified in the config, then // we'll set that directly. Otherwise, we attempt to read the // 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 - if homeChainConfig.RawRPCCert != "" { - rpcCert, err = hex.DecodeString(homeChainConfig.RawRPCCert) + if btcdMode.RawRPCCert != "" { + rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert) if err != nil { return nil, nil, err } } else { - certFile, err := os.Open(homeChainConfig.RPCCert) + certFile, err := os.Open(btcdMode.RPCCert) if err != nil { 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 // parameters. var btcdHost string - if strings.Contains(homeChainConfig.RPCHost, ":") { - btcdHost = homeChainConfig.RPCHost + if strings.Contains(btcdMode.RPCHost, ":") { + btcdHost = btcdMode.RPCHost } else { - btcdHost = fmt.Sprintf("%v:%v", homeChainConfig.RPCHost, + btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost, activeNetParams.rpcPort) } - btcdUser := homeChainConfig.RPCUser - btcdPass := homeChainConfig.RPCPass + btcdUser := btcdMode.RPCUser + btcdPass := btcdMode.RPCPass rpcConfig := &rpcclient.ConnConfig{ Host: btcdHost, Endpoint: "ws", @@ -262,7 +367,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, } walletConfig.ChainSource = chainRPC - rawRPCConn = chainRPC + btcdConn = chainRPC // If we're not in simnet or regtest mode, then we'll attempt // to use a proper fee estimator for testnet. @@ -286,6 +391,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, return nil, nil, err } } + default: + return nil, nil, fmt.Errorf("unknown node type: %s", + homeChainConfig.Node) } 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 // the btcd node has the txindex set. Atm, this is required in order to // 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 // and active, we'll try to fetch the first transaction in the // 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() - _, err = rawRPCConn.GetRawTransaction(&firstTxHash) + switch homeChainConfig.Node { + case "btcd": + _, err = btcdConn.GetRawTransaction(&firstTxHash) + case "bitcoind": + _, err = bitcoindConn.GetRawTransactionVerbose(&firstTxHash) + } if err != nil { // If the node doesn't have the txindex set, then we'll // halt startup, as we can't proceed in this state. - return nil, nil, fmt.Errorf("btcd detected to not " + - "have --txindex active, cannot proceed") + return nil, nil, fmt.Errorf("%s detected to not "+ + "have --txindex active, cannot proceed", + homeChainConfig.Node) } } diff --git a/config.go b/config.go index 4ce84bed..3258512c 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "path" "path/filepath" "regexp" "sort" @@ -16,7 +17,7 @@ import ( "strings" "time" - flags "github.com/btcsuite/go-flags" + flags "github.com/jessevdk/go-flags" "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/btcec" @@ -72,17 +73,15 @@ var ( ltcdHomeDir = btcutil.AppDataDir("ltcd", false) defaultLtcdRPCCertFile = filepath.Join(ltcdHomeDir, "rpc.cert") + + bitcoindHomeDir = btcutil.AppDataDir("bitcoin", false) ) type chainConfig struct { 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."` - 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."` + Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino"` TestNet3 bool `long:"testnet" description:"Use the test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"` @@ -97,7 +96,6 @@ type chainConfig 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"` 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"` @@ -105,6 +103,21 @@ type neutrinoConfig struct { 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 { // TODO(roasbeef): add 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."` 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"` + Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"` + LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"` + Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"` 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 func loadConfig() (*config, error) { defaultCfg := config{ - ConfigFile: defaultConfigFile, - DataDir: defaultDataDir, - DebugLevel: defaultLogLevel, - TLSCertPath: defaultTLSCertPath, - TLSKeyPath: defaultTLSKeyPath, - AdminMacPath: defaultAdminMacPath, - ReadMacPath: defaultReadMacPath, - LogDir: defaultLogDir, - PeerPort: defaultPeerPort, - RPCPort: defaultRPCPort, - RESTPort: defaultRESTPort, - MaxPendingChannels: defaultMaxPendingChannels, - NoEncryptWallet: defaultNoEncryptWallet, + ConfigFile: defaultConfigFile, + DataDir: defaultDataDir, + DebugLevel: defaultLogLevel, + TLSCertPath: defaultTLSCertPath, + TLSKeyPath: defaultTLSKeyPath, + AdminMacPath: defaultAdminMacPath, + ReadMacPath: defaultReadMacPath, + LogDir: defaultLogDir, + PeerPort: defaultPeerPort, + RPCPort: defaultRPCPort, + RESTPort: defaultRESTPort, Bitcoin: &chainConfig{ - RPCHost: defaultRPCHost, - RPCCert: defaultBtcdRPCCertFile, MinHTLC: defaultBitcoinMinHTLCMSat, BaseFee: defaultBitcoinBaseFeeMSat, FeeRate: defaultBitcoinFeeRate, TimeLockDelta: defaultBitcoinTimeLockDelta, + Node: "btcd", + }, + BtcdMode: &btcdConfig{ + RPCHost: defaultRPCHost, + RPCCert: defaultBtcdRPCCertFile, + }, + BitcoindMode: &bitcoindConfig{ + RPCHost: defaultRPCHost, }, Litecoin: &chainConfig{ - RPCHost: defaultRPCHost, - RPCCert: defaultLtcdRPCCertFile, MinHTLC: defaultLitecoinMinHTLCMSat, BaseFee: defaultLitecoinBaseFeeMSat, FeeRate: defaultLitecoinFeeRate, TimeLockDelta: defaultLitecoinTimeLockDelta, + Node: "btcd", }, + LtcdMode: &btcdConfig{ + RPCHost: defaultRPCHost, + RPCCert: defaultLtcdRPCCertFile, + }, + MaxPendingChannels: defaultMaxPendingChannels, + NoEncryptWallet: defaultNoEncryptWallet, Autopilot: &autoPilotConfig{ MaxChannels: 5, Allocation: 0.6, @@ -252,28 +277,18 @@ func loadConfig() (*config, error) { return nil, err } + switch { // 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 " + "active together" - err := 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 + return nil, fmt.Errorf(str, funcName) // Either Bitcoin must be active, or Litecoin must be active. // Otherwise, we don't know which chain we're on. case !cfg.Bitcoin.Active && !cfg.Litecoin.Active: - return nil, fmt.Errorf("either bitcoin.active or " + - "litecoin.active must be set to 1 (true)") + return nil, fmt.Errorf("%s: either bitcoin.active or "+ + "litecoin.active must be set to 1 (true)", funcName) case cfg.Litecoin.Active: if cfg.Litecoin.SimNet { @@ -286,25 +301,26 @@ func loadConfig() (*config, error) { 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 - // 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 // bitcoin with the litecoin specific information. paramCopy := bitcoinTestNetParams applyLitecoinParams(¶mCopy) activeNetParams = paramCopy - if !cfg.NeutrinoMode.Active { - // Attempt to parse out the RPC credentials for the - // litecoin chain if the information wasn't specified - err := parseRPCParams(cfg.Litecoin, litecoinChain, funcName) - if err != nil { - err := fmt.Errorf("unable to load RPC credentials for "+ - "ltcd: %v", err) - return nil, err - } + err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode, litecoinChain, + funcName) + if err != nil { + err := fmt.Errorf("unable to load RPC credentials for "+ + "ltcd: %v", err) + return nil, err } - cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir, litecoinChain.String()) // Finally we'll register the litecoin chain as our current @@ -340,15 +356,29 @@ func loadConfig() (*config, error) { minTimeLockDelta) } - if !cfg.NeutrinoMode.Active { - // If needed, we'll attempt to automatically configure - // the RPC control plan for the target btcd node. - err := parseRPCParams(cfg.Bitcoin, bitcoinChain, funcName) + switch cfg.Bitcoin.Node { + case "btcd": + err := parseRPCParams(cfg.Bitcoin, cfg.BtcdMode, + bitcoinChain, funcName) if err != nil { - err := fmt.Errorf("unable to load RPC credentials for "+ - "btcd: %v", err) + err := fmt.Errorf("unable to load RPC "+ + "credentials for btcd: %v", 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()) @@ -539,12 +569,20 @@ func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) { } } -func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error { - // If the rpcuser and rpcpass parameters aren't set, then we'll attempt - // to automatically obtain the proper credentials for btcd and set - // them within the configuration. - if cConfig.RPCUser != "" || cConfig.RPCPass != "" { - return nil +func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode, + funcName string) error { + // If the configuration has already set the RPCUser and RPCPass, and + // if we're either not using bitcoind mode or the ZMQ path is already + // specified, we can return. + 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 @@ -556,33 +594,62 @@ func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error return fmt.Errorf(str, funcName) } - daemonName := "btcd" - if net == litecoinChain { - daemonName = "ltcd" + var daemonName, homeDir, confFile string + switch net { + 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) - homeDir := btcdHomeDir - if net == litecoinChain { - homeDir = ltcdHomeDir - } - confFile := filepath.Join(homeDir, fmt.Sprintf("%v.conf", daemonName)) - rpcUser, rpcPass, err := extractRPCParams(confFile) - if err != nil { - return fmt.Errorf("unable to extract RPC "+ - "credentials: %v, cannot start w/o RPC connection", - err) + confFile = filepath.Join(homeDir, fmt.Sprintf("%v.conf", confFile)) + switch cConfig.Node { + case "btcd": + nConf := nodeConfig.(*btcdConfig) + rpcUser, rpcPass, err := extractBtcdRPCParams(confFile) + if err != nil { + return fmt.Errorf("unable to extract RPC credentials:"+ + " %v, cannot start w/o RPC connection", + 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) - cConfig.RPCUser, cConfig.RPCPass = rpcUser, rpcPass 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 // 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 // destination. btcdConfigFile, err := os.Open(btcdConfigPath) @@ -592,7 +659,7 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) { defer btcdConfigFile.Close() // 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) if err != nil { return "", "", err @@ -624,3 +691,100 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) { 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 +} diff --git a/glide.lock b/glide.lock index b6721ecc..373c9d12 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: d145c16f2f9cfdf4937eb8b7cdd65919a8c351593a179acc23f2cbca5b42f34b -updated: 2017-12-22T23:45:25.148488338-07:00 +hash: 59d73259b4445fdb5982f2ec2538f53805e6e789c26b98b0fdfef43b7d0676a4 +updated: 2018-01-12T11:04:32.444091731-07:00 imports: - name: github.com/aead/chacha20 version: d31a916ded42d1640b9d89a26f8abd53cc96790c @@ -21,8 +21,8 @@ imports: version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a - name: github.com/btcsuite/fastsha256 version: 637e656429416087660c84436a2a035d69d54e2e -- name: github.com/btcsuite/go-flags - version: 6c288d648c1cc1befcb90cb5511dcacf64ae8e61 +- name: github.com/jessevdk/go-flags + version: f88afde2fa19a30cf50ba4b05b3d13bc6bae3079 - name: github.com/btcsuite/go-socks version: 4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f subpackages: @@ -86,8 +86,8 @@ imports: version: 946bd9fbed05568b0f3cd188353d8aa28f38b688 subpackages: - internal/socket -- name: github.com/pebbe/zmq4 - version: 90d69e412a09549f2e90bac70fbb449081f1e5c1 +- name: github.com/lightninglabs/gozmq + version: b0bbb30a99d00b25c3304994d20aac68607b75c0 - name: github.com/roasbeef/btcd version: 9978b939c33973be19b932fa7b936079bb7ba38d subpackages: @@ -118,7 +118,7 @@ imports: - hdkeychain - txsort - name: github.com/roasbeef/btcwallet - version: 3037a033935cca719ba3c472870e7d080443a82f + version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb subpackages: - chain - internal/helpers diff --git a/glide.yaml b/glide.yaml index 0174fc59..6cd704bf 100644 --- a/glide.yaml +++ b/glide.yaml @@ -4,7 +4,7 @@ import: version: ^1.2.1 - package: github.com/btcsuite/btclog version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a -- package: github.com/btcsuite/go-flags +- package: github.com/jessevdk/go-flags - package: github.com/jrick/logrotate version: a93b200c26cbae3bb09dd0dc2c7c7fe1468a034a - package: github.com/davecgh/go-spew @@ -24,7 +24,7 @@ import: - txscript - wire - connmgr -- package: github.com/pebbe/zmq4 +- package: github.com/lightninglabs/gozmq - package: github.com/roasbeef/btcrpcclient version: d0f4db8b4dad0ca3d569b804f21247c3dd96acbb - package: github.com/roasbeef/btcutil @@ -35,7 +35,7 @@ import: - hdkeychain - txsort - package: github.com/roasbeef/btcwallet - version: 3037a033935cca719ba3c472870e7d080443a82f + version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb subpackages: - chain - waddrmgr diff --git a/lnd.go b/lnd.go index af9e0bb4..46e281ac 100644 --- a/lnd.go +++ b/lnd.go @@ -31,8 +31,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" - flags "github.com/btcsuite/go-flags" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" + flags "github.com/jessevdk/go-flags" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" diff --git a/lntest/node.go b/lntest/node.go index 43f93292..37af9168 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -124,10 +124,10 @@ func (cfg nodeConfig) genArgs() []string { args = append(args, "--debuglevel=debug") args = append(args, "--bitcoin.defaultchanconfs=1") args = append(args, "--bitcoin.defaultremotedelay=4") - args = append(args, fmt.Sprintf("--bitcoin.rpchost=%v", cfg.RPCConfig.Host)) - args = append(args, fmt.Sprintf("--bitcoin.rpcuser=%v", cfg.RPCConfig.User)) - args = append(args, fmt.Sprintf("--bitcoin.rpcpass=%v", cfg.RPCConfig.Pass)) - args = append(args, fmt.Sprintf("--bitcoin.rawrpccert=%v", encodedCert)) + args = append(args, fmt.Sprintf("--btcd.rpchost=%v", cfg.RPCConfig.Host)) + args = append(args, fmt.Sprintf("--btcd.rpcuser=%v", cfg.RPCConfig.User)) + args = append(args, fmt.Sprintf("--btcd.rpcpass=%v", cfg.RPCConfig.Pass)) + args = append(args, fmt.Sprintf("--btcd.rawrpccert=%v", encodedCert)) args = append(args, fmt.Sprintf("--rpcport=%v", cfg.RPCPort)) args = append(args, fmt.Sprintf("--peerport=%v", cfg.P2PPort)) args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir)) diff --git a/mock.go b/mock.go index 3a846f6a..ec5e0573 100644 --- a/mock.go +++ b/mock.go @@ -183,6 +183,11 @@ type mockWalletController struct { 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 // transaction. func (*mockWalletController) FetchInputInfo(