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

lightningnetwork / lnd / 11388202384

17 Oct 2024 03:31PM UTC coverage: 58.81% (-0.07%) from 58.884%
11388202384

push

github

web-flow
Merge pull request #9196 from lightningnetwork/fn-context-guard

fn: add ContextGuard from tapd repo

131011 of 222771 relevant lines covered (58.81%)

28214.75 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/fn"
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:lll
226
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
3✔
227
        cc := &PartialChainControl{
3✔
228
                Cfg: cfg,
3✔
229
                RoutingPolicy: models.ForwardingPolicy{
3✔
230
                        MinHTLCOut:    cfg.Bitcoin.MinHTLCOut,
3✔
231
                        BaseFee:       cfg.Bitcoin.BaseFee,
3✔
232
                        FeeRate:       cfg.Bitcoin.FeeRate,
3✔
233
                        TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
3✔
234
                },
3✔
235
                MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
3✔
236
                FeeEstimator: chainfee.NewStaticEstimator(
3✔
237
                        DefaultBitcoinStaticFeePerKW,
3✔
238
                        DefaultBitcoinStaticMinRelayFeeRate,
3✔
239
                ),
3✔
240
        }
3✔
241

3✔
242
        var err error
3✔
243
        heightHintCacheConfig := channeldb.CacheConfig{
3✔
244
                QueryDisable: cfg.HeightHintCacheQueryDisable,
3✔
245
        }
3✔
246
        if cfg.HeightHintCacheQueryDisable {
3✔
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(
3✔
252
                heightHintCacheConfig, cfg.HeightHintDB,
3✔
253
        )
3✔
254
        if err != nil {
3✔
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 != "" {
3✔
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 {
3✔
273
        case "neutrino":
1✔
274
                // We'll create ChainNotifier and FilteredChainView instances,
1✔
275
                // along with the wallet's ChainSource, which are all backed by
1✔
276
                // the neutrino light client.
1✔
277
                cc.ChainNotifier = neutrinonotify.New(
1✔
278
                        cfg.NeutrinoCS, hintCache, hintCache, cfg.BlockCache,
1✔
279
                )
1✔
280
                cc.ChainView, err = chainview.NewCfFilteredChainView(
1✔
281
                        cfg.NeutrinoCS, cfg.BlockCache,
1✔
282
                )
1✔
283
                if err != nil {
1✔
284
                        return nil, nil, err
×
285
                }
×
286

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

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

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

2✔
300
                // Otherwise, we'll be speaking directly via RPC and ZMQ to a
2✔
301
                // bitcoind node. If the specified host for the btcd RPC
2✔
302
                // server already has a port specified, then we use that
2✔
303
                // directly. Otherwise, we assume the default port according to
2✔
304
                // the selected chain parameters.
2✔
305
                var bitcoindHost string
2✔
306
                if strings.Contains(bitcoindMode.RPCHost, ":") {
4✔
307
                        bitcoindHost = bitcoindMode.RPCHost
2✔
308
                } else {
2✔
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{
2✔
339
                        ChainParams:        cfg.ActiveNetParams.Params,
2✔
340
                        Host:               bitcoindHost,
2✔
341
                        User:               bitcoindMode.RPCUser,
2✔
342
                        Pass:               bitcoindMode.RPCPass,
2✔
343
                        Dialer:             cfg.Dialer,
2✔
344
                        PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers,
2✔
345
                }
2✔
346

2✔
347
                if bitcoindMode.RPCPolling {
3✔
348
                        bitcoindCfg.PollingConfig = &chain.PollingConfig{
1✔
349
                                BlockPollingInterval:    bitcoindMode.BlockPollingInterval,
1✔
350
                                TxPollingInterval:       bitcoindMode.TxPollingInterval,
1✔
351
                                TxPollingIntervalJitter: lncfg.DefaultTxPollingJitter,
1✔
352
                        }
1✔
353
                } else {
2✔
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)
2✔
366
                if err != nil {
2✔
367
                        return nil, nil, err
×
368
                }
×
369

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

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

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

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

2✔
388
                // If we're not in regtest mode, then we'll attempt to use a
2✔
389
                // proper fee estimator for testnet.
2✔
390
                rpcConfig := &rpcclient.ConnConfig{
2✔
391
                        Host:                 bitcoindHost,
2✔
392
                        User:                 bitcoindMode.RPCUser,
2✔
393
                        Pass:                 bitcoindMode.RPCPass,
2✔
394
                        DisableConnectOnNew:  true,
2✔
395
                        DisableAutoReconnect: false,
2✔
396
                        DisableTLS:           true,
2✔
397
                        HTTPPostMode:         true,
2✔
398
                }
2✔
399
                if !cfg.Bitcoin.RegTest {
2✔
400
                        log.Infof("Initializing bitcoind backed fee estimator "+
×
401
                                "in %s mode", bitcoindMode.EstimateMode)
×
402

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

603
                cc.ChainNotifier = chainNotifier
×
604
                cc.MempoolNotifier = chainNotifier
×
605

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

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

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

641
                cc.ChainSource = chainRPC
×
642

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

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

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

663
                // If we're not in simnet or regtest mode, then we'll attempt
664
                // to use a proper fee estimator for testnet.
665
                if !cfg.Bitcoin.SimNet && !cfg.Bitcoin.RegTest {
×
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 =
3✔
702
                chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
3✔
703

3✔
704
        switch {
3✔
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 != "":
3✔
716
                // Do not cache fees on regtest to make it easier to execute
3✔
717
                // manual or automated test cases.
3✔
718
                cacheFees := !cfg.Bitcoin.RegTest
3✔
719

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

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

738
        ccCleanup := func() {
6✔
739
                if cc.FeeEstimator != nil {
6✔
740
                        if err := cc.FeeEstimator.Stop(); err != nil {
3✔
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 {
3✔
749
                return nil, nil, err
×
750
        }
×
751

752
        return cc, ccCleanup, nil
3✔
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) {
3✔
764

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

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

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

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

3✔
795
        return cc, ccCleanup, nil
3✔
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) {
2✔
804
        // Query bitcoind to get our current version.
2✔
805
        resp, err := client.RawRequest("getnetworkinfo", nil)
2✔
806
        if err != nil {
2✔
807
                return "", 0, err
×
808
        }
×
809

810
        // Parse the response to retrieve bitcoind's version.
811
        info := struct {
2✔
812
                Version int64 `json:"version"`
2✔
813
        }{}
2✔
814
        if err := json.Unmarshal(resp, &info); err != nil {
2✔
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 {
4✔
825
                return "uptime", info.Version, nil
2✔
826
        }
2✔
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