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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

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

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

250
        // Initialize the height hint cache within the chain directory.
UNCOV
251
        hintCache, err := channeldb.NewHeightHintCache(
×
UNCOV
252
                heightHintCacheConfig, cfg.HeightHintDB,
×
UNCOV
253
        )
×
UNCOV
254
        if err != nil {
×
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.
UNCOV
260
        if cfg.FeeURL != "" {
×
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.
UNCOV
272
        switch cfg.Bitcoin.Node {
×
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

UNCOV
297
        case "bitcoind":
×
UNCOV
298
                bitcoindMode := cfg.BitcoindMode
×
UNCOV
299

×
UNCOV
300
                // Otherwise, we'll be speaking directly via RPC and ZMQ to a
×
UNCOV
301
                // bitcoind node. If the specified host for the btcd RPC
×
UNCOV
302
                // server already has a port specified, then we use that
×
UNCOV
303
                // directly. Otherwise, we assume the default port according to
×
UNCOV
304
                // the selected chain parameters.
×
UNCOV
305
                var bitcoindHost string
×
UNCOV
306
                if strings.Contains(bitcoindMode.RPCHost, ":") {
×
UNCOV
307
                        bitcoindHost = bitcoindMode.RPCHost
×
UNCOV
308
                } else {
×
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

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

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

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

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

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

×
UNCOV
380
                cc.ChainNotifier = chainNotifier
×
UNCOV
381
                cc.MempoolNotifier = chainNotifier
×
UNCOV
382

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

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

×
UNCOV
399
                // If feeurl is not provided, use bitcoind's fee estimator.
×
UNCOV
400
                if cfg.Fee.URL == "" {
×
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.
UNCOV
421
                chainConn, err := rpcclient.New(rpcConfig, nil)
×
UNCOV
422
                if err != nil {
×
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.
UNCOV
429
                if !backendSupportsTaproot(chainConn) {
×
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.
UNCOV
436
                cmd, ver, err := getBitcoindHealthCheckCmd(chainConn)
×
UNCOV
437
                if err != nil {
×
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.
UNCOV
445
                if ver >= 170000 && !bitcoindMode.RPCPolling {
×
UNCOV
446
                        zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock)
×
UNCOV
447
                        if err != nil {
×
448
                                return nil, nil, err
×
449
                        }
×
UNCOV
450
                        zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx)
×
UNCOV
451
                        if err != nil {
×
452
                                return nil, nil, err
×
453
                        }
×
454

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

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

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

UNCOV
470
                        pubRawBlockActive := false
×
UNCOV
471
                        pubRawTxActive := false
×
UNCOV
472

×
UNCOV
473
                        for i := range zmq {
×
UNCOV
474
                                if zmq[i].Type == "pubrawblock" {
×
UNCOV
475
                                        url, err := url.Parse(zmq[i].Address)
×
UNCOV
476
                                        if err != nil {
×
477
                                                return nil, nil, err
×
478
                                        }
×
UNCOV
479
                                        if url.Port() != zmqPubRawBlockURL.Port() {
×
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
                                        }
×
UNCOV
487
                                        pubRawBlockActive = true
×
488
                                }
UNCOV
489
                                if zmq[i].Type == "pubrawtx" {
×
UNCOV
490
                                        url, err := url.Parse(zmq[i].Address)
×
UNCOV
491
                                        if err != nil {
×
492
                                                return nil, nil, err
×
493
                                        }
×
UNCOV
494
                                        if url.Port() != zmqPubRawTxURL.Port() {
×
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
                                        }
×
UNCOV
502
                                        pubRawTxActive = true
×
503
                                }
504
                        }
505

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

UNCOV
522
                cc.HealthCheck = func() error {
×
UNCOV
523
                        _, err := chainConn.RawRequest(cmd, nil)
×
UNCOV
524
                        if err != nil {
×
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.
UNCOV
531
                        if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
×
UNCOV
532
                                return nil
×
UNCOV
533
                        }
×
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

UNCOV
701
        cc.BestBlockTracker =
×
UNCOV
702
                chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
×
UNCOV
703

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

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

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

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

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

UNCOV
752
        return cc, ccCleanup, nil
×
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,
UNCOV
763
        pcc *PartialChainControl) (*ChainControl, func(), error) {
×
UNCOV
764

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

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

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

UNCOV
792
        log.Info("LightningWallet opened")
×
UNCOV
793
        cc.Wallet = lnWallet
×
UNCOV
794

×
UNCOV
795
        return cc, ccCleanup, nil
×
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.
UNCOV
803
func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) {
×
UNCOV
804
        // Query bitcoind to get our current version.
×
UNCOV
805
        resp, err := client.RawRequest("getnetworkinfo", nil)
×
UNCOV
806
        if err != nil {
×
807
                return "", 0, err
×
808
        }
×
809

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