mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
3837c3f12e
Add fee.min-update-timeout and fee.max-update-timeout config options to allow configuration of the web fee estimator cache.
913 lines
28 KiB
Go
913 lines
28 KiB
Go
package chainreg
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/rpcclient"
|
|
"github.com/btcsuite/btcwallet/chain"
|
|
"github.com/lightninglabs/neutrino"
|
|
"github.com/lightningnetwork/lnd/blockcache"
|
|
"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"
|
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/chainview"
|
|
"github.com/lightningnetwork/lnd/walletunlocker"
|
|
)
|
|
|
|
// Config houses necessary fields that a chainControl instance needs to
|
|
// function.
|
|
type Config struct {
|
|
// Bitcoin defines settings for the Bitcoin chain.
|
|
Bitcoin *lncfg.Chain
|
|
|
|
// HeightHintCacheQueryDisable is a boolean that disables height hint
|
|
// queries if true.
|
|
HeightHintCacheQueryDisable bool
|
|
|
|
// NeutrinoMode defines settings for connecting to a neutrino
|
|
// light-client.
|
|
NeutrinoMode *lncfg.Neutrino
|
|
|
|
// BitcoindMode defines settings for connecting to a bitcoind node.
|
|
BitcoindMode *lncfg.Bitcoind
|
|
|
|
// BtcdMode defines settings for connecting to a btcd node.
|
|
BtcdMode *lncfg.Btcd
|
|
|
|
// HeightHintDB is a pointer to the database that stores the height
|
|
// hints.
|
|
HeightHintDB kvdb.Backend
|
|
|
|
// ChanStateDB is a pointer to the database that stores the channel
|
|
// state.
|
|
ChanStateDB *channeldb.ChannelStateDB
|
|
|
|
// BlockCache is the main cache for storing block information.
|
|
BlockCache *blockcache.BlockCache
|
|
|
|
// WalletUnlockParams are the parameters that were used for unlocking
|
|
// the main wallet.
|
|
WalletUnlockParams *walletunlocker.WalletUnlockParams
|
|
|
|
// NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil
|
|
// if using neutrino.
|
|
NeutrinoCS *neutrino.ChainService
|
|
|
|
// ActiveNetParams details the current chain we are on.
|
|
ActiveNetParams BitcoinNetParams
|
|
|
|
// Deprecated: Use Fee.URL. FeeURL defines the URL for fee estimation
|
|
// we will use. This field is optional.
|
|
FeeURL string
|
|
|
|
// Fee defines settings for the web fee estimator. This field is
|
|
// optional.
|
|
Fee *lncfg.Fee
|
|
|
|
// Dialer is a function closure that will be used to establish outbound
|
|
// TCP connections to Bitcoin peers in the event of a pruned block being
|
|
// requested.
|
|
Dialer chain.Dialer
|
|
}
|
|
|
|
const (
|
|
// DefaultBitcoinMinHTLCInMSat is the default smallest value htlc this
|
|
// node will accept. This value is proposed in the channel open sequence
|
|
// and cannot be changed during the life of the channel. It is 1 msat by
|
|
// default to allow maximum flexibility in deciding what size payments
|
|
// to forward.
|
|
//
|
|
// All forwarded payments are subjected to the min htlc constraint of
|
|
// the routing policy of the outgoing channel. This implicitly controls
|
|
// the minimum htlc value on the incoming channel too.
|
|
DefaultBitcoinMinHTLCInMSat = lnwire.MilliSatoshi(1)
|
|
|
|
// DefaultBitcoinMinHTLCOutMSat is the default minimum htlc value that
|
|
// we require for sending out htlcs. Our channel peer may have a lower
|
|
// min htlc channel parameter, but we - by default - don't forward
|
|
// anything under the value defined here.
|
|
DefaultBitcoinMinHTLCOutMSat = lnwire.MilliSatoshi(1000)
|
|
|
|
// DefaultBitcoinBaseFeeMSat is the default forwarding base fee.
|
|
DefaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
|
|
|
|
// DefaultBitcoinFeeRate is the default forwarding fee rate.
|
|
DefaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
|
|
|
|
// DefaultBitcoinTimeLockDelta is the default forwarding time lock
|
|
// delta.
|
|
DefaultBitcoinTimeLockDelta = 80
|
|
|
|
// DefaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
|
|
// expressed in sat/kw.
|
|
DefaultBitcoinStaticFeePerKW = chainfee.SatPerKWeight(12500)
|
|
|
|
// DefaultBitcoinStaticMinRelayFeeRate is the min relay fee used for
|
|
// static estimators.
|
|
DefaultBitcoinStaticMinRelayFeeRate = chainfee.FeePerKwFloor
|
|
|
|
// DefaultMinOutboundPeers is the min number of connected
|
|
// outbound peers the chain backend should have to maintain a
|
|
// healthy connection to the network.
|
|
DefaultMinOutboundPeers = 6
|
|
)
|
|
|
|
// PartialChainControl contains all the primary interfaces of the chain control
|
|
// that can be purely constructed from the global configuration. No wallet
|
|
// instance is required for constructing this partial state.
|
|
type PartialChainControl struct {
|
|
// Cfg is the configuration that was used to create the partial chain
|
|
// control.
|
|
Cfg *Config
|
|
|
|
// 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 chainfee.Estimator
|
|
|
|
// ChainNotifier is used to receive blockchain events that we are
|
|
// interested in.
|
|
ChainNotifier chainntnfs.ChainNotifier
|
|
|
|
// BestBlockTracker is used to maintain a view of the global
|
|
// chain state that changes over time
|
|
BestBlockTracker *chainntnfs.BestBlockTracker
|
|
|
|
// MempoolNotifier is used to watch for spending events happened in
|
|
// mempool.
|
|
MempoolNotifier chainntnfs.MempoolWatcher
|
|
|
|
// ChainView is used in the router for maintaining an up-to-date graph.
|
|
ChainView chainview.FilteredChainView
|
|
|
|
// ChainSource is the primary chain interface. This is used to operate
|
|
// the wallet and do things such as rescanning, sending transactions,
|
|
// notifications for received funds, etc.
|
|
ChainSource chain.Interface
|
|
|
|
// RoutingPolicy is the routing policy we have decided to use.
|
|
RoutingPolicy models.ForwardingPolicy
|
|
|
|
// MinHtlcIn is the minimum HTLC we will accept.
|
|
MinHtlcIn lnwire.MilliSatoshi
|
|
}
|
|
|
|
// ChainControl couples the three primary interfaces lnd utilizes for a
|
|
// particular chain together. A single ChainControl instance will exist for all
|
|
// the chains lnd is currently active on.
|
|
type ChainControl struct {
|
|
// PartialChainControl is the part of the chain control that was
|
|
// initialized purely from the configuration and doesn't contain any
|
|
// wallet related elements.
|
|
*PartialChainControl
|
|
|
|
// ChainIO represents an abstraction over a source that can query the
|
|
// blockchain.
|
|
ChainIO lnwallet.BlockChainIO
|
|
|
|
// Signer is used to provide signatures over things like transactions.
|
|
Signer input.Signer
|
|
|
|
// KeyRing represents a set of keys that we have the private keys to.
|
|
KeyRing keychain.SecretKeyRing
|
|
|
|
// Wc is an abstraction over some basic wallet commands. This base set
|
|
// of commands will be provided to the Wallet *LightningWallet raw
|
|
// pointer below.
|
|
Wc lnwallet.WalletController
|
|
|
|
// MsgSigner is used to sign arbitrary messages.
|
|
MsgSigner lnwallet.MessageSigner
|
|
|
|
// Wallet is our LightningWallet that also contains the abstract Wc
|
|
// above. This wallet handles all of the lightning operations.
|
|
Wallet *lnwallet.LightningWallet
|
|
}
|
|
|
|
// NewPartialChainControl creates a new partial chain control that contains all
|
|
// the parts that can be purely constructed from the passed in global
|
|
// configuration and doesn't need any wallet instance yet.
|
|
//
|
|
//nolint:lll
|
|
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
|
|
cc := &PartialChainControl{
|
|
Cfg: cfg,
|
|
RoutingPolicy: models.ForwardingPolicy{
|
|
MinHTLCOut: cfg.Bitcoin.MinHTLCOut,
|
|
BaseFee: cfg.Bitcoin.BaseFee,
|
|
FeeRate: cfg.Bitcoin.FeeRate,
|
|
TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
|
|
},
|
|
MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
|
|
FeeEstimator: chainfee.NewStaticEstimator(
|
|
DefaultBitcoinStaticFeePerKW,
|
|
DefaultBitcoinStaticMinRelayFeeRate,
|
|
),
|
|
}
|
|
|
|
var err error
|
|
heightHintCacheConfig := channeldb.CacheConfig{
|
|
QueryDisable: cfg.HeightHintCacheQueryDisable,
|
|
}
|
|
if cfg.HeightHintCacheQueryDisable {
|
|
log.Infof("Height Hint Cache Queries disabled")
|
|
}
|
|
|
|
// Initialize the height hint cache within the chain directory.
|
|
hintCache, err := channeldb.NewHeightHintCache(
|
|
heightHintCacheConfig, cfg.HeightHintDB,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to initialize height hint "+
|
|
"cache: %v", err)
|
|
}
|
|
|
|
// Map the deprecated feeurl flag to fee.url.
|
|
if cfg.FeeURL != "" {
|
|
if cfg.Fee.URL != "" {
|
|
return nil, nil, errors.New("fee.url and " +
|
|
"feeurl are mutually exclusive")
|
|
}
|
|
|
|
cfg.Fee.URL = cfg.FeeURL
|
|
}
|
|
|
|
// 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.
|
|
switch cfg.Bitcoin.Node {
|
|
case "neutrino":
|
|
// We'll create ChainNotifier and FilteredChainView instances,
|
|
// along with the wallet's ChainSource, which are all backed by
|
|
// the neutrino light client.
|
|
cc.ChainNotifier = neutrinonotify.New(
|
|
cfg.NeutrinoCS, hintCache, hintCache, cfg.BlockCache,
|
|
)
|
|
cc.ChainView, err = chainview.NewCfFilteredChainView(
|
|
cfg.NeutrinoCS, cfg.BlockCache,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cc.ChainSource = chain.NewNeutrinoClient(
|
|
cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
|
|
)
|
|
|
|
// Get our best block as a health check.
|
|
cc.HealthCheck = func() error {
|
|
_, _, err := cc.ChainSource.GetBestBlock()
|
|
return err
|
|
}
|
|
|
|
case "bitcoind":
|
|
bitcoindMode := cfg.BitcoindMode
|
|
|
|
// Otherwise, we'll be speaking directly via RPC and ZMQ to a
|
|
// bitcoind node. If the specified host for the btcd 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(bitcoindMode.RPCHost, ":") {
|
|
bitcoindHost = 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(cfg.ActiveNetParams.RPCPort)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
rpcPort -= 2
|
|
bitcoindHost = fmt.Sprintf("%v:%d",
|
|
bitcoindMode.RPCHost, rpcPort)
|
|
if cfg.Bitcoin.RegTest || cfg.Bitcoin.SigNet {
|
|
conn, err := net.Dial("tcp", bitcoindHost)
|
|
if err != nil || conn == nil {
|
|
switch {
|
|
case cfg.Bitcoin.RegTest:
|
|
rpcPort = 18443
|
|
case cfg.Bitcoin.SigNet:
|
|
rpcPort = 38332
|
|
}
|
|
bitcoindHost = fmt.Sprintf("%v:%d",
|
|
bitcoindMode.RPCHost,
|
|
rpcPort)
|
|
} else {
|
|
conn.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
bitcoindCfg := &chain.BitcoindConfig{
|
|
ChainParams: cfg.ActiveNetParams.Params,
|
|
Host: bitcoindHost,
|
|
User: bitcoindMode.RPCUser,
|
|
Pass: bitcoindMode.RPCPass,
|
|
Dialer: cfg.Dialer,
|
|
PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers,
|
|
}
|
|
|
|
if bitcoindMode.RPCPolling {
|
|
bitcoindCfg.PollingConfig = &chain.PollingConfig{
|
|
BlockPollingInterval: bitcoindMode.BlockPollingInterval,
|
|
TxPollingInterval: bitcoindMode.TxPollingInterval,
|
|
TxPollingIntervalJitter: lncfg.DefaultTxPollingJitter,
|
|
}
|
|
} else {
|
|
bitcoindCfg.ZMQConfig = &chain.ZMQConfig{
|
|
ZMQBlockHost: bitcoindMode.ZMQPubRawBlock,
|
|
ZMQTxHost: bitcoindMode.ZMQPubRawTx,
|
|
ZMQReadDeadline: bitcoindMode.ZMQReadDeadline,
|
|
MempoolPollingInterval: bitcoindMode.TxPollingInterval,
|
|
PollingIntervalJitter: lncfg.DefaultTxPollingJitter,
|
|
}
|
|
}
|
|
|
|
// Establish the connection to bitcoind and create the clients
|
|
// required for our relevant subsystems.
|
|
bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if err := bitcoindConn.Start(); err != nil {
|
|
return nil, nil, fmt.Errorf("unable to connect to "+
|
|
"bitcoind: %v", err)
|
|
}
|
|
|
|
chainNotifier := bitcoindnotify.New(
|
|
bitcoindConn, cfg.ActiveNetParams.Params, hintCache,
|
|
hintCache, cfg.BlockCache,
|
|
)
|
|
|
|
cc.ChainNotifier = chainNotifier
|
|
cc.MempoolNotifier = chainNotifier
|
|
|
|
cc.ChainView = chainview.NewBitcoindFilteredChainView(
|
|
bitcoindConn, cfg.BlockCache,
|
|
)
|
|
cc.ChainSource = bitcoindConn.NewBitcoindClient()
|
|
|
|
// If we're not in regtest mode, then we'll attempt to use a
|
|
// proper fee estimator for testnet.
|
|
rpcConfig := &rpcclient.ConnConfig{
|
|
Host: bitcoindHost,
|
|
User: bitcoindMode.RPCUser,
|
|
Pass: bitcoindMode.RPCPass,
|
|
DisableConnectOnNew: true,
|
|
DisableAutoReconnect: false,
|
|
DisableTLS: true,
|
|
HTTPPostMode: true,
|
|
}
|
|
if !cfg.Bitcoin.RegTest {
|
|
log.Infof("Initializing bitcoind backed fee estimator "+
|
|
"in %s mode", bitcoindMode.EstimateMode)
|
|
|
|
// 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 := chainfee.SatPerKVByte(25 * 1000)
|
|
cc.FeeEstimator, err = chainfee.NewBitcoindEstimator(
|
|
*rpcConfig, bitcoindMode.EstimateMode,
|
|
fallBackFeeRate.FeePerKWeight(),
|
|
)
|
|
if err != nil {
|
|
return nil, 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, nil, err
|
|
}
|
|
|
|
// Before we continue any further, we'll ensure that the
|
|
// backend understands Taproot. If not, then all the default
|
|
// features can't be used.
|
|
if !backendSupportsTaproot(chainConn) {
|
|
return nil, nil, fmt.Errorf("node backend does not " +
|
|
"support taproot")
|
|
}
|
|
|
|
// The api we will use for our health check depends on the
|
|
// bitcoind version.
|
|
cmd, ver, err := getBitcoindHealthCheckCmd(chainConn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// If the getzmqnotifications api is available (was added in
|
|
// version 0.17.0) we make sure lnd subscribes to the correct
|
|
// zmq events. We do this to avoid a situation in which we are
|
|
// not notified of new transactions or blocks.
|
|
if ver >= 170000 && !bitcoindMode.RPCPolling {
|
|
zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Fetch all active zmq notifications from the bitcoind client.
|
|
resp, err := chainConn.RawRequest("getzmqnotifications", nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
zmq := []struct {
|
|
Type string `json:"type"`
|
|
Address string `json:"address"`
|
|
}{}
|
|
|
|
if err = json.Unmarshal([]byte(resp), &zmq); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pubRawBlockActive := false
|
|
pubRawTxActive := false
|
|
|
|
for i := range zmq {
|
|
if zmq[i].Type == "pubrawblock" {
|
|
url, err := url.Parse(zmq[i].Address)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if url.Port() != zmqPubRawBlockURL.Port() {
|
|
log.Warnf(
|
|
"unable to subscribe to zmq block events on "+
|
|
"%s (bitcoind is running on %s)",
|
|
zmqPubRawBlockURL.Host,
|
|
url.Host,
|
|
)
|
|
}
|
|
pubRawBlockActive = true
|
|
}
|
|
if zmq[i].Type == "pubrawtx" {
|
|
url, err := url.Parse(zmq[i].Address)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if url.Port() != zmqPubRawTxURL.Port() {
|
|
log.Warnf(
|
|
"unable to subscribe to zmq tx events on "+
|
|
"%s (bitcoind is running on %s)",
|
|
zmqPubRawTxURL.Host,
|
|
url.Host,
|
|
)
|
|
}
|
|
pubRawTxActive = true
|
|
}
|
|
}
|
|
|
|
// Return an error if raw tx or raw block notification over
|
|
// zmq is inactive.
|
|
if !pubRawBlockActive {
|
|
return nil, nil, errors.New(
|
|
"block notification over zmq is inactive on " +
|
|
"bitcoind",
|
|
)
|
|
}
|
|
if !pubRawTxActive {
|
|
return nil, nil, errors.New(
|
|
"tx notification over zmq is inactive on " +
|
|
"bitcoind",
|
|
)
|
|
}
|
|
}
|
|
|
|
cc.HealthCheck = func() error {
|
|
_, err := chainConn.RawRequest(cmd, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// On local test networks we usually don't have multiple
|
|
// chain backend peers, so we can skip
|
|
// the checkOutboundPeers test.
|
|
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
|
|
return nil
|
|
}
|
|
|
|
// Make sure the bitcoind chain backend maintains a
|
|
// healthy connection to the network by checking the
|
|
// number of outbound peers.
|
|
return checkOutboundPeers(chainConn)
|
|
}
|
|
|
|
case "btcd":
|
|
// Otherwise, we'll be speaking directly via RPC to a node.
|
|
//
|
|
// So first we'll load btcd'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 (
|
|
rpcCert []byte
|
|
btcdMode = cfg.BtcdMode
|
|
)
|
|
if btcdMode.RawRPCCert != "" {
|
|
rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
} else {
|
|
certFile, err := os.Open(btcdMode.RPCCert)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
rpcCert, err = io.ReadAll(certFile)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if err := certFile.Close(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// If the specified host for the btcd 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 btcdHost string
|
|
if strings.Contains(btcdMode.RPCHost, ":") {
|
|
btcdHost = btcdMode.RPCHost
|
|
} else {
|
|
btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
|
|
cfg.ActiveNetParams.RPCPort)
|
|
}
|
|
|
|
btcdUser := btcdMode.RPCUser
|
|
btcdPass := btcdMode.RPCPass
|
|
rpcConfig := &rpcclient.ConnConfig{
|
|
Host: btcdHost,
|
|
Endpoint: "ws",
|
|
User: btcdUser,
|
|
Pass: btcdPass,
|
|
Certificates: rpcCert,
|
|
DisableTLS: false,
|
|
DisableConnectOnNew: true,
|
|
DisableAutoReconnect: false,
|
|
}
|
|
|
|
chainNotifier, err := btcdnotify.New(
|
|
rpcConfig, cfg.ActiveNetParams.Params, hintCache,
|
|
hintCache, cfg.BlockCache,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cc.ChainNotifier = chainNotifier
|
|
cc.MempoolNotifier = chainNotifier
|
|
|
|
// Finally, we'll create an instance of the default chain view
|
|
// to be used within the routing layer.
|
|
cc.ChainView, err = chainview.NewBtcdFilteredChainView(
|
|
*rpcConfig, cfg.BlockCache,
|
|
)
|
|
if err != nil {
|
|
log.Errorf("unable to create chain view: %v", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Create a special websockets rpc client for btcd which will be
|
|
// used by the wallet for notifications, calls, etc.
|
|
chainRPC, err := chain.NewRPCClient(
|
|
cfg.ActiveNetParams.Params, btcdHost, btcdUser,
|
|
btcdPass, rpcCert, false, 20,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Before we continue any further, we'll ensure that the
|
|
// backend understands Taproot. If not, then all the default
|
|
// features can't be used.
|
|
restConfCopy := *rpcConfig
|
|
restConfCopy.Endpoint = ""
|
|
restConfCopy.HTTPPostMode = true
|
|
chainConn, err := rpcclient.New(&restConfCopy, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if !backendSupportsTaproot(chainConn) {
|
|
return nil, nil, fmt.Errorf("node backend does not " +
|
|
"support taproot")
|
|
}
|
|
|
|
cc.ChainSource = chainRPC
|
|
|
|
// Use a query for our best block as a health check.
|
|
cc.HealthCheck = func() error {
|
|
_, _, err := cc.ChainSource.GetBestBlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// On local test networks we usually don't have multiple
|
|
// chain backend peers, so we can skip
|
|
// the checkOutboundPeers test.
|
|
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
|
|
return nil
|
|
}
|
|
|
|
// Make sure the btcd chain backend maintains a
|
|
// healthy connection to the network by checking the
|
|
// number of outbound peers.
|
|
return checkOutboundPeers(chainRPC.Client)
|
|
}
|
|
|
|
// If we're not in simnet or regtest mode, then we'll attempt
|
|
// to use a proper fee estimator for testnet.
|
|
if !cfg.Bitcoin.SimNet && !cfg.Bitcoin.RegTest {
|
|
log.Info("Initializing btcd backed fee estimator")
|
|
|
|
// Finally, we'll re-initialize the fee estimator, as
|
|
// if we're using btcd as a backend, then we can use
|
|
// live fee estimates, rather than a statically coded
|
|
// value.
|
|
fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
|
|
cc.FeeEstimator, err = chainfee.NewBtcdEstimator(
|
|
*rpcConfig, fallBackFeeRate.FeePerKWeight(),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
case "nochainbackend":
|
|
backend := &NoChainBackend{}
|
|
source := &NoChainSource{
|
|
BestBlockTime: time.Now(),
|
|
}
|
|
|
|
cc.ChainNotifier = backend
|
|
cc.ChainView = backend
|
|
cc.FeeEstimator = backend
|
|
|
|
cc.ChainSource = source
|
|
cc.HealthCheck = func() error {
|
|
return nil
|
|
}
|
|
|
|
default:
|
|
return nil, nil, fmt.Errorf("unknown node type: %s",
|
|
cfg.Bitcoin.Node)
|
|
}
|
|
|
|
cc.BestBlockTracker =
|
|
chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
|
|
|
|
switch {
|
|
// If the fee URL isn't set, and the user is running mainnet, then
|
|
// we'll return an error to instruct them to set a proper fee
|
|
// estimator.
|
|
case cfg.Fee.URL == "" && cfg.Bitcoin.MainNet &&
|
|
cfg.Bitcoin.Node == "neutrino":
|
|
|
|
return nil, nil, fmt.Errorf("--fee.url parameter required " +
|
|
"when running neutrino on mainnet")
|
|
|
|
// Override default fee estimator if an external service is specified.
|
|
case cfg.Fee.URL != "":
|
|
// Do not cache fees on regtest to make it easier to execute
|
|
// manual or automated test cases.
|
|
cacheFees := !cfg.Bitcoin.RegTest
|
|
|
|
log.Infof("Using external fee estimator %v: cached=%v: "+
|
|
"min update timeout=%v, max update timeout=%v",
|
|
cfg.Fee.URL, cacheFees, cfg.Fee.MinUpdateTimeout,
|
|
cfg.Fee.MaxUpdateTimeout)
|
|
|
|
cc.FeeEstimator, err = chainfee.NewWebAPIEstimator(
|
|
chainfee.SparseConfFeeSource{
|
|
URL: cfg.Fee.URL,
|
|
},
|
|
!cacheFees,
|
|
cfg.Fee.MinUpdateTimeout,
|
|
cfg.Fee.MaxUpdateTimeout,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
ccCleanup := func() {
|
|
if cc.FeeEstimator != nil {
|
|
if err := cc.FeeEstimator.Stop(); err != nil {
|
|
log.Errorf("Failed to stop feeEstimator: %v",
|
|
err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start fee estimator.
|
|
if err := cc.FeeEstimator.Start(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return cc, ccCleanup, nil
|
|
}
|
|
|
|
// NewChainControl attempts to create a ChainControl instance according
|
|
// to the parameters in the passed configuration. Currently three
|
|
// branches of ChainControl instances exist: one backed by a running btcd
|
|
// full-node, another backed by a running bitcoind full-node, and the other
|
|
// backed by a running neutrino light client instance. When running with a
|
|
// neutrino light client instance, `neutrinoCS` must be non-nil.
|
|
func NewChainControl(walletConfig lnwallet.Config,
|
|
msgSigner lnwallet.MessageSigner,
|
|
pcc *PartialChainControl) (*ChainControl, func(), error) {
|
|
|
|
cc := &ChainControl{
|
|
PartialChainControl: pcc,
|
|
MsgSigner: msgSigner,
|
|
Signer: walletConfig.Signer,
|
|
ChainIO: walletConfig.ChainIO,
|
|
Wc: walletConfig.WalletController,
|
|
KeyRing: walletConfig.SecretKeyRing,
|
|
}
|
|
|
|
ccCleanup := func() {
|
|
if cc.Wallet != nil {
|
|
if err := cc.Wallet.Shutdown(); err != nil {
|
|
log.Errorf("Failed to shutdown wallet: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
lnWallet, err := lnwallet.NewLightningWallet(walletConfig)
|
|
if err != nil {
|
|
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
|
|
err)
|
|
}
|
|
if err := lnWallet.Startup(); err != nil {
|
|
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
|
|
err)
|
|
}
|
|
|
|
log.Info("LightningWallet opened")
|
|
cc.Wallet = lnWallet
|
|
|
|
return cc, ccCleanup, 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, int64, error) {
|
|
// Query bitcoind to get our current version.
|
|
resp, err := client.RawRequest("getnetworkinfo", nil)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
// Parse the response to retrieve bitcoind's version.
|
|
info := struct {
|
|
Version int64 `json:"version"`
|
|
}{}
|
|
if err := json.Unmarshal(resp, &info); err != nil {
|
|
return "", 0, 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", info.Version, nil
|
|
}
|
|
|
|
return "getblockchaininfo", info.Version, nil
|
|
}
|
|
|
|
var (
|
|
// BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
|
|
// chain.
|
|
BitcoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
|
|
0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71,
|
|
0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae,
|
|
0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad,
|
|
0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
|
|
})
|
|
|
|
// BitcoinSignetGenesis is the genesis hash of Bitcoin's signet chain.
|
|
BitcoinSignetGenesis = chainhash.Hash([chainhash.HashSize]byte{
|
|
0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
|
|
0x77, 0xa0, 0x63, 0xaf, 0x32, 0xb2, 0xbb, 0xc9,
|
|
0x7c, 0x9f, 0xf9, 0xf0, 0x1f, 0x2c, 0x42, 0x25,
|
|
0xe9, 0x73, 0x98, 0x81, 0x08, 0x00, 0x00, 0x00,
|
|
})
|
|
|
|
// BitcoinMainnetGenesis is the genesis hash of Bitcoin's main chain.
|
|
BitcoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
|
|
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
|
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
|
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
|
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
})
|
|
|
|
// ChainDNSSeeds is a map of a chain's hash to the set of DNS seeds
|
|
// that will be use to bootstrap peers upon first startup.
|
|
//
|
|
// The first item in the array is the primary host we'll use to attempt
|
|
// the SRV lookup we require. If we're unable to receive a response
|
|
// over UDP, then we'll fall back to manual TCP resolution. The second
|
|
// item in the array is a special A record that we'll query in order to
|
|
// receive the IP address of the current authoritative DNS server for
|
|
// the network seed.
|
|
//
|
|
// TODO(roasbeef): extend and collapse these and chainparams.go into
|
|
// struct like chaincfg.Params.
|
|
ChainDNSSeeds = map[chainhash.Hash][][2]string{
|
|
BitcoinMainnetGenesis: {
|
|
{
|
|
"nodes.lightning.directory",
|
|
"soa.nodes.lightning.directory",
|
|
},
|
|
{
|
|
"lseed.bitcoinstats.com",
|
|
},
|
|
},
|
|
|
|
BitcoinTestnetGenesis: {
|
|
{
|
|
"test.nodes.lightning.directory",
|
|
"soa.nodes.lightning.directory",
|
|
},
|
|
},
|
|
|
|
BitcoinSignetGenesis: {
|
|
{
|
|
"ln.signet.secp.tech",
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
// checkOutboundPeers checks the number of outbound peers connected to the
|
|
// provided RPC client. If the number of outbound peers is below 6, a warning
|
|
// is logged. This function is intended to ensure that the chain backend
|
|
// maintains a healthy connection to the network.
|
|
func checkOutboundPeers(client *rpcclient.Client) error {
|
|
peers, err := client.GetPeerInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var outboundPeers int
|
|
for _, peer := range peers {
|
|
if !peer.Inbound {
|
|
outboundPeers++
|
|
}
|
|
}
|
|
|
|
if outboundPeers < DefaultMinOutboundPeers {
|
|
log.Warnf("The chain backend has an insufficient number "+
|
|
"of connected outbound peers (%d connected, expected "+
|
|
"minimum is %d) which can be a security issue. "+
|
|
"Connect to more trusted nodes manually if necessary.",
|
|
outboundPeers, DefaultMinOutboundPeers)
|
|
}
|
|
|
|
return nil
|
|
}
|