multi: use uptime api for bitcoind healthcheck if version > 0.15
The getblockchaininfo call in bitcoind uses a commonly used lock, csmain, in bitcoind. This made the endpoint unsuitable for a health check, because some nodes were seeing waits up to 5 minutes (!). This commit updates our health check function to use the uptime api, provided our bitcoind version is > 0.15, when the api was added. We do not need to switch our health check for btcd, because it has more granular locking.
This commit is contained in:
parent
339a9d3915
commit
ca3b36c4b1
@ -2,6 +2,7 @@ package chainreg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -177,6 +178,11 @@ type ChainControl struct {
|
|||||||
// ChainIO represents an abstraction over a source that can query the blockchain.
|
// ChainIO represents an abstraction over a source that can query the blockchain.
|
||||||
ChainIO lnwallet.BlockChainIO
|
ChainIO lnwallet.BlockChainIO
|
||||||
|
|
||||||
|
// HealthCheck is a function which can be used to send a low-cost, fast
|
||||||
|
// query to the chain backend to ensure we still have access to our
|
||||||
|
// node.
|
||||||
|
HealthCheck func() error
|
||||||
|
|
||||||
// FeeEstimator is used to estimate an optimal fee for transactions important to us.
|
// FeeEstimator is used to estimate an optimal fee for transactions important to us.
|
||||||
FeeEstimator chainfee.Estimator
|
FeeEstimator chainfee.Estimator
|
||||||
|
|
||||||
@ -318,6 +324,12 @@ func NewChainControl(cfg *Config) (*ChainControl, error) {
|
|||||||
cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
|
cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Get our best block as a health check.
|
||||||
|
cc.HealthCheck = func() error {
|
||||||
|
_, _, err := walletConfig.ChainSource.GetBestBlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case "bitcoind", "litecoind":
|
case "bitcoind", "litecoind":
|
||||||
var bitcoindMode *lncfg.Bitcoind
|
var bitcoindMode *lncfg.Bitcoind
|
||||||
switch {
|
switch {
|
||||||
@ -431,6 +443,27 @@ func NewChainControl(cfg *Config) (*ChainControl, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to use some apis that are not exposed by btcwallet,
|
||||||
|
// for a health check function so we create an ad-hoc bitcoind
|
||||||
|
// connection.
|
||||||
|
chainConn, err := rpcclient.New(rpcConfig, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The api we will use for our health check depends on the
|
||||||
|
// bitcoind version.
|
||||||
|
cmd, err := getBitcoindHealthCheckCmd(chainConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.HealthCheck = func() error {
|
||||||
|
_, err := chainConn.RawRequest(cmd, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case "btcd", "ltcd":
|
case "btcd", "ltcd":
|
||||||
// Otherwise, we'll be speaking directly via RPC to a node.
|
// Otherwise, we'll be speaking directly via RPC to a node.
|
||||||
//
|
//
|
||||||
@ -514,6 +547,12 @@ func NewChainControl(cfg *Config) (*ChainControl, error) {
|
|||||||
|
|
||||||
walletConfig.ChainSource = chainRPC
|
walletConfig.ChainSource = chainRPC
|
||||||
|
|
||||||
|
// Use a query for our best block as a health check.
|
||||||
|
cc.HealthCheck = func() error {
|
||||||
|
_, _, err := walletConfig.ChainSource.GetBestBlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
if !cfg.Bitcoin.SimNet && !cfg.Litecoin.SimNet &&
|
if !cfg.Bitcoin.SimNet && !cfg.Litecoin.SimNet &&
|
||||||
@ -612,6 +651,39 @@ func NewChainControl(cfg *Config) (*ChainControl, error) {
|
|||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBitcoindHealthCheckCmd queries bitcoind for its version to decide which
|
||||||
|
// api we should use for our health check. We prefer to use the uptime
|
||||||
|
// command, because it has no locking and is an inexpensive call, which was
|
||||||
|
// added in version 0.15. If we are on an earlier version, we fallback to using
|
||||||
|
// getblockchaininfo.
|
||||||
|
func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, error) {
|
||||||
|
// Query bitcoind to get our current version.
|
||||||
|
resp, err := client.RawRequest("getnetworkinfo", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response to retrieve bitcoind's version.
|
||||||
|
info := struct {
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(resp, &info); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitcoind returns a single value representing the semantic version:
|
||||||
|
// 1000000 * CLIENT_VERSION_MAJOR + 10000 * CLIENT_VERSION_MINOR
|
||||||
|
// + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD
|
||||||
|
//
|
||||||
|
// The uptime call was added in version 0.15.0, so we return it for
|
||||||
|
// any version value >= 150000, as per the above calculation.
|
||||||
|
if info.Version >= 150000 {
|
||||||
|
return "uptime", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "getblockchaininfo", nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
|
// BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
|
||||||
// chain.
|
// chain.
|
||||||
|
@ -1292,10 +1292,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
// will not run it.
|
// will not run it.
|
||||||
chainHealthCheck := healthcheck.NewObservation(
|
chainHealthCheck := healthcheck.NewObservation(
|
||||||
"chain backend",
|
"chain backend",
|
||||||
func() error {
|
cc.HealthCheck,
|
||||||
_, _, err := cc.ChainIO.GetBestBlock()
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
cfg.HealthChecks.ChainCheck.Interval,
|
cfg.HealthChecks.ChainCheck.Interval,
|
||||||
cfg.HealthChecks.ChainCheck.Timeout,
|
cfg.HealthChecks.ChainCheck.Timeout,
|
||||||
cfg.HealthChecks.ChainCheck.Backoff,
|
cfg.HealthChecks.ChainCheck.Backoff,
|
||||||
|
Loading…
Reference in New Issue
Block a user