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

lightningnetwork / lnd / 12375116696

17 Dec 2024 02:29PM UTC coverage: 58.366% (-0.2%) from 58.595%
12375116696

Pull #8777

github

ziggie1984
docs: add release-notes
Pull Request #8777: multi: make deletion of edge atomic.

132 of 177 new or added lines in 6 files covered. (74.58%)

670 existing lines in 37 files now uncovered.

133926 of 229458 relevant lines covered (58.37%)

19223.6 hits per line

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

38.56
/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/fn/v2"
27
        "github.com/lightningnetwork/lnd/graph/db/models"
28
        "github.com/lightningnetwork/lnd/input"
29
        "github.com/lightningnetwork/lnd/keychain"
30
        "github.com/lightningnetwork/lnd/kvdb"
31
        "github.com/lightningnetwork/lnd/lncfg"
32
        "github.com/lightningnetwork/lnd/lnwallet"
33
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
34
        "github.com/lightningnetwork/lnd/lnwire"
35
        "github.com/lightningnetwork/lnd/routing/chainview"
36
        "github.com/lightningnetwork/lnd/walletunlocker"
37
)
38

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

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

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

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

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

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

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

67
        // AuxLeafStore is an optional store that can be used to store auxiliary
68
        // leaves for certain custom channel types.
69
        AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
70

71
        // AuxSigner is an optional signer that can be used to sign auxiliary
72
        // leaves for certain custom channel types.
73
        AuxSigner fn.Option[lnwallet.AuxSigner]
74

75
        // BlockCache is the main cache for storing block information.
76
        BlockCache *blockcache.BlockCache
77

78
        // WalletUnlockParams are the parameters that were used for unlocking
79
        // the main wallet.
80
        WalletUnlockParams *walletunlocker.WalletUnlockParams
81

82
        // NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil
83
        // if using neutrino.
84
        NeutrinoCS *neutrino.ChainService
85

86
        // ActiveNetParams details the current chain we are on.
87
        ActiveNetParams BitcoinNetParams
88

89
        // Deprecated: Use Fee.URL. FeeURL defines the URL for fee estimation
90
        // we will use. This field is optional.
91
        FeeURL string
92

93
        // Fee defines settings for the web fee estimator. This field is
94
        // optional.
95
        Fee *lncfg.Fee
96

97
        // Dialer is a function closure that will be used to establish outbound
98
        // TCP connections to Bitcoin peers in the event of a pruned block being
99
        // requested.
100
        Dialer chain.Dialer
101
}
102

103
const (
104
        // DefaultBitcoinMinHTLCInMSat is the default smallest value htlc this
105
        // node will accept. This value is proposed in the channel open sequence
106
        // and cannot be changed during the life of the channel. It is 1 msat by
107
        // default to allow maximum flexibility in deciding what size payments
108
        // to forward.
109
        //
110
        // All forwarded payments are subjected to the min htlc constraint of
111
        // the routing policy of the outgoing channel. This implicitly controls
112
        // the minimum htlc value on the incoming channel too.
113
        DefaultBitcoinMinHTLCInMSat = lnwire.MilliSatoshi(1)
114

115
        // DefaultBitcoinMinHTLCOutMSat is the default minimum htlc value that
116
        // we require for sending out htlcs. Our channel peer may have a lower
117
        // min htlc channel parameter, but we - by default - don't forward
118
        // anything under the value defined here.
119
        DefaultBitcoinMinHTLCOutMSat = lnwire.MilliSatoshi(1000)
120

121
        // DefaultBitcoinBaseFeeMSat is the default forwarding base fee.
122
        DefaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
123

124
        // DefaultBitcoinFeeRate is the default forwarding fee rate.
125
        DefaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
126

127
        // DefaultBitcoinTimeLockDelta is the default forwarding time lock
128
        // delta.
129
        DefaultBitcoinTimeLockDelta = 80
130

131
        // DefaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
132
        // expressed in sat/kw.
133
        DefaultBitcoinStaticFeePerKW = chainfee.SatPerKWeight(12500)
134

135
        // DefaultBitcoinStaticMinRelayFeeRate is the min relay fee used for
136
        // static estimators.
137
        DefaultBitcoinStaticMinRelayFeeRate = chainfee.FeePerKwFloor
138

139
        // DefaultMinOutboundPeers is the min number of connected
140
        // outbound peers the chain backend should have to maintain a
141
        // healthy connection to the network.
142
        DefaultMinOutboundPeers = 6
143
)
144

145
// PartialChainControl contains all the primary interfaces of the chain control
146
// that can be purely constructed from the global configuration. No wallet
147
// instance is required for constructing this partial state.
148
type PartialChainControl struct {
149
        // Cfg is the configuration that was used to create the partial chain
150
        // control.
151
        Cfg *Config
152

153
        // HealthCheck is a function which can be used to send a low-cost, fast
154
        // query to the chain backend to ensure we still have access to our
155
        // node.
156
        HealthCheck func() error
157

158
        // FeeEstimator is used to estimate an optimal fee for transactions
159
        // important to us.
160
        FeeEstimator chainfee.Estimator
161

162
        // ChainNotifier is used to receive blockchain events that we are
163
        // interested in.
164
        ChainNotifier chainntnfs.ChainNotifier
165

166
        // BestBlockTracker is used to maintain a view of the global
167
        // chain state that changes over time
168
        BestBlockTracker *chainntnfs.BestBlockTracker
169

170
        // MempoolNotifier is used to watch for spending events happened in
171
        // mempool.
172
        MempoolNotifier chainntnfs.MempoolWatcher
173

174
        // ChainView is used in the router for maintaining an up-to-date graph.
175
        ChainView chainview.FilteredChainView
176

177
        // ChainSource is the primary chain interface. This is used to operate
178
        // the wallet and do things such as rescanning, sending transactions,
179
        // notifications for received funds, etc.
180
        ChainSource chain.Interface
181

182
        // RoutingPolicy is the routing policy we have decided to use.
183
        RoutingPolicy models.ForwardingPolicy
184

185
        // MinHtlcIn is the minimum HTLC we will accept.
186
        MinHtlcIn lnwire.MilliSatoshi
187
}
188

189
// ChainControl couples the three primary interfaces lnd utilizes for a
190
// particular chain together. A single ChainControl instance will exist for all
191
// the chains lnd is currently active on.
192
type ChainControl struct {
193
        // PartialChainControl is the part of the chain control that was
194
        // initialized purely from the configuration and doesn't contain any
195
        // wallet related elements.
196
        *PartialChainControl
197

198
        // ChainIO represents an abstraction over a source that can query the
199
        // blockchain.
200
        ChainIO lnwallet.BlockChainIO
201

202
        // Signer is used to provide signatures over things like transactions.
203
        Signer input.Signer
204

205
        // KeyRing represents a set of keys that we have the private keys to.
206
        KeyRing keychain.SecretKeyRing
207

208
        // Wc is an abstraction over some basic wallet commands. This base set
209
        // of commands will be provided to the Wallet *LightningWallet raw
210
        // pointer below.
211
        Wc lnwallet.WalletController
212

213
        // MsgSigner is used to sign arbitrary messages.
214
        MsgSigner lnwallet.MessageSigner
215

216
        // Wallet is our LightningWallet that also contains the abstract Wc
217
        // above. This wallet handles all of the lightning operations.
218
        Wallet *lnwallet.LightningWallet
219
}
220

221
// NewPartialChainControl creates a new partial chain control that contains all
222
// the parts that can be purely constructed from the passed in global
223
// configuration and doesn't need any wallet instance yet.
224
//
225
//nolint:ll
226
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
1✔
227
        cc := &PartialChainControl{
1✔
228
                Cfg: cfg,
1✔
229
                RoutingPolicy: models.ForwardingPolicy{
1✔
230
                        MinHTLCOut:    cfg.Bitcoin.MinHTLCOut,
1✔
231
                        BaseFee:       cfg.Bitcoin.BaseFee,
1✔
232
                        FeeRate:       cfg.Bitcoin.FeeRate,
1✔
233
                        TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
1✔
234
                },
1✔
235
                MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
1✔
236
                FeeEstimator: chainfee.NewStaticEstimator(
1✔
237
                        DefaultBitcoinStaticFeePerKW,
1✔
238
                        DefaultBitcoinStaticMinRelayFeeRate,
1✔
239
                ),
1✔
240
        }
1✔
241

1✔
242
        var err error
1✔
243
        heightHintCacheConfig := channeldb.CacheConfig{
1✔
244
                QueryDisable: cfg.HeightHintCacheQueryDisable,
1✔
245
        }
1✔
246
        if cfg.HeightHintCacheQueryDisable {
1✔
247
                log.Infof("Height Hint Cache Queries disabled")
×
248
        }
×
249

250
        // Initialize the height hint cache within the chain directory.
251
        hintCache, err := channeldb.NewHeightHintCache(
1✔
252
                heightHintCacheConfig, cfg.HeightHintDB,
1✔
253
        )
1✔
254
        if err != nil {
1✔
255
                return nil, nil, fmt.Errorf("unable to initialize height hint "+
×
256
                        "cache: %v", err)
×
257
        }
×
258

259
        // Map the deprecated feeurl flag to fee.url.
260
        if cfg.FeeURL != "" {
1✔
261
                if cfg.Fee.URL != "" {
×
262
                        return nil, nil, errors.New("fee.url and " +
×
263
                                "feeurl are mutually exclusive")
×
264
                }
×
265

266
                cfg.Fee.URL = cfg.FeeURL
×
267
        }
268

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

UNCOV
287
                cc.ChainSource = chain.NewNeutrinoClient(
×
UNCOV
288
                        cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
×
UNCOV
289
                )
×
UNCOV
290

×
UNCOV
291
                // Get our best block as a health check.
×
UNCOV
292
                cc.HealthCheck = func() error {
×
UNCOV
293
                        _, _, err := cc.ChainSource.GetBestBlock()
×
UNCOV
294
                        return err
×
UNCOV
295
                }
×
296

297
        case "bitcoind":
1✔
298
                bitcoindMode := cfg.BitcoindMode
1✔
299

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

338
                bitcoindCfg := &chain.BitcoindConfig{
1✔
339
                        ChainParams:        cfg.ActiveNetParams.Params,
1✔
340
                        Host:               bitcoindHost,
1✔
341
                        User:               bitcoindMode.RPCUser,
1✔
342
                        Pass:               bitcoindMode.RPCPass,
1✔
343
                        Dialer:             cfg.Dialer,
1✔
344
                        PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers,
1✔
345
                }
1✔
346

1✔
347
                if bitcoindMode.RPCPolling {
1✔
UNCOV
348
                        bitcoindCfg.PollingConfig = &chain.PollingConfig{
×
UNCOV
349
                                BlockPollingInterval:    bitcoindMode.BlockPollingInterval,
×
UNCOV
350
                                TxPollingInterval:       bitcoindMode.TxPollingInterval,
×
UNCOV
351
                                TxPollingIntervalJitter: lncfg.DefaultTxPollingJitter,
×
UNCOV
352
                        }
×
353
                } else {
1✔
354
                        bitcoindCfg.ZMQConfig = &chain.ZMQConfig{
1✔
355
                                ZMQBlockHost:           bitcoindMode.ZMQPubRawBlock,
1✔
356
                                ZMQTxHost:              bitcoindMode.ZMQPubRawTx,
1✔
357
                                ZMQReadDeadline:        bitcoindMode.ZMQReadDeadline,
1✔
358
                                MempoolPollingInterval: bitcoindMode.TxPollingInterval,
1✔
359
                                PollingIntervalJitter:  lncfg.DefaultTxPollingJitter,
1✔
360
                        }
1✔
361
                }
1✔
362

363
                // Establish the connection to bitcoind and create the clients
364
                // required for our relevant subsystems.
365
                bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg)
1✔
366
                if err != nil {
1✔
367
                        return nil, nil, err
×
368
                }
×
369

370
                if err := bitcoindConn.Start(); err != nil {
1✔
371
                        return nil, nil, fmt.Errorf("unable to connect to "+
×
372
                                "bitcoind: %v", err)
×
373
                }
×
374

375
                chainNotifier := bitcoindnotify.New(
1✔
376
                        bitcoindConn, cfg.ActiveNetParams.Params, hintCache,
1✔
377
                        hintCache, cfg.BlockCache,
1✔
378
                )
1✔
379

1✔
380
                cc.ChainNotifier = chainNotifier
1✔
381
                cc.MempoolNotifier = chainNotifier
1✔
382

1✔
383
                cc.ChainView = chainview.NewBitcoindFilteredChainView(
1✔
384
                        bitcoindConn, cfg.BlockCache,
1✔
385
                )
1✔
386
                cc.ChainSource = bitcoindConn.NewBitcoindClient()
1✔
387

1✔
388
                // Initialize config to connect to bitcoind RPC.
1✔
389
                rpcConfig := &rpcclient.ConnConfig{
1✔
390
                        Host:                 bitcoindHost,
1✔
391
                        User:                 bitcoindMode.RPCUser,
1✔
392
                        Pass:                 bitcoindMode.RPCPass,
1✔
393
                        DisableConnectOnNew:  true,
1✔
394
                        DisableAutoReconnect: false,
1✔
395
                        DisableTLS:           true,
1✔
396
                        HTTPPostMode:         true,
1✔
397
                }
1✔
398

1✔
399
                // If feeurl is not provided, use bitcoind's fee estimator.
1✔
400
                if cfg.Fee.URL == "" {
1✔
401
                        log.Infof("Initializing bitcoind backed fee estimator "+
×
402
                                "in %s mode", bitcoindMode.EstimateMode)
×
403

×
404
                        // Finally, we'll re-initialize the fee estimator, as
×
405
                        // if we're using bitcoind as a backend, then we can
×
406
                        // use live fee estimates, rather than a statically
×
407
                        // coded value.
×
408
                        fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
×
409
                        cc.FeeEstimator, err = chainfee.NewBitcoindEstimator(
×
410
                                *rpcConfig, bitcoindMode.EstimateMode,
×
411
                                fallBackFeeRate.FeePerKWeight(),
×
412
                        )
×
413
                        if err != nil {
×
414
                                return nil, nil, err
×
415
                        }
×
416
                }
417

418
                // We need to use some apis that are not exposed by btcwallet,
419
                // for a health check function so we create an ad-hoc bitcoind
420
                // connection.
421
                chainConn, err := rpcclient.New(rpcConfig, nil)
1✔
422
                if err != nil {
1✔
423
                        return nil, nil, err
×
424
                }
×
425

426
                // Before we continue any further, we'll ensure that the
427
                // backend understands Taproot. If not, then all the default
428
                // features can't be used.
429
                if !backendSupportsTaproot(chainConn) {
1✔
430
                        return nil, nil, fmt.Errorf("node backend does not " +
×
431
                                "support taproot")
×
432
                }
×
433

434
                // The api we will use for our health check depends on the
435
                // bitcoind version.
436
                cmd, ver, err := getBitcoindHealthCheckCmd(chainConn)
1✔
437
                if err != nil {
1✔
438
                        return nil, nil, err
×
439
                }
×
440

441
                // If the getzmqnotifications api is available (was added in
442
                // version 0.17.0) we make sure lnd subscribes to the correct
443
                // zmq events. We do this to avoid a situation in which we are
444
                // not notified of new transactions or blocks.
445
                if ver >= 170000 && !bitcoindMode.RPCPolling {
2✔
446
                        zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock)
1✔
447
                        if err != nil {
1✔
448
                                return nil, nil, err
×
449
                        }
×
450
                        zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx)
1✔
451
                        if err != nil {
1✔
452
                                return nil, nil, err
×
453
                        }
×
454

455
                        // Fetch all active zmq notifications from the bitcoind client.
456
                        resp, err := chainConn.RawRequest("getzmqnotifications", nil)
1✔
457
                        if err != nil {
1✔
458
                                return nil, nil, err
×
459
                        }
×
460

461
                        zmq := []struct {
1✔
462
                                Type    string `json:"type"`
1✔
463
                                Address string `json:"address"`
1✔
464
                        }{}
1✔
465

1✔
466
                        if err = json.Unmarshal([]byte(resp), &zmq); err != nil {
1✔
467
                                return nil, nil, err
×
468
                        }
×
469

470
                        pubRawBlockActive := false
1✔
471
                        pubRawTxActive := false
1✔
472

1✔
473
                        for i := range zmq {
2✔
474
                                if zmq[i].Type == "pubrawblock" {
2✔
475
                                        url, err := url.Parse(zmq[i].Address)
1✔
476
                                        if err != nil {
1✔
477
                                                return nil, nil, err
×
478
                                        }
×
479
                                        if url.Port() != zmqPubRawBlockURL.Port() {
1✔
480
                                                log.Warnf(
×
481
                                                        "unable to subscribe to zmq block events on "+
×
482
                                                                "%s (bitcoind is running on %s)",
×
483
                                                        zmqPubRawBlockURL.Host,
×
484
                                                        url.Host,
×
485
                                                )
×
486
                                        }
×
487
                                        pubRawBlockActive = true
1✔
488
                                }
489
                                if zmq[i].Type == "pubrawtx" {
2✔
490
                                        url, err := url.Parse(zmq[i].Address)
1✔
491
                                        if err != nil {
1✔
492
                                                return nil, nil, err
×
493
                                        }
×
494
                                        if url.Port() != zmqPubRawTxURL.Port() {
1✔
495
                                                log.Warnf(
×
496
                                                        "unable to subscribe to zmq tx events on "+
×
497
                                                                "%s (bitcoind is running on %s)",
×
498
                                                        zmqPubRawTxURL.Host,
×
499
                                                        url.Host,
×
500
                                                )
×
501
                                        }
×
502
                                        pubRawTxActive = true
1✔
503
                                }
504
                        }
505

506
                        // Return an error if raw tx or raw block notification over
507
                        // zmq is inactive.
508
                        if !pubRawBlockActive {
1✔
509
                                return nil, nil, errors.New(
×
510
                                        "block notification over zmq is inactive on " +
×
511
                                                "bitcoind",
×
512
                                )
×
513
                        }
×
514
                        if !pubRawTxActive {
1✔
515
                                return nil, nil, errors.New(
×
516
                                        "tx notification over zmq is inactive on " +
×
517
                                                "bitcoind",
×
518
                                )
×
519
                        }
×
520
                }
521

522
                cc.HealthCheck = func() error {
2✔
523
                        _, err := chainConn.RawRequest(cmd, nil)
1✔
524
                        if err != nil {
1✔
525
                                return err
×
526
                        }
×
527

528
                        // On local test networks we usually don't have multiple
529
                        // chain backend peers, so we can skip
530
                        // the checkOutboundPeers test.
531
                        if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
2✔
532
                                return nil
1✔
533
                        }
1✔
534

535
                        // Make sure the bitcoind chain backend maintains a
536
                        // healthy connection to the network by checking the
537
                        // number of outbound peers.
538
                        return checkOutboundPeers(chainConn)
×
539
                }
540

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

571
                // If the specified host for the btcd RPC server already
572
                // has a port specified, then we use that directly. Otherwise,
573
                // we assume the default port according to the selected chain
574
                // parameters.
UNCOV
575
                var btcdHost string
×
UNCOV
576
                if strings.Contains(btcdMode.RPCHost, ":") {
×
UNCOV
577
                        btcdHost = btcdMode.RPCHost
×
UNCOV
578
                } else {
×
579
                        btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
×
580
                                cfg.ActiveNetParams.RPCPort)
×
581
                }
×
582

UNCOV
583
                btcdUser := btcdMode.RPCUser
×
UNCOV
584
                btcdPass := btcdMode.RPCPass
×
UNCOV
585
                rpcConfig := &rpcclient.ConnConfig{
×
UNCOV
586
                        Host:                 btcdHost,
×
UNCOV
587
                        Endpoint:             "ws",
×
UNCOV
588
                        User:                 btcdUser,
×
UNCOV
589
                        Pass:                 btcdPass,
×
UNCOV
590
                        Certificates:         rpcCert,
×
UNCOV
591
                        DisableTLS:           false,
×
UNCOV
592
                        DisableConnectOnNew:  true,
×
UNCOV
593
                        DisableAutoReconnect: false,
×
UNCOV
594
                }
×
UNCOV
595

×
UNCOV
596
                chainNotifier, err := btcdnotify.New(
×
UNCOV
597
                        rpcConfig, cfg.ActiveNetParams.Params, hintCache,
×
UNCOV
598
                        hintCache, cfg.BlockCache,
×
UNCOV
599
                )
×
UNCOV
600
                if err != nil {
×
601
                        return nil, nil, err
×
602
                }
×
603

UNCOV
604
                cc.ChainNotifier = chainNotifier
×
UNCOV
605
                cc.MempoolNotifier = chainNotifier
×
UNCOV
606

×
UNCOV
607
                // Finally, we'll create an instance of the default chain view
×
UNCOV
608
                // to be used within the routing layer.
×
UNCOV
609
                cc.ChainView, err = chainview.NewBtcdFilteredChainView(
×
UNCOV
610
                        *rpcConfig, cfg.BlockCache,
×
UNCOV
611
                )
×
UNCOV
612
                if err != nil {
×
613
                        log.Errorf("unable to create chain view: %v", err)
×
614
                        return nil, nil, err
×
615
                }
×
616

617
                // Create a special websockets rpc client for btcd which will be
618
                // used by the wallet for notifications, calls, etc.
UNCOV
619
                chainRPC, err := chain.NewRPCClient(
×
UNCOV
620
                        cfg.ActiveNetParams.Params, btcdHost, btcdUser,
×
UNCOV
621
                        btcdPass, rpcCert, false, 20,
×
UNCOV
622
                )
×
UNCOV
623
                if err != nil {
×
624
                        return nil, nil, err
×
625
                }
×
626

627
                // Before we continue any further, we'll ensure that the
628
                // backend understands Taproot. If not, then all the default
629
                // features can't be used.
UNCOV
630
                restConfCopy := *rpcConfig
×
UNCOV
631
                restConfCopy.Endpoint = ""
×
UNCOV
632
                restConfCopy.HTTPPostMode = true
×
UNCOV
633
                chainConn, err := rpcclient.New(&restConfCopy, nil)
×
UNCOV
634
                if err != nil {
×
635
                        return nil, nil, err
×
636
                }
×
UNCOV
637
                if !backendSupportsTaproot(chainConn) {
×
638
                        return nil, nil, fmt.Errorf("node backend does not " +
×
639
                                "support taproot")
×
640
                }
×
641

UNCOV
642
                cc.ChainSource = chainRPC
×
UNCOV
643

×
UNCOV
644
                // Use a query for our best block as a health check.
×
UNCOV
645
                cc.HealthCheck = func() error {
×
UNCOV
646
                        _, _, err := cc.ChainSource.GetBestBlock()
×
UNCOV
647
                        if err != nil {
×
648
                                return err
×
649
                        }
×
650

651
                        // On local test networks we usually don't have multiple
652
                        // chain backend peers, so we can skip
653
                        // the checkOutboundPeers test.
UNCOV
654
                        if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
×
UNCOV
655
                                return nil
×
UNCOV
656
                        }
×
657

658
                        // Make sure the btcd chain backend maintains a
659
                        // healthy connection to the network by checking the
660
                        // number of outbound peers.
661
                        return checkOutboundPeers(chainRPC.Client)
×
662
                }
663

664
                // If feeurl is not provided, use btcd's fee estimator.
UNCOV
665
                if cfg.Fee.URL == "" {
×
666
                        log.Info("Initializing btcd backed fee estimator")
×
667

×
668
                        // Finally, we'll re-initialize the fee estimator, as
×
669
                        // if we're using btcd as a backend, then we can use
×
670
                        // live fee estimates, rather than a statically coded
×
671
                        // value.
×
672
                        fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
×
673
                        cc.FeeEstimator, err = chainfee.NewBtcdEstimator(
×
674
                                *rpcConfig, fallBackFeeRate.FeePerKWeight(),
×
675
                        )
×
676
                        if err != nil {
×
677
                                return nil, nil, err
×
678
                        }
×
679
                }
680

681
        case "nochainbackend":
×
682
                backend := &NoChainBackend{}
×
683
                source := &NoChainSource{
×
684
                        BestBlockTime: time.Now(),
×
685
                }
×
686

×
687
                cc.ChainNotifier = backend
×
688
                cc.ChainView = backend
×
689
                cc.FeeEstimator = backend
×
690

×
691
                cc.ChainSource = source
×
692
                cc.HealthCheck = func() error {
×
693
                        return nil
×
694
                }
×
695

696
        default:
×
697
                return nil, nil, fmt.Errorf("unknown node type: %s",
×
698
                        cfg.Bitcoin.Node)
×
699
        }
700

701
        cc.BestBlockTracker =
1✔
702
                chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
1✔
703

1✔
704
        switch {
1✔
705
        // If the fee URL isn't set, and the user is running mainnet, then
706
        // we'll return an error to instruct them to set a proper fee
707
        // estimator.
708
        case cfg.Fee.URL == "" && cfg.Bitcoin.MainNet &&
709
                cfg.Bitcoin.Node == "neutrino":
×
710

×
711
                return nil, nil, fmt.Errorf("--fee.url parameter required " +
×
712
                        "when running neutrino on mainnet")
×
713

714
        // Override default fee estimator if an external service is specified.
715
        case cfg.Fee.URL != "":
1✔
716
                // Do not cache fees on regtest to make it easier to execute
1✔
717
                // manual or automated test cases.
1✔
718
                cacheFees := !cfg.Bitcoin.RegTest
1✔
719

1✔
720
                log.Infof("Using external fee estimator %v: cached=%v: "+
1✔
721
                        "min update timeout=%v, max update timeout=%v",
1✔
722
                        cfg.Fee.URL, cacheFees, cfg.Fee.MinUpdateTimeout,
1✔
723
                        cfg.Fee.MaxUpdateTimeout)
1✔
724

1✔
725
                cc.FeeEstimator, err = chainfee.NewWebAPIEstimator(
1✔
726
                        chainfee.SparseConfFeeSource{
1✔
727
                                URL: cfg.Fee.URL,
1✔
728
                        },
1✔
729
                        !cacheFees,
1✔
730
                        cfg.Fee.MinUpdateTimeout,
1✔
731
                        cfg.Fee.MaxUpdateTimeout,
1✔
732
                )
1✔
733
                if err != nil {
1✔
734
                        return nil, nil, err
×
735
                }
×
736
        }
737

738
        ccCleanup := func() {
2✔
739
                if cc.FeeEstimator != nil {
2✔
740
                        if err := cc.FeeEstimator.Stop(); err != nil {
1✔
741
                                log.Errorf("Failed to stop feeEstimator: %v",
×
742
                                        err)
×
743
                        }
×
744
                }
745
        }
746

747
        // Start fee estimator.
748
        if err := cc.FeeEstimator.Start(); err != nil {
1✔
749
                return nil, nil, err
×
750
        }
×
751

752
        return cc, ccCleanup, nil
1✔
753
}
754

755
// NewChainControl attempts to create a ChainControl instance according
756
// to the parameters in the passed configuration. Currently three
757
// branches of ChainControl instances exist: one backed by a running btcd
758
// full-node, another backed by a running bitcoind full-node, and the other
759
// backed by a running neutrino light client instance. When running with a
760
// neutrino light client instance, `neutrinoCS` must be non-nil.
761
func NewChainControl(walletConfig lnwallet.Config,
762
        msgSigner lnwallet.MessageSigner,
763
        pcc *PartialChainControl) (*ChainControl, func(), error) {
1✔
764

1✔
765
        cc := &ChainControl{
1✔
766
                PartialChainControl: pcc,
1✔
767
                MsgSigner:           msgSigner,
1✔
768
                Signer:              walletConfig.Signer,
1✔
769
                ChainIO:             walletConfig.ChainIO,
1✔
770
                Wc:                  walletConfig.WalletController,
1✔
771
                KeyRing:             walletConfig.SecretKeyRing,
1✔
772
        }
1✔
773

1✔
774
        ccCleanup := func() {
2✔
775
                if cc.Wallet != nil {
2✔
776
                        if err := cc.Wallet.Shutdown(); err != nil {
1✔
777
                                log.Errorf("Failed to shutdown wallet: %v", err)
×
778
                        }
×
779
                }
780
        }
781

782
        lnWallet, err := lnwallet.NewLightningWallet(walletConfig)
1✔
783
        if err != nil {
1✔
784
                return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
×
785
                        err)
×
786
        }
×
787
        if err := lnWallet.Startup(); err != nil {
1✔
788
                return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
×
789
                        err)
×
790
        }
×
791

792
        log.Info("LightningWallet opened")
1✔
793
        cc.Wallet = lnWallet
1✔
794

1✔
795
        return cc, ccCleanup, nil
1✔
796
}
797

798
// getBitcoindHealthCheckCmd queries bitcoind for its version to decide which
799
// api we should use for our health check. We prefer to use the uptime
800
// command, because it has no locking and is an inexpensive call, which was
801
// added in version 0.15. If we are on an earlier version, we fallback to using
802
// getblockchaininfo.
803
func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) {
1✔
804
        // Query bitcoind to get our current version.
1✔
805
        resp, err := client.RawRequest("getnetworkinfo", nil)
1✔
806
        if err != nil {
1✔
807
                return "", 0, err
×
808
        }
×
809

810
        // Parse the response to retrieve bitcoind's version.
811
        info := struct {
1✔
812
                Version int64 `json:"version"`
1✔
813
        }{}
1✔
814
        if err := json.Unmarshal(resp, &info); err != nil {
1✔
815
                return "", 0, err
×
816
        }
×
817

818
        // Bitcoind returns a single value representing the semantic version:
819
        // 1000000 * CLIENT_VERSION_MAJOR + 10000 * CLIENT_VERSION_MINOR
820
        // + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD
821
        //
822
        // The uptime call was added in version 0.15.0, so we return it for
823
        // any version value >= 150000, as per the above calculation.
824
        if info.Version >= 150000 {
2✔
825
                return "uptime", info.Version, nil
1✔
826
        }
1✔
827

828
        return "getblockchaininfo", info.Version, nil
×
829
}
830

831
var (
832
        // BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
833
        // chain.
834
        BitcoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
835
                0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71,
836
                0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae,
837
                0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad,
838
                0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
839
        })
840

841
        // BitcoinSignetGenesis is the genesis hash of Bitcoin's signet chain.
842
        BitcoinSignetGenesis = chainhash.Hash([chainhash.HashSize]byte{
843
                0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
844
                0x77, 0xa0, 0x63, 0xaf, 0x32, 0xb2, 0xbb, 0xc9,
845
                0x7c, 0x9f, 0xf9, 0xf0, 0x1f, 0x2c, 0x42, 0x25,
846
                0xe9, 0x73, 0x98, 0x81, 0x08, 0x00, 0x00, 0x00,
847
        })
848

849
        // BitcoinMainnetGenesis is the genesis hash of Bitcoin's main chain.
850
        BitcoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
851
                0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
852
                0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
853
                0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
854
                0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
855
        })
856

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

880
                BitcoinTestnetGenesis: {
881
                        {
882
                                "test.nodes.lightning.directory",
883
                                "soa.nodes.lightning.directory",
884
                        },
885
                },
886

887
                BitcoinSignetGenesis: {
888
                        {
889
                                "ln.signet.secp.tech",
890
                        },
891
                },
892
        }
893
)
894

895
// checkOutboundPeers checks the number of outbound peers connected to the
896
// provided RPC client. If the number of outbound peers is below 6, a warning
897
// is logged. This function is intended to ensure that the chain backend
898
// maintains a healthy connection to the network.
899
func checkOutboundPeers(client *rpcclient.Client) error {
×
900
        peers, err := client.GetPeerInfo()
×
901
        if err != nil {
×
902
                return err
×
903
        }
×
904

905
        var outboundPeers int
×
906
        for _, peer := range peers {
×
907
                if !peer.Inbound {
×
908
                        outboundPeers++
×
909
                }
×
910
        }
911

912
        if outboundPeers < DefaultMinOutboundPeers {
×
913
                log.Warnf("The chain backend has an insufficient number "+
×
914
                        "of connected outbound peers (%d connected, expected "+
×
915
                        "minimum is %d) which can be a security issue. "+
×
916
                        "Connect to more trusted nodes manually if necessary.",
×
917
                        outboundPeers, DefaultMinOutboundPeers)
×
918
        }
×
919

920
        return nil
×
921
}
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