• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 10204896993

01 Aug 2024 07:57PM UTC coverage: 58.591% (-0.08%) from 58.674%
10204896993

push

github

web-flow
Merge pull request #8962 from ProofOfKeags/refactor/quiescence-micro-spinoffs

[NANO]: Refactor/quiescence micro spinoffs

3 of 4 new or added lines in 2 files covered. (75.0%)

242 existing lines in 26 files now uncovered.

125214 of 213710 relevant lines covered (58.59%)

28092.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.01
/chainreg/chainregistry.go
1
package chainreg
2

3
import (
4
        "encoding/hex"
5
        "encoding/json"
6
        "errors"
7
        "fmt"
8
        "io"
9
        "net"
10
        "net/url"
11
        "os"
12
        "strconv"
13
        "strings"
14
        "time"
15

16
        "github.com/btcsuite/btcd/chaincfg/chainhash"
17
        "github.com/btcsuite/btcd/rpcclient"
18
        "github.com/btcsuite/btcwallet/chain"
19
        "github.com/lightninglabs/neutrino"
20
        "github.com/lightningnetwork/lnd/blockcache"
21
        "github.com/lightningnetwork/lnd/chainntnfs"
22
        "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
23
        "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
24
        "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
25
        "github.com/lightningnetwork/lnd/channeldb"
26
        "github.com/lightningnetwork/lnd/channeldb/models"
27
        "github.com/lightningnetwork/lnd/input"
28
        "github.com/lightningnetwork/lnd/keychain"
29
        "github.com/lightningnetwork/lnd/kvdb"
30
        "github.com/lightningnetwork/lnd/lncfg"
31
        "github.com/lightningnetwork/lnd/lnwallet"
32
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
33
        "github.com/lightningnetwork/lnd/lnwire"
34
        "github.com/lightningnetwork/lnd/routing/chainview"
35
        "github.com/lightningnetwork/lnd/walletunlocker"
36
)
37

38
// Config houses necessary fields that a chainControl instance needs to
39
// function.
40
type Config struct {
41
        // Bitcoin defines settings for the Bitcoin chain.
42
        Bitcoin *lncfg.Chain
43

44
        // HeightHintCacheQueryDisable is a boolean that disables height hint
45
        // queries if true.
46
        HeightHintCacheQueryDisable bool
47

48
        // NeutrinoMode defines settings for connecting to a neutrino
49
        // light-client.
50
        NeutrinoMode *lncfg.Neutrino
51

52
        // BitcoindMode defines settings for connecting to a bitcoind node.
53
        BitcoindMode *lncfg.Bitcoind
54

55
        // BtcdMode defines settings for connecting to a btcd node.
56
        BtcdMode *lncfg.Btcd
57

58
        // HeightHintDB is a pointer to the database that stores the height
59
        // hints.
60
        HeightHintDB kvdb.Backend
61

62
        // ChanStateDB is a pointer to the database that stores the channel
63
        // state.
64
        ChanStateDB *channeldb.ChannelStateDB
65

66
        // BlockCache is the main cache for storing block information.
67
        BlockCache *blockcache.BlockCache
68

69
        // WalletUnlockParams are the parameters that were used for unlocking
70
        // the main wallet.
71
        WalletUnlockParams *walletunlocker.WalletUnlockParams
72

73
        // NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil
74
        // if using neutrino.
75
        NeutrinoCS *neutrino.ChainService
76

77
        // ActiveNetParams details the current chain we are on.
78
        ActiveNetParams BitcoinNetParams
79

80
        // Deprecated: Use Fee.URL. FeeURL defines the URL for fee estimation
81
        // we will use. This field is optional.
82
        FeeURL string
83

84
        // Fee defines settings for the web fee estimator. This field is
85
        // optional.
86
        Fee *lncfg.Fee
87

88
        // Dialer is a function closure that will be used to establish outbound
89
        // TCP connections to Bitcoin peers in the event of a pruned block being
90
        // requested.
91
        Dialer chain.Dialer
92
}
93

94
const (
95
        // DefaultBitcoinMinHTLCInMSat is the default smallest value htlc this
96
        // node will accept. This value is proposed in the channel open sequence
97
        // and cannot be changed during the life of the channel. It is 1 msat by
98
        // default to allow maximum flexibility in deciding what size payments
99
        // to forward.
100
        //
101
        // All forwarded payments are subjected to the min htlc constraint of
102
        // the routing policy of the outgoing channel. This implicitly controls
103
        // the minimum htlc value on the incoming channel too.
104
        DefaultBitcoinMinHTLCInMSat = lnwire.MilliSatoshi(1)
105

106
        // DefaultBitcoinMinHTLCOutMSat is the default minimum htlc value that
107
        // we require for sending out htlcs. Our channel peer may have a lower
108
        // min htlc channel parameter, but we - by default - don't forward
109
        // anything under the value defined here.
110
        DefaultBitcoinMinHTLCOutMSat = lnwire.MilliSatoshi(1000)
111

112
        // DefaultBitcoinBaseFeeMSat is the default forwarding base fee.
113
        DefaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
114

115
        // DefaultBitcoinFeeRate is the default forwarding fee rate.
116
        DefaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
117

118
        // DefaultBitcoinTimeLockDelta is the default forwarding time lock
119
        // delta.
120
        DefaultBitcoinTimeLockDelta = 80
121

122
        // DefaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
123
        // expressed in sat/kw.
124
        DefaultBitcoinStaticFeePerKW = chainfee.SatPerKWeight(12500)
125

126
        // DefaultBitcoinStaticMinRelayFeeRate is the min relay fee used for
127
        // static estimators.
128
        DefaultBitcoinStaticMinRelayFeeRate = chainfee.FeePerKwFloor
129

130
        // DefaultMinOutboundPeers is the min number of connected
131
        // outbound peers the chain backend should have to maintain a
132
        // healthy connection to the network.
133
        DefaultMinOutboundPeers = 6
134
)
135

136
// PartialChainControl contains all the primary interfaces of the chain control
137
// that can be purely constructed from the global configuration. No wallet
138
// instance is required for constructing this partial state.
139
type PartialChainControl struct {
140
        // Cfg is the configuration that was used to create the partial chain
141
        // control.
142
        Cfg *Config
143

144
        // HealthCheck is a function which can be used to send a low-cost, fast
145
        // query to the chain backend to ensure we still have access to our
146
        // node.
147
        HealthCheck func() error
148

149
        // FeeEstimator is used to estimate an optimal fee for transactions
150
        // important to us.
151
        FeeEstimator chainfee.Estimator
152

153
        // ChainNotifier is used to receive blockchain events that we are
154
        // interested in.
155
        ChainNotifier chainntnfs.ChainNotifier
156

157
        // BestBlockTracker is used to maintain a view of the global
158
        // chain state that changes over time
159
        BestBlockTracker *chainntnfs.BestBlockTracker
160

161
        // MempoolNotifier is used to watch for spending events happened in
162
        // mempool.
163
        MempoolNotifier chainntnfs.MempoolWatcher
164

165
        // ChainView is used in the router for maintaining an up-to-date graph.
166
        ChainView chainview.FilteredChainView
167

168
        // ChainSource is the primary chain interface. This is used to operate
169
        // the wallet and do things such as rescanning, sending transactions,
170
        // notifications for received funds, etc.
171
        ChainSource chain.Interface
172

173
        // RoutingPolicy is the routing policy we have decided to use.
174
        RoutingPolicy models.ForwardingPolicy
175

176
        // MinHtlcIn is the minimum HTLC we will accept.
177
        MinHtlcIn lnwire.MilliSatoshi
178
}
179

180
// ChainControl couples the three primary interfaces lnd utilizes for a
181
// particular chain together. A single ChainControl instance will exist for all
182
// the chains lnd is currently active on.
183
type ChainControl struct {
184
        // PartialChainControl is the part of the chain control that was
185
        // initialized purely from the configuration and doesn't contain any
186
        // wallet related elements.
187
        *PartialChainControl
188

189
        // ChainIO represents an abstraction over a source that can query the
190
        // blockchain.
191
        ChainIO lnwallet.BlockChainIO
192

193
        // Signer is used to provide signatures over things like transactions.
194
        Signer input.Signer
195

196
        // KeyRing represents a set of keys that we have the private keys to.
197
        KeyRing keychain.SecretKeyRing
198

199
        // Wc is an abstraction over some basic wallet commands. This base set
200
        // of commands will be provided to the Wallet *LightningWallet raw
201
        // pointer below.
202
        Wc lnwallet.WalletController
203

204
        // MsgSigner is used to sign arbitrary messages.
205
        MsgSigner lnwallet.MessageSigner
206

207
        // Wallet is our LightningWallet that also contains the abstract Wc
208
        // above. This wallet handles all of the lightning operations.
209
        Wallet *lnwallet.LightningWallet
210
}
211

212
// NewPartialChainControl creates a new partial chain control that contains all
213
// the parts that can be purely constructed from the passed in global
214
// configuration and doesn't need any wallet instance yet.
215
//
216
//nolint:lll
217
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
3✔
218
        cc := &PartialChainControl{
3✔
219
                Cfg: cfg,
3✔
220
                RoutingPolicy: models.ForwardingPolicy{
3✔
221
                        MinHTLCOut:    cfg.Bitcoin.MinHTLCOut,
3✔
222
                        BaseFee:       cfg.Bitcoin.BaseFee,
3✔
223
                        FeeRate:       cfg.Bitcoin.FeeRate,
3✔
224
                        TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
3✔
225
                },
3✔
226
                MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
3✔
227
                FeeEstimator: chainfee.NewStaticEstimator(
3✔
228
                        DefaultBitcoinStaticFeePerKW,
3✔
229
                        DefaultBitcoinStaticMinRelayFeeRate,
3✔
230
                ),
3✔
231
        }
3✔
232

3✔
233
        var err error
3✔
234
        heightHintCacheConfig := channeldb.CacheConfig{
3✔
235
                QueryDisable: cfg.HeightHintCacheQueryDisable,
3✔
236
        }
3✔
237
        if cfg.HeightHintCacheQueryDisable {
3✔
238
                log.Infof("Height Hint Cache Queries disabled")
×
239
        }
×
240

241
        // Initialize the height hint cache within the chain directory.
242
        hintCache, err := channeldb.NewHeightHintCache(
3✔
243
                heightHintCacheConfig, cfg.HeightHintDB,
3✔
244
        )
3✔
245
        if err != nil {
3✔
246
                return nil, nil, fmt.Errorf("unable to initialize height hint "+
×
247
                        "cache: %v", err)
×
248
        }
×
249

250
        // Map the deprecated feeurl flag to fee.url.
251
        if cfg.FeeURL != "" {
3✔
252
                if cfg.Fee.URL != "" {
×
253
                        return nil, nil, errors.New("fee.url and " +
×
254
                                "feeurl are mutually exclusive")
×
255
                }
×
256

257
                cfg.Fee.URL = cfg.FeeURL
×
258
        }
259

260
        // If spv mode is active, then we'll be using a distinct set of
261
        // chainControl interfaces that interface directly with the p2p network
262
        // of the selected chain.
263
        switch cfg.Bitcoin.Node {
3✔
264
        case "neutrino":
1✔
265
                // We'll create ChainNotifier and FilteredChainView instances,
1✔
266
                // along with the wallet's ChainSource, which are all backed by
1✔
267
                // the neutrino light client.
1✔
268
                cc.ChainNotifier = neutrinonotify.New(
1✔
269
                        cfg.NeutrinoCS, hintCache, hintCache, cfg.BlockCache,
1✔
270
                )
1✔
271
                cc.ChainView, err = chainview.NewCfFilteredChainView(
1✔
272
                        cfg.NeutrinoCS, cfg.BlockCache,
1✔
273
                )
1✔
274
                if err != nil {
1✔
275
                        return nil, nil, err
×
276
                }
×
277

278
                cc.ChainSource = chain.NewNeutrinoClient(
1✔
279
                        cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
1✔
280
                )
1✔
281

1✔
282
                // Get our best block as a health check.
1✔
283
                cc.HealthCheck = func() error {
2✔
284
                        _, _, err := cc.ChainSource.GetBestBlock()
1✔
285
                        return err
1✔
286
                }
1✔
287

288
        case "bitcoind":
2✔
289
                bitcoindMode := cfg.BitcoindMode
2✔
290

2✔
291
                // Otherwise, we'll be speaking directly via RPC and ZMQ to a
2✔
292
                // bitcoind node. If the specified host for the btcd RPC
2✔
293
                // server already has a port specified, then we use that
2✔
294
                // directly. Otherwise, we assume the default port according to
2✔
295
                // the selected chain parameters.
2✔
296
                var bitcoindHost string
2✔
297
                if strings.Contains(bitcoindMode.RPCHost, ":") {
4✔
298
                        bitcoindHost = bitcoindMode.RPCHost
2✔
299
                } else {
2✔
300
                        // The RPC ports specified in chainparams.go assume
×
301
                        // btcd, which picks a different port so that btcwallet
×
302
                        // can use the same RPC port as bitcoind. We convert
×
303
                        // this back to the btcwallet/bitcoind port.
×
304
                        rpcPort, err := strconv.Atoi(cfg.ActiveNetParams.RPCPort)
×
305
                        if err != nil {
×
306
                                return nil, nil, err
×
307
                        }
×
308
                        rpcPort -= 2
×
309
                        bitcoindHost = fmt.Sprintf("%v:%d",
×
310
                                bitcoindMode.RPCHost, rpcPort)
×
311
                        if cfg.Bitcoin.RegTest || cfg.Bitcoin.SigNet {
×
312
                                conn, err := net.Dial("tcp", bitcoindHost)
×
313
                                if err != nil || conn == nil {
×
314
                                        switch {
×
315
                                        case cfg.Bitcoin.RegTest:
×
316
                                                rpcPort = 18443
×
317
                                        case cfg.Bitcoin.SigNet:
×
318
                                                rpcPort = 38332
×
319
                                        }
320
                                        bitcoindHost = fmt.Sprintf("%v:%d",
×
321
                                                bitcoindMode.RPCHost,
×
322
                                                rpcPort)
×
323
                                } else {
×
324
                                        conn.Close()
×
325
                                }
×
326
                        }
327
                }
328

329
                bitcoindCfg := &chain.BitcoindConfig{
2✔
330
                        ChainParams:        cfg.ActiveNetParams.Params,
2✔
331
                        Host:               bitcoindHost,
2✔
332
                        User:               bitcoindMode.RPCUser,
2✔
333
                        Pass:               bitcoindMode.RPCPass,
2✔
334
                        Dialer:             cfg.Dialer,
2✔
335
                        PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers,
2✔
336
                }
2✔
337

2✔
338
                if bitcoindMode.RPCPolling {
3✔
339
                        bitcoindCfg.PollingConfig = &chain.PollingConfig{
1✔
340
                                BlockPollingInterval:    bitcoindMode.BlockPollingInterval,
1✔
341
                                TxPollingInterval:       bitcoindMode.TxPollingInterval,
1✔
342
                                TxPollingIntervalJitter: lncfg.DefaultTxPollingJitter,
1✔
343
                        }
1✔
344
                } else {
2✔
345
                        bitcoindCfg.ZMQConfig = &chain.ZMQConfig{
1✔
346
                                ZMQBlockHost:           bitcoindMode.ZMQPubRawBlock,
1✔
347
                                ZMQTxHost:              bitcoindMode.ZMQPubRawTx,
1✔
348
                                ZMQReadDeadline:        bitcoindMode.ZMQReadDeadline,
1✔
349
                                MempoolPollingInterval: bitcoindMode.TxPollingInterval,
1✔
350
                                PollingIntervalJitter:  lncfg.DefaultTxPollingJitter,
1✔
351
                        }
1✔
352
                }
1✔
353

354
                // Establish the connection to bitcoind and create the clients
355
                // required for our relevant subsystems.
356
                bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg)
2✔
357
                if err != nil {
2✔
358
                        return nil, nil, err
×
359
                }
×
360

361
                if err := bitcoindConn.Start(); err != nil {
2✔
362
                        return nil, nil, fmt.Errorf("unable to connect to "+
×
363
                                "bitcoind: %v", err)
×
364
                }
×
365

366
                chainNotifier := bitcoindnotify.New(
2✔
367
                        bitcoindConn, cfg.ActiveNetParams.Params, hintCache,
2✔
368
                        hintCache, cfg.BlockCache,
2✔
369
                )
2✔
370

2✔
371
                cc.ChainNotifier = chainNotifier
2✔
372
                cc.MempoolNotifier = chainNotifier
2✔
373

2✔
374
                cc.ChainView = chainview.NewBitcoindFilteredChainView(
2✔
375
                        bitcoindConn, cfg.BlockCache,
2✔
376
                )
2✔
377
                cc.ChainSource = bitcoindConn.NewBitcoindClient()
2✔
378

2✔
379
                // If we're not in regtest mode, then we'll attempt to use a
2✔
380
                // proper fee estimator for testnet.
2✔
381
                rpcConfig := &rpcclient.ConnConfig{
2✔
382
                        Host:                 bitcoindHost,
2✔
383
                        User:                 bitcoindMode.RPCUser,
2✔
384
                        Pass:                 bitcoindMode.RPCPass,
2✔
385
                        DisableConnectOnNew:  true,
2✔
386
                        DisableAutoReconnect: false,
2✔
387
                        DisableTLS:           true,
2✔
388
                        HTTPPostMode:         true,
2✔
389
                }
2✔
390
                if !cfg.Bitcoin.RegTest {
2✔
391
                        log.Infof("Initializing bitcoind backed fee estimator "+
×
392
                                "in %s mode", bitcoindMode.EstimateMode)
×
393

×
394
                        // Finally, we'll re-initialize the fee estimator, as
×
395
                        // if we're using bitcoind as a backend, then we can
×
396
                        // use live fee estimates, rather than a statically
×
397
                        // coded value.
×
398
                        fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
×
399
                        cc.FeeEstimator, err = chainfee.NewBitcoindEstimator(
×
400
                                *rpcConfig, bitcoindMode.EstimateMode,
×
401
                                fallBackFeeRate.FeePerKWeight(),
×
402
                        )
×
403
                        if err != nil {
×
404
                                return nil, nil, err
×
405
                        }
×
406
                }
407

408
                // We need to use some apis that are not exposed by btcwallet,
409
                // for a health check function so we create an ad-hoc bitcoind
410
                // connection.
411
                chainConn, err := rpcclient.New(rpcConfig, nil)
2✔
412
                if err != nil {
2✔
413
                        return nil, nil, err
×
414
                }
×
415

416
                // Before we continue any further, we'll ensure that the
417
                // backend understands Taproot. If not, then all the default
418
                // features can't be used.
419
                if !backendSupportsTaproot(chainConn) {
2✔
420
                        return nil, nil, fmt.Errorf("node backend does not " +
×
421
                                "support taproot")
×
422
                }
×
423

424
                // The api we will use for our health check depends on the
425
                // bitcoind version.
426
                cmd, ver, err := getBitcoindHealthCheckCmd(chainConn)
2✔
427
                if err != nil {
2✔
428
                        return nil, nil, err
×
429
                }
×
430

431
                // If the getzmqnotifications api is available (was added in
432
                // version 0.17.0) we make sure lnd subscribes to the correct
433
                // zmq events. We do this to avoid a situation in which we are
434
                // not notified of new transactions or blocks.
435
                if ver >= 170000 && !bitcoindMode.RPCPolling {
3✔
436
                        zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock)
1✔
437
                        if err != nil {
1✔
438
                                return nil, nil, err
×
439
                        }
×
440
                        zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx)
1✔
441
                        if err != nil {
1✔
442
                                return nil, nil, err
×
443
                        }
×
444

445
                        // Fetch all active zmq notifications from the bitcoind client.
446
                        resp, err := chainConn.RawRequest("getzmqnotifications", nil)
1✔
447
                        if err != nil {
1✔
448
                                return nil, nil, err
×
449
                        }
×
450

451
                        zmq := []struct {
1✔
452
                                Type    string `json:"type"`
1✔
453
                                Address string `json:"address"`
1✔
454
                        }{}
1✔
455

1✔
456
                        if err = json.Unmarshal([]byte(resp), &zmq); err != nil {
1✔
457
                                return nil, nil, err
×
458
                        }
×
459

460
                        pubRawBlockActive := false
1✔
461
                        pubRawTxActive := false
1✔
462

1✔
463
                        for i := range zmq {
2✔
464
                                if zmq[i].Type == "pubrawblock" {
2✔
465
                                        url, err := url.Parse(zmq[i].Address)
1✔
466
                                        if err != nil {
1✔
467
                                                return nil, nil, err
×
468
                                        }
×
469
                                        if url.Port() != zmqPubRawBlockURL.Port() {
1✔
470
                                                log.Warnf(
×
471
                                                        "unable to subscribe to zmq block events on "+
×
472
                                                                "%s (bitcoind is running on %s)",
×
473
                                                        zmqPubRawBlockURL.Host,
×
474
                                                        url.Host,
×
475
                                                )
×
476
                                        }
×
477
                                        pubRawBlockActive = true
1✔
478
                                }
479
                                if zmq[i].Type == "pubrawtx" {
2✔
480
                                        url, err := url.Parse(zmq[i].Address)
1✔
481
                                        if err != nil {
1✔
482
                                                return nil, nil, err
×
483
                                        }
×
484
                                        if url.Port() != zmqPubRawTxURL.Port() {
1✔
485
                                                log.Warnf(
×
486
                                                        "unable to subscribe to zmq tx events on "+
×
487
                                                                "%s (bitcoind is running on %s)",
×
488
                                                        zmqPubRawTxURL.Host,
×
489
                                                        url.Host,
×
490
                                                )
×
491
                                        }
×
492
                                        pubRawTxActive = true
1✔
493
                                }
494
                        }
495

496
                        // Return an error if raw tx or raw block notification over
497
                        // zmq is inactive.
498
                        if !pubRawBlockActive {
1✔
499
                                return nil, nil, errors.New(
×
500
                                        "block notification over zmq is inactive on " +
×
501
                                                "bitcoind",
×
502
                                )
×
503
                        }
×
504
                        if !pubRawTxActive {
1✔
505
                                return nil, nil, errors.New(
×
506
                                        "tx notification over zmq is inactive on " +
×
507
                                                "bitcoind",
×
508
                                )
×
509
                        }
×
510
                }
511

512
                cc.HealthCheck = func() error {
4✔
513
                        _, err := chainConn.RawRequest(cmd, nil)
2✔
514
                        if err != nil {
4✔
515
                                return err
2✔
516
                        }
2✔
517

518
                        // On local test networks we usually don't have multiple
519
                        // chain backend peers, so we can skip
520
                        // the checkOutboundPeers test.
521
                        if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
4✔
522
                                return nil
2✔
523
                        }
2✔
524

525
                        // Make sure the bitcoind chain backend maintains a
526
                        // healthy connection to the network by checking the
527
                        // number of outbound peers.
528
                        return checkOutboundPeers(chainConn)
×
529
                }
530

UNCOV
531
        case "btcd":
×
UNCOV
532
                // Otherwise, we'll be speaking directly via RPC to a node.
×
UNCOV
533
                //
×
UNCOV
534
                // So first we'll load btcd's TLS cert for the RPC
×
UNCOV
535
                // connection. If a raw cert was specified in the config, then
×
UNCOV
536
                // we'll set that directly. Otherwise, we attempt to read the
×
UNCOV
537
                // cert from the path specified in the config.
×
UNCOV
538
                var (
×
UNCOV
539
                        rpcCert  []byte
×
UNCOV
540
                        btcdMode = cfg.BtcdMode
×
UNCOV
541
                )
×
UNCOV
542
                if btcdMode.RawRPCCert != "" {
×
UNCOV
543
                        rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
×
UNCOV
544
                        if err != nil {
×
545
                                return nil, nil, err
×
546
                        }
×
547
                } else {
×
548
                        certFile, err := os.Open(btcdMode.RPCCert)
×
549
                        if err != nil {
×
550
                                return nil, nil, err
×
551
                        }
×
552
                        rpcCert, err = io.ReadAll(certFile)
×
553
                        if err != nil {
×
554
                                return nil, nil, err
×
555
                        }
×
556
                        if err := certFile.Close(); err != nil {
×
557
                                return nil, nil, err
×
558
                        }
×
559
                }
560

561
                // If the specified host for the btcd RPC server already
562
                // has a port specified, then we use that directly. Otherwise,
563
                // we assume the default port according to the selected chain
564
                // parameters.
UNCOV
565
                var btcdHost string
×
UNCOV
566
                if strings.Contains(btcdMode.RPCHost, ":") {
×
UNCOV
567
                        btcdHost = btcdMode.RPCHost
×
UNCOV
568
                } else {
×
569
                        btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
×
570
                                cfg.ActiveNetParams.RPCPort)
×
571
                }
×
572

UNCOV
573
                btcdUser := btcdMode.RPCUser
×
UNCOV
574
                btcdPass := btcdMode.RPCPass
×
UNCOV
575
                rpcConfig := &rpcclient.ConnConfig{
×
UNCOV
576
                        Host:                 btcdHost,
×
UNCOV
577
                        Endpoint:             "ws",
×
UNCOV
578
                        User:                 btcdUser,
×
UNCOV
579
                        Pass:                 btcdPass,
×
UNCOV
580
                        Certificates:         rpcCert,
×
UNCOV
581
                        DisableTLS:           false,
×
UNCOV
582
                        DisableConnectOnNew:  true,
×
UNCOV
583
                        DisableAutoReconnect: false,
×
UNCOV
584
                }
×
UNCOV
585

×
UNCOV
586
                chainNotifier, err := btcdnotify.New(
×
UNCOV
587
                        rpcConfig, cfg.ActiveNetParams.Params, hintCache,
×
UNCOV
588
                        hintCache, cfg.BlockCache,
×
UNCOV
589
                )
×
UNCOV
590
                if err != nil {
×
591
                        return nil, nil, err
×
592
                }
×
593

UNCOV
594
                cc.ChainNotifier = chainNotifier
×
UNCOV
595
                cc.MempoolNotifier = chainNotifier
×
UNCOV
596

×
UNCOV
597
                // Finally, we'll create an instance of the default chain view
×
UNCOV
598
                // to be used within the routing layer.
×
UNCOV
599
                cc.ChainView, err = chainview.NewBtcdFilteredChainView(
×
UNCOV
600
                        *rpcConfig, cfg.BlockCache,
×
UNCOV
601
                )
×
UNCOV
602
                if err != nil {
×
603
                        log.Errorf("unable to create chain view: %v", err)
×
604
                        return nil, nil, err
×
605
                }
×
606

607
                // Create a special websockets rpc client for btcd which will be
608
                // used by the wallet for notifications, calls, etc.
UNCOV
609
                chainRPC, err := chain.NewRPCClient(
×
UNCOV
610
                        cfg.ActiveNetParams.Params, btcdHost, btcdUser,
×
UNCOV
611
                        btcdPass, rpcCert, false, 20,
×
UNCOV
612
                )
×
UNCOV
613
                if err != nil {
×
614
                        return nil, nil, err
×
615
                }
×
616

617
                // Before we continue any further, we'll ensure that the
618
                // backend understands Taproot. If not, then all the default
619
                // features can't be used.
UNCOV
620
                restConfCopy := *rpcConfig
×
UNCOV
621
                restConfCopy.Endpoint = ""
×
UNCOV
622
                restConfCopy.HTTPPostMode = true
×
UNCOV
623
                chainConn, err := rpcclient.New(&restConfCopy, nil)
×
UNCOV
624
                if err != nil {
×
625
                        return nil, nil, err
×
626
                }
×
UNCOV
627
                if !backendSupportsTaproot(chainConn) {
×
628
                        return nil, nil, fmt.Errorf("node backend does not " +
×
629
                                "support taproot")
×
630
                }
×
631

UNCOV
632
                cc.ChainSource = chainRPC
×
UNCOV
633

×
UNCOV
634
                // Use a query for our best block as a health check.
×
UNCOV
635
                cc.HealthCheck = func() error {
×
UNCOV
636
                        _, _, err := cc.ChainSource.GetBestBlock()
×
UNCOV
637
                        if err != nil {
×
UNCOV
638
                                return err
×
UNCOV
639
                        }
×
640

641
                        // On local test networks we usually don't have multiple
642
                        // chain backend peers, so we can skip
643
                        // the checkOutboundPeers test.
UNCOV
644
                        if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
×
UNCOV
645
                                return nil
×
UNCOV
646
                        }
×
647

648
                        // Make sure the btcd chain backend maintains a
649
                        // healthy connection to the network by checking the
650
                        // number of outbound peers.
651
                        return checkOutboundPeers(chainRPC.Client)
×
652
                }
653

654
                // If we're not in simnet or regtest mode, then we'll attempt
655
                // to use a proper fee estimator for testnet.
UNCOV
656
                if !cfg.Bitcoin.SimNet && !cfg.Bitcoin.RegTest {
×
657
                        log.Info("Initializing btcd backed fee estimator")
×
658

×
659
                        // Finally, we'll re-initialize the fee estimator, as
×
660
                        // if we're using btcd as a backend, then we can use
×
661
                        // live fee estimates, rather than a statically coded
×
662
                        // value.
×
663
                        fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
×
664
                        cc.FeeEstimator, err = chainfee.NewBtcdEstimator(
×
665
                                *rpcConfig, fallBackFeeRate.FeePerKWeight(),
×
666
                        )
×
667
                        if err != nil {
×
668
                                return nil, nil, err
×
669
                        }
×
670
                }
671

672
        case "nochainbackend":
×
673
                backend := &NoChainBackend{}
×
674
                source := &NoChainSource{
×
675
                        BestBlockTime: time.Now(),
×
676
                }
×
677

×
678
                cc.ChainNotifier = backend
×
679
                cc.ChainView = backend
×
680
                cc.FeeEstimator = backend
×
681

×
682
                cc.ChainSource = source
×
683
                cc.HealthCheck = func() error {
×
684
                        return nil
×
685
                }
×
686

687
        default:
×
688
                return nil, nil, fmt.Errorf("unknown node type: %s",
×
689
                        cfg.Bitcoin.Node)
×
690
        }
691

692
        cc.BestBlockTracker =
3✔
693
                chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
3✔
694

3✔
695
        switch {
3✔
696
        // If the fee URL isn't set, and the user is running mainnet, then
697
        // we'll return an error to instruct them to set a proper fee
698
        // estimator.
699
        case cfg.Fee.URL == "" && cfg.Bitcoin.MainNet &&
700
                cfg.Bitcoin.Node == "neutrino":
×
701

×
702
                return nil, nil, fmt.Errorf("--fee.url parameter required " +
×
703
                        "when running neutrino on mainnet")
×
704

705
        // Override default fee estimator if an external service is specified.
706
        case cfg.Fee.URL != "":
3✔
707
                // Do not cache fees on regtest to make it easier to execute
3✔
708
                // manual or automated test cases.
3✔
709
                cacheFees := !cfg.Bitcoin.RegTest
3✔
710

3✔
711
                log.Infof("Using external fee estimator %v: cached=%v: "+
3✔
712
                        "min update timeout=%v, max update timeout=%v",
3✔
713
                        cfg.Fee.URL, cacheFees, cfg.Fee.MinUpdateTimeout,
3✔
714
                        cfg.Fee.MaxUpdateTimeout)
3✔
715

3✔
716
                cc.FeeEstimator, err = chainfee.NewWebAPIEstimator(
3✔
717
                        chainfee.SparseConfFeeSource{
3✔
718
                                URL: cfg.Fee.URL,
3✔
719
                        },
3✔
720
                        !cacheFees,
3✔
721
                        cfg.Fee.MinUpdateTimeout,
3✔
722
                        cfg.Fee.MaxUpdateTimeout,
3✔
723
                )
3✔
724
                if err != nil {
3✔
725
                        return nil, nil, err
×
726
                }
×
727
        }
728

729
        ccCleanup := func() {
6✔
730
                if cc.FeeEstimator != nil {
6✔
731
                        if err := cc.FeeEstimator.Stop(); err != nil {
3✔
732
                                log.Errorf("Failed to stop feeEstimator: %v",
×
733
                                        err)
×
734
                        }
×
735
                }
736
        }
737

738
        // Start fee estimator.
739
        if err := cc.FeeEstimator.Start(); err != nil {
3✔
740
                return nil, nil, err
×
741
        }
×
742

743
        return cc, ccCleanup, nil
3✔
744
}
745

746
// NewChainControl attempts to create a ChainControl instance according
747
// to the parameters in the passed configuration. Currently three
748
// branches of ChainControl instances exist: one backed by a running btcd
749
// full-node, another backed by a running bitcoind full-node, and the other
750
// backed by a running neutrino light client instance. When running with a
751
// neutrino light client instance, `neutrinoCS` must be non-nil.
752
func NewChainControl(walletConfig lnwallet.Config,
753
        msgSigner lnwallet.MessageSigner,
754
        pcc *PartialChainControl) (*ChainControl, func(), error) {
3✔
755

3✔
756
        cc := &ChainControl{
3✔
757
                PartialChainControl: pcc,
3✔
758
                MsgSigner:           msgSigner,
3✔
759
                Signer:              walletConfig.Signer,
3✔
760
                ChainIO:             walletConfig.ChainIO,
3✔
761
                Wc:                  walletConfig.WalletController,
3✔
762
                KeyRing:             walletConfig.SecretKeyRing,
3✔
763
        }
3✔
764

3✔
765
        ccCleanup := func() {
6✔
766
                if cc.Wallet != nil {
6✔
767
                        if err := cc.Wallet.Shutdown(); err != nil {
3✔
768
                                log.Errorf("Failed to shutdown wallet: %v", err)
×
769
                        }
×
770
                }
771
        }
772

773
        lnWallet, err := lnwallet.NewLightningWallet(walletConfig)
3✔
774
        if err != nil {
3✔
775
                return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
×
776
                        err)
×
777
        }
×
778
        if err := lnWallet.Startup(); err != nil {
3✔
779
                return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
×
780
                        err)
×
781
        }
×
782

783
        log.Info("LightningWallet opened")
3✔
784
        cc.Wallet = lnWallet
3✔
785

3✔
786
        return cc, ccCleanup, nil
3✔
787
}
788

789
// getBitcoindHealthCheckCmd queries bitcoind for its version to decide which
790
// api we should use for our health check. We prefer to use the uptime
791
// command, because it has no locking and is an inexpensive call, which was
792
// added in version 0.15. If we are on an earlier version, we fallback to using
793
// getblockchaininfo.
794
func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) {
2✔
795
        // Query bitcoind to get our current version.
2✔
796
        resp, err := client.RawRequest("getnetworkinfo", nil)
2✔
797
        if err != nil {
2✔
798
                return "", 0, err
×
799
        }
×
800

801
        // Parse the response to retrieve bitcoind's version.
802
        info := struct {
2✔
803
                Version int64 `json:"version"`
2✔
804
        }{}
2✔
805
        if err := json.Unmarshal(resp, &info); err != nil {
2✔
806
                return "", 0, err
×
807
        }
×
808

809
        // Bitcoind returns a single value representing the semantic version:
810
        // 1000000 * CLIENT_VERSION_MAJOR + 10000 * CLIENT_VERSION_MINOR
811
        // + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD
812
        //
813
        // The uptime call was added in version 0.15.0, so we return it for
814
        // any version value >= 150000, as per the above calculation.
815
        if info.Version >= 150000 {
4✔
816
                return "uptime", info.Version, nil
2✔
817
        }
2✔
818

819
        return "getblockchaininfo", info.Version, nil
×
820
}
821

822
var (
823
        // BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
824
        // chain.
825
        BitcoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
826
                0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71,
827
                0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae,
828
                0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad,
829
                0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
830
        })
831

832
        // BitcoinSignetGenesis is the genesis hash of Bitcoin's signet chain.
833
        BitcoinSignetGenesis = chainhash.Hash([chainhash.HashSize]byte{
834
                0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
835
                0x77, 0xa0, 0x63, 0xaf, 0x32, 0xb2, 0xbb, 0xc9,
836
                0x7c, 0x9f, 0xf9, 0xf0, 0x1f, 0x2c, 0x42, 0x25,
837
                0xe9, 0x73, 0x98, 0x81, 0x08, 0x00, 0x00, 0x00,
838
        })
839

840
        // BitcoinMainnetGenesis is the genesis hash of Bitcoin's main chain.
841
        BitcoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
842
                0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
843
                0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
844
                0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
845
                0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
846
        })
847

848
        // ChainDNSSeeds is a map of a chain's hash to the set of DNS seeds
849
        // that will be use to bootstrap peers upon first startup.
850
        //
851
        // The first item in the array is the primary host we'll use to attempt
852
        // the SRV lookup we require. If we're unable to receive a response
853
        // over UDP, then we'll fall back to manual TCP resolution. The second
854
        // item in the array is a special A record that we'll query in order to
855
        // receive the IP address of the current authoritative DNS server for
856
        // the network seed.
857
        //
858
        // TODO(roasbeef): extend and collapse these and chainparams.go into
859
        // struct like chaincfg.Params.
860
        ChainDNSSeeds = map[chainhash.Hash][][2]string{
861
                BitcoinMainnetGenesis: {
862
                        {
863
                                "nodes.lightning.directory",
864
                                "soa.nodes.lightning.directory",
865
                        },
866
                        {
867
                                "lseed.bitcoinstats.com",
868
                        },
869
                },
870

871
                BitcoinTestnetGenesis: {
872
                        {
873
                                "test.nodes.lightning.directory",
874
                                "soa.nodes.lightning.directory",
875
                        },
876
                },
877

878
                BitcoinSignetGenesis: {
879
                        {
880
                                "ln.signet.secp.tech",
881
                        },
882
                },
883
        }
884
)
885

886
// checkOutboundPeers checks the number of outbound peers connected to the
887
// provided RPC client. If the number of outbound peers is below 6, a warning
888
// is logged. This function is intended to ensure that the chain backend
889
// maintains a healthy connection to the network.
890
func checkOutboundPeers(client *rpcclient.Client) error {
×
891
        peers, err := client.GetPeerInfo()
×
892
        if err != nil {
×
893
                return err
×
894
        }
×
895

896
        var outboundPeers int
×
897
        for _, peer := range peers {
×
898
                if !peer.Inbound {
×
899
                        outboundPeers++
×
900
                }
×
901
        }
902

903
        if outboundPeers < DefaultMinOutboundPeers {
×
904
                log.Warnf("The chain backend has an insufficient number "+
×
905
                        "of connected outbound peers (%d connected, expected "+
×
906
                        "minimum is %d) which can be a security issue. "+
×
907
                        "Connect to more trusted nodes manually if necessary.",
×
908
                        outboundPeers, DefaultMinOutboundPeers)
×
909
        }
×
910

911
        return nil
×
912
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc