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

lightningnetwork / lnd / 13157733617

05 Feb 2025 12:49PM UTC coverage: 57.712% (-1.1%) from 58.82%
13157733617

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19472 existing lines in 252 files now uncovered.

103634 of 179570 relevant lines covered (57.71%)

24840.31 hits per line

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

0.0
/config_builder.go
1
package lnd
2

3
import (
4
        "bytes"
5
        "context"
6
        "database/sql"
7
        "errors"
8
        "fmt"
9
        "net"
10
        "os"
11
        "path/filepath"
12
        "sort"
13
        "strconv"
14
        "strings"
15
        "sync/atomic"
16
        "time"
17

18
        "github.com/btcsuite/btcd/chaincfg"
19
        "github.com/btcsuite/btcd/chaincfg/chainhash"
20
        "github.com/btcsuite/btcd/wire"
21
        "github.com/btcsuite/btclog/v2"
22
        "github.com/btcsuite/btcwallet/chain"
23
        "github.com/btcsuite/btcwallet/waddrmgr"
24
        "github.com/btcsuite/btcwallet/wallet"
25
        "github.com/btcsuite/btcwallet/walletdb"
26
        proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
27
        "github.com/lightninglabs/neutrino"
28
        "github.com/lightninglabs/neutrino/blockntfns"
29
        "github.com/lightninglabs/neutrino/headerfs"
30
        "github.com/lightninglabs/neutrino/pushtx"
31
        "github.com/lightningnetwork/lnd/blockcache"
32
        "github.com/lightningnetwork/lnd/chainntnfs"
33
        "github.com/lightningnetwork/lnd/chainreg"
34
        "github.com/lightningnetwork/lnd/channeldb"
35
        "github.com/lightningnetwork/lnd/clock"
36
        "github.com/lightningnetwork/lnd/fn/v2"
37
        "github.com/lightningnetwork/lnd/funding"
38
        graphdb "github.com/lightningnetwork/lnd/graph/db"
39
        "github.com/lightningnetwork/lnd/htlcswitch"
40
        "github.com/lightningnetwork/lnd/invoices"
41
        "github.com/lightningnetwork/lnd/keychain"
42
        "github.com/lightningnetwork/lnd/kvdb"
43
        "github.com/lightningnetwork/lnd/lncfg"
44
        "github.com/lightningnetwork/lnd/lnrpc"
45
        "github.com/lightningnetwork/lnd/lnwallet"
46
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
47
        "github.com/lightningnetwork/lnd/lnwallet/chancloser"
48
        "github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
49
        "github.com/lightningnetwork/lnd/macaroons"
50
        "github.com/lightningnetwork/lnd/msgmux"
51
        "github.com/lightningnetwork/lnd/rpcperms"
52
        "github.com/lightningnetwork/lnd/signal"
53
        "github.com/lightningnetwork/lnd/sqldb"
54
        "github.com/lightningnetwork/lnd/sqldb/sqlc"
55
        "github.com/lightningnetwork/lnd/sweep"
56
        "github.com/lightningnetwork/lnd/walletunlocker"
57
        "github.com/lightningnetwork/lnd/watchtower"
58
        "github.com/lightningnetwork/lnd/watchtower/wtclient"
59
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
60
        "google.golang.org/grpc"
61
        "gopkg.in/macaroon-bakery.v2/bakery"
62
)
63

64
const (
65
        // invoiceMigrationBatchSize is the number of invoices that will be
66
        // migrated in a single batch.
67
        invoiceMigrationBatchSize = 1000
68

69
        // invoiceMigration is the version of the migration that will be used to
70
        // migrate invoices from the kvdb to the sql database.
71
        invoiceMigration = 7
72
)
73

74
// GrpcRegistrar is an interface that must be satisfied by an external subserver
75
// that wants to be able to register its own gRPC server onto lnd's main
76
// grpc.Server instance.
77
type GrpcRegistrar interface {
78
        // RegisterGrpcSubserver is called for each net.Listener on which lnd
79
        // creates a grpc.Server instance. External subservers implementing this
80
        // method can then register their own gRPC server structs to the main
81
        // server instance.
82
        RegisterGrpcSubserver(*grpc.Server) error
83
}
84

85
// RestRegistrar is an interface that must be satisfied by an external subserver
86
// that wants to be able to register its own REST mux onto lnd's main
87
// proxy.ServeMux instance.
88
type RestRegistrar interface {
89
        // RegisterRestSubserver is called after lnd creates the main
90
        // proxy.ServeMux instance. External subservers implementing this method
91
        // can then register their own REST proxy stubs to the main server
92
        // instance.
93
        RegisterRestSubserver(context.Context, *proxy.ServeMux, string,
94
                []grpc.DialOption) error
95
}
96

97
// ExternalValidator is an interface that must be satisfied by an external
98
// macaroon validator.
99
type ExternalValidator interface {
100
        macaroons.MacaroonValidator
101

102
        // Permissions returns the permissions that the external validator is
103
        // validating. It is a map between the full HTTP URI of each RPC and its
104
        // required macaroon permissions. If multiple action/entity tuples are
105
        // specified per URI, they are all required. See rpcserver.go for a list
106
        // of valid action and entity values.
107
        Permissions() map[string][]bakery.Op
108
}
109

110
// DatabaseBuilder is an interface that must be satisfied by the implementation
111
// that provides lnd's main database backend instances.
112
type DatabaseBuilder interface {
113
        // BuildDatabase extracts the current databases that we'll use for
114
        // normal operation in the daemon. A function closure that closes all
115
        // opened databases is also returned.
116
        BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error)
117
}
118

119
// WalletConfigBuilder is an interface that must be satisfied by a custom wallet
120
// implementation.
121
type WalletConfigBuilder interface {
122
        // BuildWalletConfig is responsible for creating or unlocking and then
123
        // fully initializing a wallet.
124
        BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents,
125
                *rpcperms.InterceptorChain,
126
                []*ListenerWithSignal) (*chainreg.PartialChainControl,
127
                *btcwallet.Config, func(), error)
128
}
129

130
// ChainControlBuilder is an interface that must be satisfied by a custom wallet
131
// implementation.
132
type ChainControlBuilder interface {
133
        // BuildChainControl is responsible for creating a fully populated chain
134
        // control instance from a wallet.
135
        BuildChainControl(*chainreg.PartialChainControl,
136
                *btcwallet.Config) (*chainreg.ChainControl, func(), error)
137
}
138

139
// ImplementationCfg is a struct that holds all configuration items for
140
// components that can be implemented outside lnd itself.
141
type ImplementationCfg struct {
142
        // GrpcRegistrar is a type that can register additional gRPC subservers
143
        // before the main gRPC server is started.
144
        GrpcRegistrar
145

146
        // RestRegistrar is a type that can register additional REST subservers
147
        // before the main REST proxy is started.
148
        RestRegistrar
149

150
        // ExternalValidator is a type that can provide external macaroon
151
        // validation.
152
        ExternalValidator
153

154
        // DatabaseBuilder is a type that can provide lnd's main database
155
        // backend instances.
156
        DatabaseBuilder
157

158
        // WalletConfigBuilder is a type that can provide a wallet configuration
159
        // with a fully loaded and unlocked wallet.
160
        WalletConfigBuilder
161

162
        // ChainControlBuilder is a type that can provide a custom wallet
163
        // implementation.
164
        ChainControlBuilder
165

166
        // AuxComponents is a set of auxiliary components that can be used by
167
        // lnd for certain custom channel types.
168
        AuxComponents
169
}
170

171
// AuxComponents is a set of auxiliary components that can be used by lnd for
172
// certain custom channel types.
173
type AuxComponents struct {
174
        // AuxLeafStore is an optional data source that can be used by custom
175
        // channels to fetch+store various data.
176
        AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
177

178
        // TrafficShaper is an optional traffic shaper that can be used to
179
        // control the outgoing channel of a payment.
180
        TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
181

182
        // MsgRouter is an optional message router that if set will be used in
183
        // place of a new blank default message router.
184
        MsgRouter fn.Option[msgmux.Router]
185

186
        // AuxFundingController is an optional controller that can be used to
187
        // modify the way we handle certain custom channel types. It's also
188
        // able to automatically handle new custom protocol messages related to
189
        // the funding process.
190
        AuxFundingController fn.Option[funding.AuxFundingController]
191

192
        // AuxSigner is an optional signer that can be used to sign auxiliary
193
        // leaves for certain custom channel types.
194
        AuxSigner fn.Option[lnwallet.AuxSigner]
195

196
        // AuxDataParser is an optional data parser that can be used to parse
197
        // auxiliary data for certain custom channel types.
198
        AuxDataParser fn.Option[AuxDataParser]
199

200
        // AuxChanCloser is an optional channel closer that can be used to
201
        // modify the way a coop-close transaction is constructed.
202
        AuxChanCloser fn.Option[chancloser.AuxChanCloser]
203

204
        // AuxSweeper is an optional interface that can be used to modify the
205
        // way sweep transaction are generated.
206
        AuxSweeper fn.Option[sweep.AuxSweeper]
207

208
        // AuxContractResolver is an optional interface that can be used to
209
        // modify the way contracts are resolved.
210
        AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
211
}
212

213
// DefaultWalletImpl is the default implementation of our normal, btcwallet
214
// backed configuration.
215
type DefaultWalletImpl struct {
216
        cfg         *Config
217
        logger      btclog.Logger
218
        interceptor signal.Interceptor
219

220
        watchOnly        bool
221
        migrateWatchOnly bool
222
        pwService        *walletunlocker.UnlockerService
223
}
224

225
// NewDefaultWalletImpl creates a new default wallet implementation.
226
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
UNCOV
227
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
×
UNCOV
228

×
UNCOV
229
        return &DefaultWalletImpl{
×
UNCOV
230
                cfg:         cfg,
×
UNCOV
231
                logger:      logger,
×
UNCOV
232
                interceptor: interceptor,
×
UNCOV
233
                watchOnly:   watchOnly,
×
UNCOV
234
                pwService:   createWalletUnlockerService(cfg),
×
UNCOV
235
        }
×
UNCOV
236
}
×
237

238
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
239
// instance. External subservers implementing this method can then register
240
// their own REST proxy stubs to the main server instance.
241
//
242
// NOTE: This is part of the GrpcRegistrar interface.
243
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
244
        mux *proxy.ServeMux, restProxyDest string,
UNCOV
245
        restDialOpts []grpc.DialOption) error {
×
UNCOV
246

×
UNCOV
247
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
×
UNCOV
248
                ctx, mux, restProxyDest, restDialOpts,
×
UNCOV
249
        )
×
UNCOV
250
}
×
251

252
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
253
// grpc.Server instance. External subservers implementing this method can then
254
// register their own gRPC server structs to the main server instance.
255
//
256
// NOTE: This is part of the GrpcRegistrar interface.
UNCOV
257
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
×
UNCOV
258
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
×
UNCOV
259

×
UNCOV
260
        return nil
×
UNCOV
261
}
×
262

263
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
264
// checks its signature, makes sure all specified permissions for the called
265
// method are contained within and finally ensures all caveat conditions are
266
// met. A non-nil error is returned if any of the checks fail.
267
//
268
// NOTE: This is part of the ExternalValidator interface.
269
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
270
        requiredPermissions []bakery.Op, fullMethod string) error {
×
271

×
272
        // Because the default implementation does not return any permissions,
×
273
        // we shouldn't be registered as an external validator at all and this
×
274
        // should never be invoked.
×
275
        return fmt.Errorf("default implementation does not support external " +
×
276
                "macaroon validation")
×
277
}
×
278

279
// Permissions returns the permissions that the external validator is
280
// validating. It is a map between the full HTTP URI of each RPC and its
281
// required macaroon permissions. If multiple action/entity tuples are specified
282
// per URI, they are all required. See rpcserver.go for a list of valid action
283
// and entity values.
284
//
285
// NOTE: This is part of the ExternalValidator interface.
UNCOV
286
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
×
UNCOV
287
        return nil
×
UNCOV
288
}
×
289

290
// BuildWalletConfig is responsible for creating or unlocking and then
291
// fully initializing a wallet.
292
//
293
// NOTE: This is part of the WalletConfigBuilder interface.
294
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
295
        dbs *DatabaseInstances, aux *AuxComponents,
296
        interceptorChain *rpcperms.InterceptorChain,
297
        grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
UNCOV
298
        *btcwallet.Config, func(), error) {
×
UNCOV
299

×
UNCOV
300
        // Keep track of our various cleanup functions. We use a defer function
×
UNCOV
301
        // as well to not repeat ourselves with every return statement.
×
UNCOV
302
        var (
×
UNCOV
303
                cleanUpTasks []func()
×
UNCOV
304
                earlyExit    = true
×
UNCOV
305
                cleanUp      = func() {
×
UNCOV
306
                        for _, fn := range cleanUpTasks {
×
UNCOV
307
                                if fn == nil {
×
308
                                        continue
×
309
                                }
310

UNCOV
311
                                fn()
×
312
                        }
313
                }
314
        )
UNCOV
315
        defer func() {
×
UNCOV
316
                if earlyExit {
×
317
                        cleanUp()
×
318
                }
×
319
        }()
320

321
        // Initialize a new block cache.
UNCOV
322
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
×
UNCOV
323

×
UNCOV
324
        // Before starting the wallet, we'll create and start our Neutrino
×
UNCOV
325
        // light client instance, if enabled, in order to allow it to sync
×
UNCOV
326
        // while the rest of the daemon continues startup.
×
UNCOV
327
        mainChain := d.cfg.Bitcoin
×
UNCOV
328
        var neutrinoCS *neutrino.ChainService
×
UNCOV
329
        if mainChain.Node == "neutrino" {
×
UNCOV
330
                neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend(
×
UNCOV
331
                        ctx, d.cfg, mainChain.ChainDir, blockCache,
×
UNCOV
332
                )
×
UNCOV
333
                if err != nil {
×
334
                        err := fmt.Errorf("unable to initialize neutrino "+
×
335
                                "backend: %v", err)
×
336
                        d.logger.Error(err)
×
337
                        return nil, nil, nil, err
×
338
                }
×
UNCOV
339
                cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp)
×
UNCOV
340
                neutrinoCS = neutrinoBackend
×
341
        }
342

UNCOV
343
        var (
×
UNCOV
344
                walletInitParams = walletunlocker.WalletUnlockParams{
×
UNCOV
345
                        // In case we do auto-unlock, we need to be able to send
×
UNCOV
346
                        // into the channel without blocking so we buffer it.
×
UNCOV
347
                        MacResponseChan: make(chan []byte, 1),
×
UNCOV
348
                }
×
UNCOV
349
                privateWalletPw = lnwallet.DefaultPrivatePassphrase
×
UNCOV
350
                publicWalletPw  = lnwallet.DefaultPublicPassphrase
×
UNCOV
351
        )
×
UNCOV
352

×
UNCOV
353
        // If the user didn't request a seed, then we'll manually assume a
×
UNCOV
354
        // wallet birthday of now, as otherwise the seed would've specified
×
UNCOV
355
        // this information.
×
UNCOV
356
        walletInitParams.Birthday = time.Now()
×
UNCOV
357

×
UNCOV
358
        d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
×
UNCOV
359
        d.pwService.SetMacaroonDB(dbs.MacaroonDB)
×
UNCOV
360
        walletExists, err := d.pwService.WalletExists()
×
UNCOV
361
        if err != nil {
×
362
                return nil, nil, nil, err
×
363
        }
×
364

UNCOV
365
        if !walletExists {
×
UNCOV
366
                interceptorChain.SetWalletNotCreated()
×
UNCOV
367
        } else {
×
UNCOV
368
                interceptorChain.SetWalletLocked()
×
UNCOV
369
        }
×
370

371
        // If we've started in auto unlock mode, then a wallet should already
372
        // exist because we don't want to enable the RPC unlocker in that case
373
        // for security reasons (an attacker could inject their seed since the
374
        // RPC is unauthenticated). Only if the user explicitly wants to allow
375
        // wallet creation we don't error out here.
UNCOV
376
        if d.cfg.WalletUnlockPasswordFile != "" && !walletExists &&
×
UNCOV
377
                !d.cfg.WalletUnlockAllowCreate {
×
378

×
379
                return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
×
380
                        "was specified but wallet does not exist; initialize " +
×
381
                        "the wallet before using auto unlocking")
×
382
        }
×
383

384
        // What wallet mode are we running in? We've already made sure the no
385
        // seed backup and auto unlock aren't both set during config parsing.
UNCOV
386
        switch {
×
387
        // No seed backup means we're also using the default password.
UNCOV
388
        case d.cfg.NoSeedBackup:
×
389
                // We continue normally, the default password has already been
390
                // set above.
391

392
        // A password for unlocking is provided in a file.
393
        case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
×
394
                d.logger.Infof("Attempting automatic wallet unlock with " +
×
395
                        "password provided in file")
×
396
                pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
×
397
                if err != nil {
×
398
                        return nil, nil, nil, fmt.Errorf("error reading "+
×
399
                                "password from file %s: %v",
×
400
                                d.cfg.WalletUnlockPasswordFile, err)
×
401
                }
×
402

403
                // Remove any newlines at the end of the file. The lndinit tool
404
                // won't ever write a newline but maybe the file was provisioned
405
                // by another process or user.
406
                pwBytes = bytes.TrimRight(pwBytes, "\r\n")
×
407

×
408
                // We have the password now, we can ask the unlocker service to
×
409
                // do the unlock for us.
×
410
                unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
×
411
                        pwBytes, 0,
×
412
                )
×
413
                if err != nil {
×
414
                        return nil, nil, nil, fmt.Errorf("error unlocking "+
×
415
                                "wallet with password from file: %v", err)
×
416
                }
×
417

418
                cleanUpTasks = append(cleanUpTasks, func() {
×
419
                        if err := unloadWalletFn(); err != nil {
×
420
                                d.logger.Errorf("Could not unload wallet: %v",
×
421
                                        err)
×
422
                        }
×
423
                })
424

425
                privateWalletPw = pwBytes
×
426
                publicWalletPw = pwBytes
×
427
                walletInitParams.Wallet = unlockedWallet
×
428
                walletInitParams.UnloadWallet = unloadWalletFn
×
429

430
        // If none of the automatic startup options are selected, we fall back
431
        // to the default behavior of waiting for the wallet creation/unlocking
432
        // over RPC.
UNCOV
433
        default:
×
UNCOV
434
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
×
435
                        return nil, nil, nil, err
×
436
                }
×
437

UNCOV
438
                params, err := waitForWalletPassword(
×
UNCOV
439
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
×
UNCOV
440
                        d.interceptor.ShutdownChannel(),
×
UNCOV
441
                )
×
UNCOV
442
                if err != nil {
×
443
                        err := fmt.Errorf("unable to set up wallet password "+
×
444
                                "listeners: %v", err)
×
445
                        d.logger.Error(err)
×
446
                        return nil, nil, nil, err
×
447
                }
×
448

UNCOV
449
                walletInitParams = *params
×
UNCOV
450
                privateWalletPw = walletInitParams.Password
×
UNCOV
451
                publicWalletPw = walletInitParams.Password
×
UNCOV
452
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
453
                        if err := walletInitParams.UnloadWallet(); err != nil {
×
454
                                d.logger.Errorf("Could not unload wallet: %v",
×
455
                                        err)
×
456
                        }
×
457
                })
458

UNCOV
459
                if walletInitParams.RecoveryWindow > 0 {
×
UNCOV
460
                        d.logger.Infof("Wallet recovery mode enabled with "+
×
UNCOV
461
                                "address lookahead of %d addresses",
×
UNCOV
462
                                walletInitParams.RecoveryWindow)
×
UNCOV
463
                }
×
464
        }
465

UNCOV
466
        var macaroonService *macaroons.Service
×
UNCOV
467
        if !d.cfg.NoMacaroons {
×
UNCOV
468
                // Create the macaroon authentication/authorization service.
×
UNCOV
469
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
×
UNCOV
470
                if err != nil {
×
471
                        return nil, nil, nil, err
×
472
                }
×
UNCOV
473
                macaroonService, err = macaroons.NewService(
×
UNCOV
474
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
×
UNCOV
475
                        macaroons.IPLockChecker,
×
UNCOV
476
                        macaroons.CustomChecker(interceptorChain),
×
UNCOV
477
                )
×
UNCOV
478
                if err != nil {
×
479
                        err := fmt.Errorf("unable to set up macaroon "+
×
480
                                "authentication: %v", err)
×
481
                        d.logger.Error(err)
×
482
                        return nil, nil, nil, err
×
483
                }
×
UNCOV
484
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
485
                        if err := macaroonService.Close(); err != nil {
×
486
                                d.logger.Errorf("Could not close macaroon "+
×
487
                                        "service: %v", err)
×
488
                        }
×
489
                })
490

491
                // Try to unlock the macaroon store with the private password.
492
                // Ignore ErrAlreadyUnlocked since it could be unlocked by the
493
                // wallet unlocker.
UNCOV
494
                err = macaroonService.CreateUnlock(&privateWalletPw)
×
UNCOV
495
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
×
496
                        err := fmt.Errorf("unable to unlock macaroons: %w", err)
×
497
                        d.logger.Error(err)
×
498
                        return nil, nil, nil, err
×
499
                }
×
500

501
                // If we have a macaroon root key from the init wallet params,
502
                // set the root key before baking any macaroons.
UNCOV
503
                if len(walletInitParams.MacRootKey) > 0 {
×
504
                        err := macaroonService.SetRootKey(
×
505
                                walletInitParams.MacRootKey,
×
506
                        )
×
507
                        if err != nil {
×
508
                                return nil, nil, nil, err
×
509
                        }
×
510
                }
511

512
                // Send an admin macaroon to all our listeners that requested
513
                // one by setting a non-nil macaroon channel.
UNCOV
514
                adminMacBytes, err := bakeMacaroon(
×
UNCOV
515
                        ctx, macaroonService, adminPermissions(),
×
UNCOV
516
                )
×
UNCOV
517
                if err != nil {
×
518
                        return nil, nil, nil, err
×
519
                }
×
UNCOV
520
                for _, lis := range grpcListeners {
×
UNCOV
521
                        if lis.MacChan != nil {
×
522
                                lis.MacChan <- adminMacBytes
×
523
                        }
×
524
                }
525

526
                // In case we actually needed to unlock the wallet, we now need
527
                // to create an instance of the admin macaroon and send it to
528
                // the unlocker so it can forward it to the user. In no seed
529
                // backup mode, there's nobody listening on the channel and we'd
530
                // block here forever.
UNCOV
531
                if !d.cfg.NoSeedBackup {
×
UNCOV
532
                        // The channel is buffered by one element so writing
×
UNCOV
533
                        // should not block here.
×
UNCOV
534
                        walletInitParams.MacResponseChan <- adminMacBytes
×
UNCOV
535
                }
×
536

537
                // If the user requested a stateless initialization, no macaroon
538
                // files should be created.
UNCOV
539
                if !walletInitParams.StatelessInit {
×
UNCOV
540
                        // Create default macaroon files for lncli to use if
×
UNCOV
541
                        // they don't exist.
×
UNCOV
542
                        err = genDefaultMacaroons(
×
UNCOV
543
                                ctx, macaroonService, d.cfg.AdminMacPath,
×
UNCOV
544
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
×
UNCOV
545
                        )
×
UNCOV
546
                        if err != nil {
×
547
                                err := fmt.Errorf("unable to create macaroons "+
×
548
                                        "%v", err)
×
549
                                d.logger.Error(err)
×
550
                                return nil, nil, nil, err
×
551
                        }
×
552
                }
553

554
                // As a security service to the user, if they requested
555
                // stateless initialization and there are macaroon files on disk
556
                // we log a warning.
UNCOV
557
                if walletInitParams.StatelessInit {
×
UNCOV
558
                        msg := "Found %s macaroon on disk (%s) even though " +
×
UNCOV
559
                                "--stateless_init was requested. Unencrypted " +
×
UNCOV
560
                                "state is accessible by the host system. You " +
×
UNCOV
561
                                "should change the password and use " +
×
UNCOV
562
                                "--new_mac_root_key with --stateless_init to " +
×
UNCOV
563
                                "clean up and invalidate old macaroons."
×
UNCOV
564

×
UNCOV
565
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
×
566
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
567
                        }
×
UNCOV
568
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
×
569
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
570
                        }
×
UNCOV
571
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
×
572
                                d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
×
573
                        }
×
574
                }
575

576
                // We add the macaroon service to our RPC interceptor. This
577
                // will start checking macaroons against permissions on every
578
                // RPC invocation.
UNCOV
579
                interceptorChain.AddMacaroonService(macaroonService)
×
580
        }
581

582
        // Now that the wallet password has been provided, transition the RPC
583
        // state into Unlocked.
UNCOV
584
        interceptorChain.SetWalletUnlocked()
×
UNCOV
585

×
UNCOV
586
        // Since calls to the WalletUnlocker service wait for a response on the
×
UNCOV
587
        // macaroon channel, we close it here to make sure they return in case
×
UNCOV
588
        // we did not return the admin macaroon above. This will be the case if
×
UNCOV
589
        // --no-macaroons is used.
×
UNCOV
590
        close(walletInitParams.MacResponseChan)
×
UNCOV
591

×
UNCOV
592
        // We'll also close all the macaroon channels since lnd is done sending
×
UNCOV
593
        // macaroon data over it.
×
UNCOV
594
        for _, lis := range grpcListeners {
×
UNCOV
595
                if lis.MacChan != nil {
×
596
                        close(lis.MacChan)
×
597
                }
×
598
        }
599

600
        // With the information parsed from the configuration, create valid
601
        // instances of the pertinent interfaces required to operate the
602
        // Lightning Network Daemon.
603
        //
604
        // When we create the chain control, we need storage for the height
605
        // hints and also the wallet itself, for these two we want them to be
606
        // replicated, so we'll pass in the remote channel DB instance.
UNCOV
607
        chainControlCfg := &chainreg.Config{
×
UNCOV
608
                Bitcoin:                     d.cfg.Bitcoin,
×
UNCOV
609
                HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable,
×
UNCOV
610
                NeutrinoMode:                d.cfg.NeutrinoMode,
×
UNCOV
611
                BitcoindMode:                d.cfg.BitcoindMode,
×
UNCOV
612
                BtcdMode:                    d.cfg.BtcdMode,
×
UNCOV
613
                HeightHintDB:                dbs.HeightHintDB,
×
UNCOV
614
                ChanStateDB:                 dbs.ChanStateDB.ChannelStateDB(),
×
UNCOV
615
                NeutrinoCS:                  neutrinoCS,
×
UNCOV
616
                AuxLeafStore:                aux.AuxLeafStore,
×
UNCOV
617
                AuxSigner:                   aux.AuxSigner,
×
UNCOV
618
                ActiveNetParams:             d.cfg.ActiveNetParams,
×
UNCOV
619
                FeeURL:                      d.cfg.FeeURL,
×
UNCOV
620
                Fee: &lncfg.Fee{
×
UNCOV
621
                        URL:              d.cfg.Fee.URL,
×
UNCOV
622
                        MinUpdateTimeout: d.cfg.Fee.MinUpdateTimeout,
×
UNCOV
623
                        MaxUpdateTimeout: d.cfg.Fee.MaxUpdateTimeout,
×
UNCOV
624
                },
×
UNCOV
625
                Dialer: func(addr string) (net.Conn, error) {
×
626
                        return d.cfg.net.Dial(
×
627
                                "tcp", addr, d.cfg.ConnectionTimeout,
×
628
                        )
×
629
                },
×
630
                BlockCache:         blockCache,
631
                WalletUnlockParams: &walletInitParams,
632
        }
633

634
        // Let's go ahead and create the partial chain control now that is only
635
        // dependent on our configuration and doesn't require any wallet
636
        // specific information.
UNCOV
637
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
×
UNCOV
638
                chainControlCfg,
×
UNCOV
639
        )
×
UNCOV
640
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
×
UNCOV
641
        if err != nil {
×
642
                err := fmt.Errorf("unable to create partial chain control: %w",
×
643
                        err)
×
644
                d.logger.Error(err)
×
645
                return nil, nil, nil, err
×
646
        }
×
647

UNCOV
648
        walletConfig := &btcwallet.Config{
×
UNCOV
649
                PrivatePass:      privateWalletPw,
×
UNCOV
650
                PublicPass:       publicWalletPw,
×
UNCOV
651
                Birthday:         walletInitParams.Birthday,
×
UNCOV
652
                RecoveryWindow:   walletInitParams.RecoveryWindow,
×
UNCOV
653
                NetParams:        d.cfg.ActiveNetParams.Params,
×
UNCOV
654
                CoinType:         d.cfg.ActiveNetParams.CoinType,
×
UNCOV
655
                Wallet:           walletInitParams.Wallet,
×
UNCOV
656
                LoaderOptions:    []btcwallet.LoaderOption{dbs.WalletDB},
×
UNCOV
657
                ChainSource:      partialChainControl.ChainSource,
×
UNCOV
658
                WatchOnly:        d.watchOnly,
×
UNCOV
659
                MigrateWatchOnly: d.migrateWatchOnly,
×
UNCOV
660
        }
×
UNCOV
661

×
UNCOV
662
        // Parse coin selection strategy.
×
UNCOV
663
        switch d.cfg.CoinSelectionStrategy {
×
UNCOV
664
        case "largest":
×
UNCOV
665
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
×
666

667
        case "random":
×
668
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
669

670
        default:
×
671
                return nil, nil, nil, fmt.Errorf("unknown coin selection "+
×
672
                        "strategy %v", d.cfg.CoinSelectionStrategy)
×
673
        }
674

UNCOV
675
        earlyExit = false
×
UNCOV
676
        return partialChainControl, walletConfig, cleanUp, nil
×
677
}
678

679
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
680
// rebroadcaster client.
681
func proxyBlockEpoch(
682
        notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
UNCOV
683
        error) {
×
UNCOV
684

×
UNCOV
685
        return func() (*blockntfns.Subscription, error) {
×
UNCOV
686
                blockEpoch, err := notifier.RegisterBlockEpochNtfn(
×
UNCOV
687
                        nil,
×
UNCOV
688
                )
×
UNCOV
689
                if err != nil {
×
690
                        return nil, err
×
691
                }
×
692

UNCOV
693
                sub := blockntfns.Subscription{
×
UNCOV
694
                        Notifications: make(chan blockntfns.BlockNtfn, 6),
×
UNCOV
695
                        Cancel:        blockEpoch.Cancel,
×
UNCOV
696
                }
×
UNCOV
697
                go func() {
×
UNCOV
698
                        for blk := range blockEpoch.Epochs {
×
UNCOV
699
                                ntfn := blockntfns.NewBlockConnected(
×
UNCOV
700
                                        *blk.BlockHeader,
×
UNCOV
701
                                        uint32(blk.Height),
×
UNCOV
702
                                )
×
UNCOV
703

×
UNCOV
704
                                sub.Notifications <- ntfn
×
UNCOV
705
                        }
×
706
                }()
707

UNCOV
708
                return &sub, nil
×
709
        }
710
}
711

712
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
713
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
714
type walletReBroadcaster struct {
715
        started atomic.Bool
716

717
        *pushtx.Broadcaster
718
}
719

720
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
721
func newWalletReBroadcaster(
UNCOV
722
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
×
UNCOV
723

×
UNCOV
724
        return &walletReBroadcaster{
×
UNCOV
725
                Broadcaster: broadcaster,
×
UNCOV
726
        }
×
UNCOV
727
}
×
728

729
// Start launches all goroutines the rebroadcaster needs to operate.
UNCOV
730
func (w *walletReBroadcaster) Start() error {
×
UNCOV
731
        defer w.started.Store(true)
×
UNCOV
732

×
UNCOV
733
        return w.Broadcaster.Start()
×
UNCOV
734
}
×
735

736
// Started returns true if the broadcaster is already active.
UNCOV
737
func (w *walletReBroadcaster) Started() bool {
×
UNCOV
738
        return w.started.Load()
×
UNCOV
739
}
×
740

741
// BuildChainControl is responsible for creating a fully populated chain
742
// control instance from a wallet.
743
//
744
// NOTE: This is part of the ChainControlBuilder interface.
745
func (d *DefaultWalletImpl) BuildChainControl(
746
        partialChainControl *chainreg.PartialChainControl,
UNCOV
747
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
UNCOV
748

×
UNCOV
749
        walletController, err := btcwallet.New(
×
UNCOV
750
                *walletConfig, partialChainControl.Cfg.BlockCache,
×
UNCOV
751
        )
×
UNCOV
752
        if err != nil {
×
753
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
754
                d.logger.Error(err)
×
755
                return nil, nil, err
×
756
        }
×
757

UNCOV
758
        keyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
759
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
760
        )
×
UNCOV
761

×
UNCOV
762
        // Create, and start the lnwallet, which handles the core payment
×
UNCOV
763
        // channel logic, and exposes control via proxy state machines.
×
UNCOV
764
        lnWalletConfig := lnwallet.Config{
×
UNCOV
765
                Database:              partialChainControl.Cfg.ChanStateDB,
×
UNCOV
766
                Notifier:              partialChainControl.ChainNotifier,
×
UNCOV
767
                WalletController:      walletController,
×
UNCOV
768
                Signer:                walletController,
×
UNCOV
769
                FeeEstimator:          partialChainControl.FeeEstimator,
×
UNCOV
770
                SecretKeyRing:         keyRing,
×
UNCOV
771
                ChainIO:               walletController,
×
UNCOV
772
                NetParams:             *walletConfig.NetParams,
×
UNCOV
773
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
774
                AuxLeafStore:          partialChainControl.Cfg.AuxLeafStore,
×
UNCOV
775
                AuxSigner:             partialChainControl.Cfg.AuxSigner,
×
UNCOV
776
        }
×
UNCOV
777

×
UNCOV
778
        // The broadcast is already always active for neutrino nodes, so we
×
UNCOV
779
        // don't want to create a rebroadcast loop.
×
UNCOV
780
        if partialChainControl.Cfg.NeutrinoCS == nil {
×
UNCOV
781
                cs := partialChainControl.ChainSource
×
UNCOV
782
                broadcastCfg := pushtx.Config{
×
UNCOV
783
                        Broadcast: func(tx *wire.MsgTx) error {
×
UNCOV
784
                                _, err := cs.SendRawTransaction(
×
UNCOV
785
                                        tx, true,
×
UNCOV
786
                                )
×
UNCOV
787

×
UNCOV
788
                                return err
×
UNCOV
789
                        },
×
790
                        SubscribeBlocks: proxyBlockEpoch(
791
                                partialChainControl.ChainNotifier,
792
                        ),
793
                        RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
794
                        // In case the backend is different from neutrino we
795
                        // make sure that broadcast backend errors are mapped
796
                        // to the neutrino broadcastErr.
UNCOV
797
                        MapCustomBroadcastError: func(err error) error {
×
UNCOV
798
                                rpcErr := cs.MapRPCErr(err)
×
UNCOV
799
                                return broadcastErrorMapper(rpcErr)
×
UNCOV
800
                        },
×
801
                }
802

UNCOV
803
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
×
UNCOV
804
                        pushtx.NewBroadcaster(&broadcastCfg),
×
UNCOV
805
                )
×
806
        }
807

808
        // We've created the wallet configuration now, so we can finish
809
        // initializing the main chain control.
UNCOV
810
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
×
UNCOV
811
                lnWalletConfig, walletController, partialChainControl,
×
UNCOV
812
        )
×
UNCOV
813
        if err != nil {
×
814
                err := fmt.Errorf("unable to create chain control: %w", err)
×
815
                d.logger.Error(err)
×
816
                return nil, nil, err
×
817
        }
×
818

UNCOV
819
        return activeChainControl, cleanUp, nil
×
820
}
821

822
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
823
// an RPC interface.
824
type RPCSignerWalletImpl struct {
825
        // DefaultWalletImpl is the embedded instance of the default
826
        // implementation that the remote signer uses as its watch-only wallet
827
        // for keeping track of addresses and UTXOs.
828
        *DefaultWalletImpl
829
}
830

831
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
832
// implementation.
833
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
834
        interceptor signal.Interceptor,
UNCOV
835
        migrateWatchOnly bool) *RPCSignerWalletImpl {
×
UNCOV
836

×
UNCOV
837
        return &RPCSignerWalletImpl{
×
UNCOV
838
                DefaultWalletImpl: &DefaultWalletImpl{
×
UNCOV
839
                        cfg:              cfg,
×
UNCOV
840
                        logger:           logger,
×
UNCOV
841
                        interceptor:      interceptor,
×
UNCOV
842
                        watchOnly:        true,
×
UNCOV
843
                        migrateWatchOnly: migrateWatchOnly,
×
UNCOV
844
                        pwService:        createWalletUnlockerService(cfg),
×
UNCOV
845
                },
×
UNCOV
846
        }
×
UNCOV
847
}
×
848

849
// BuildChainControl is responsible for creating or unlocking and then fully
850
// initializing a wallet and returning it as part of a fully populated chain
851
// control instance.
852
//
853
// NOTE: This is part of the ChainControlBuilder interface.
854
func (d *RPCSignerWalletImpl) BuildChainControl(
855
        partialChainControl *chainreg.PartialChainControl,
UNCOV
856
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
UNCOV
857

×
UNCOV
858
        walletController, err := btcwallet.New(
×
UNCOV
859
                *walletConfig, partialChainControl.Cfg.BlockCache,
×
UNCOV
860
        )
×
UNCOV
861
        if err != nil {
×
862
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
863
                d.logger.Error(err)
×
864
                return nil, nil, err
×
865
        }
×
866

UNCOV
867
        baseKeyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
868
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
869
        )
×
UNCOV
870

×
UNCOV
871
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
×
UNCOV
872
                baseKeyRing, walletController,
×
UNCOV
873
                d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
×
UNCOV
874
        )
×
UNCOV
875
        if err != nil {
×
876
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
877
                        "%v", err)
×
878
                d.logger.Error(err)
×
879
                return nil, nil, err
×
880
        }
×
881

882
        // Create, and start the lnwallet, which handles the core payment
883
        // channel logic, and exposes control via proxy state machines.
UNCOV
884
        lnWalletConfig := lnwallet.Config{
×
UNCOV
885
                Database:              partialChainControl.Cfg.ChanStateDB,
×
UNCOV
886
                Notifier:              partialChainControl.ChainNotifier,
×
UNCOV
887
                WalletController:      rpcKeyRing,
×
UNCOV
888
                Signer:                rpcKeyRing,
×
UNCOV
889
                FeeEstimator:          partialChainControl.FeeEstimator,
×
UNCOV
890
                SecretKeyRing:         rpcKeyRing,
×
UNCOV
891
                ChainIO:               walletController,
×
UNCOV
892
                NetParams:             *walletConfig.NetParams,
×
UNCOV
893
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
894
        }
×
UNCOV
895

×
UNCOV
896
        // We've created the wallet configuration now, so we can finish
×
UNCOV
897
        // initializing the main chain control.
×
UNCOV
898
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
×
UNCOV
899
                lnWalletConfig, rpcKeyRing, partialChainControl,
×
UNCOV
900
        )
×
UNCOV
901
        if err != nil {
×
902
                err := fmt.Errorf("unable to create chain control: %w", err)
×
903
                d.logger.Error(err)
×
904
                return nil, nil, err
×
905
        }
×
906

UNCOV
907
        return activeChainControl, cleanUp, nil
×
908
}
909

910
// DatabaseInstances is a struct that holds all instances to the actual
911
// databases that are used in lnd.
912
type DatabaseInstances struct {
913
        // GraphDB is the database that stores the channel graph used for path
914
        // finding.
915
        GraphDB *graphdb.ChannelGraph
916

917
        // ChanStateDB is the database that stores all of our node's channel
918
        // state.
919
        ChanStateDB *channeldb.DB
920

921
        // HeightHintDB is the database that stores height hints for spends.
922
        HeightHintDB kvdb.Backend
923

924
        // InvoiceDB is the database that stores information about invoices.
925
        InvoiceDB invoices.InvoiceDB
926

927
        // MacaroonDB is the database that stores macaroon root keys.
928
        MacaroonDB kvdb.Backend
929

930
        // DecayedLogDB is the database that stores p2p related encryption
931
        // information.
932
        DecayedLogDB kvdb.Backend
933

934
        // TowerClientDB is the database that stores the watchtower client's
935
        // configuration.
936
        TowerClientDB wtclient.DB
937

938
        // TowerServerDB is the database that stores the watchtower server's
939
        // configuration.
940
        TowerServerDB watchtower.DB
941

942
        // WalletDB is the configuration for loading the wallet database using
943
        // the btcwallet's loader.
944
        WalletDB btcwallet.LoaderOption
945

946
        // NativeSQLStore holds a reference to the native SQL store that can
947
        // be used for native SQL queries for tables that already support it.
948
        // This may be nil if the use-native-sql flag was not set.
949
        NativeSQLStore sqldb.DB
950
}
951

952
// DefaultDatabaseBuilder is a type that builds the default database backends
953
// for lnd, using the given configuration to decide what actual implementation
954
// to use.
955
type DefaultDatabaseBuilder struct {
956
        cfg    *Config
957
        logger btclog.Logger
958
}
959

960
// NewDefaultDatabaseBuilder returns a new instance of the default database
961
// builder.
962
func NewDefaultDatabaseBuilder(cfg *Config,
UNCOV
963
        logger btclog.Logger) *DefaultDatabaseBuilder {
×
UNCOV
964

×
UNCOV
965
        return &DefaultDatabaseBuilder{
×
UNCOV
966
                cfg:    cfg,
×
UNCOV
967
                logger: logger,
×
UNCOV
968
        }
×
UNCOV
969
}
×
970

971
// BuildDatabase extracts the current databases that we'll use for normal
972
// operation in the daemon. A function closure that closes all opened databases
973
// is also returned.
974
func (d *DefaultDatabaseBuilder) BuildDatabase(
UNCOV
975
        ctx context.Context) (*DatabaseInstances, func(), error) {
×
UNCOV
976

×
UNCOV
977
        d.logger.Infof("Opening the main database, this might take a few " +
×
UNCOV
978
                "minutes...")
×
UNCOV
979

×
UNCOV
980
        cfg := d.cfg
×
UNCOV
981
        if cfg.DB.Backend == lncfg.BoltBackend {
×
UNCOV
982
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
×
UNCOV
983
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
×
UNCOV
984
                        cfg.DB.Bolt.AutoCompact)
×
UNCOV
985
        }
×
986

UNCOV
987
        startOpenTime := time.Now()
×
UNCOV
988

×
UNCOV
989
        databaseBackends, err := cfg.DB.GetBackends(
×
UNCOV
990
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
×
UNCOV
991
                        cfg.Watchtower.TowerDir, BitcoinChainName,
×
UNCOV
992
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
993
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
×
UNCOV
994
        )
×
UNCOV
995
        if err != nil {
×
996
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
997
                        "backends: %v", err)
×
998
        }
×
999

1000
        // With the full remote mode we made sure both the graph and channel
1001
        // state DB point to the same local or remote DB and the same namespace
1002
        // within that DB.
UNCOV
1003
        dbs := &DatabaseInstances{
×
UNCOV
1004
                HeightHintDB:   databaseBackends.HeightHintDB,
×
UNCOV
1005
                MacaroonDB:     databaseBackends.MacaroonDB,
×
UNCOV
1006
                DecayedLogDB:   databaseBackends.DecayedLogDB,
×
UNCOV
1007
                WalletDB:       databaseBackends.WalletDB,
×
UNCOV
1008
                NativeSQLStore: databaseBackends.NativeSQLStore,
×
UNCOV
1009
        }
×
UNCOV
1010
        cleanUp := func() {
×
UNCOV
1011
                // We can just close the returned close functions directly. Even
×
UNCOV
1012
                // if we decorate the channel DB with an additional struct, its
×
UNCOV
1013
                // close function still just points to the kvdb backend.
×
UNCOV
1014
                for name, closeFunc := range databaseBackends.CloseFuncs {
×
UNCOV
1015
                        if err := closeFunc(); err != nil {
×
1016
                                d.logger.Errorf("Error closing %s "+
×
1017
                                        "database: %v", name, err)
×
1018
                        }
×
1019
                }
1020
        }
UNCOV
1021
        if databaseBackends.Remote {
×
1022
                d.logger.Infof("Using remote %v database! Creating "+
×
1023
                        "graph and channel state DB instances", cfg.DB.Backend)
×
UNCOV
1024
        } else {
×
UNCOV
1025
                d.logger.Infof("Creating local graph and channel state DB " +
×
UNCOV
1026
                        "instances")
×
UNCOV
1027
        }
×
1028

UNCOV
1029
        graphDBOptions := []graphdb.OptionModifier{
×
UNCOV
1030
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
×
UNCOV
1031
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
×
UNCOV
1032
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
×
UNCOV
1033
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
×
UNCOV
1034
        }
×
UNCOV
1035

×
UNCOV
1036
        // We want to pre-allocate the channel graph cache according to what we
×
UNCOV
1037
        // expect for mainnet to speed up memory allocation.
×
UNCOV
1038
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
×
1039
                graphDBOptions = append(
×
1040
                        graphDBOptions, graphdb.WithPreAllocCacheNumNodes(
×
1041
                                graphdb.DefaultPreAllocCacheNumNodes,
×
1042
                        ),
×
1043
                )
×
1044
        }
×
1045

UNCOV
1046
        dbs.GraphDB, err = graphdb.NewChannelGraph(
×
UNCOV
1047
                databaseBackends.GraphDB, graphDBOptions...,
×
UNCOV
1048
        )
×
UNCOV
1049
        if err != nil {
×
1050
                cleanUp()
×
1051

×
1052
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1053
                d.logger.Error(err)
×
1054

×
1055
                return nil, nil, err
×
1056
        }
×
1057

UNCOV
1058
        dbOptions := []channeldb.OptionModifier{
×
UNCOV
1059
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
×
UNCOV
1060
                channeldb.OptionKeepFailedPaymentAttempts(
×
UNCOV
1061
                        cfg.KeepFailedPaymentAttempts,
×
UNCOV
1062
                ),
×
UNCOV
1063
                channeldb.OptionStoreFinalHtlcResolutions(
×
UNCOV
1064
                        cfg.StoreFinalHtlcResolutions,
×
UNCOV
1065
                ),
×
UNCOV
1066
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
×
UNCOV
1067
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
×
UNCOV
1068
        }
×
UNCOV
1069

×
UNCOV
1070
        // Otherwise, we'll open two instances, one for the state we only need
×
UNCOV
1071
        // locally, and the other for things we want to ensure are replicated.
×
UNCOV
1072
        dbs.ChanStateDB, err = channeldb.CreateWithBackend(
×
UNCOV
1073
                databaseBackends.ChanStateDB, dbOptions...,
×
UNCOV
1074
        )
×
UNCOV
1075
        switch {
×
1076
        // Give the DB a chance to dry run the migration. Since we know that
1077
        // both the channel state and graph DBs are still always behind the same
1078
        // backend, we know this would be applied to both of those DBs.
1079
        case err == channeldb.ErrDryRunMigrationOK:
×
1080
                d.logger.Infof("Channel DB dry run migration successful")
×
1081
                return nil, nil, err
×
1082

1083
        case err != nil:
×
1084
                cleanUp()
×
1085

×
1086
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1087
                d.logger.Error(err)
×
1088
                return nil, nil, err
×
1089
        }
1090

1091
        // Instantiate a native SQL store if the flag is set.
UNCOV
1092
        if d.cfg.DB.UseNativeSQL {
×
1093
                migrations := sqldb.GetMigrations()
×
1094

×
1095
                // If the user has not explicitly disabled the SQL invoice
×
1096
                // migration, attach the custom migration function to invoice
×
1097
                // migration (version 7). Even if this custom migration is
×
1098
                // disabled, the regular native SQL store migrations will still
×
1099
                // run. If the database version is already above this custom
×
1100
                // migration's version (7), it will be skipped permanently,
×
1101
                // regardless of the flag.
×
1102
                if !d.cfg.DB.SkipSQLInvoiceMigration {
×
1103
                        migrationFn := func(tx *sqlc.Queries) error {
×
1104
                                err := invoices.MigrateInvoicesToSQL(
×
1105
                                        ctx, dbs.ChanStateDB.Backend,
×
1106
                                        dbs.ChanStateDB, tx,
×
1107
                                        invoiceMigrationBatchSize,
×
1108
                                )
×
1109
                                if err != nil {
×
1110
                                        return fmt.Errorf("failed to migrate "+
×
1111
                                                "invoices to SQL: %w", err)
×
1112
                                }
×
1113

1114
                                // Set the invoice bucket tombstone to indicate
1115
                                // that the migration has been completed.
1116
                                d.logger.Debugf("Setting invoice bucket " +
×
1117
                                        "tombstone")
×
1118

×
1119
                                return dbs.ChanStateDB.SetInvoiceBucketTombstone() //nolint:ll
×
1120
                        }
1121

1122
                        // Make sure we attach the custom migration function to
1123
                        // the correct migration version.
1124
                        for i := 0; i < len(migrations); i++ {
×
1125
                                if migrations[i].Version != invoiceMigration {
×
1126
                                        continue
×
1127
                                }
1128

1129
                                migrations[i].MigrationFn = migrationFn
×
1130
                        }
1131
                }
1132

1133
                // We need to apply all migrations to the native SQL store
1134
                // before we can use it.
1135
                err = dbs.NativeSQLStore.ApplyAllMigrations(ctx, migrations)
×
1136
                if err != nil {
×
1137
                        cleanUp()
×
1138
                        err = fmt.Errorf("faild to run migrations for the "+
×
1139
                                "native SQL store: %w", err)
×
1140
                        d.logger.Error(err)
×
1141

×
1142
                        return nil, nil, err
×
1143
                }
×
1144

1145
                // With the DB ready and migrations applied, we can now create
1146
                // the base DB and transaction executor for the native SQL
1147
                // invoice store.
1148
                baseDB := dbs.NativeSQLStore.GetBaseDB()
×
1149
                executor := sqldb.NewTransactionExecutor(
×
1150
                        baseDB, func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1151
                                return baseDB.WithTx(tx)
×
1152
                        },
×
1153
                )
1154

1155
                sqlInvoiceDB := invoices.NewSQLStore(
×
1156
                        executor, clock.NewDefaultClock(),
×
1157
                )
×
1158

×
1159
                dbs.InvoiceDB = sqlInvoiceDB
×
UNCOV
1160
        } else {
×
UNCOV
1161
                // Check if the invoice bucket tombstone is set. If it is, we
×
UNCOV
1162
                // need to return and ask the user switch back to using the
×
UNCOV
1163
                // native SQL store.
×
UNCOV
1164
                ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
×
UNCOV
1165
                d.logger.Debugf("Invoice bucket tombstone set to: %v",
×
UNCOV
1166
                        ripInvoices)
×
UNCOV
1167

×
UNCOV
1168
                if err != nil {
×
1169
                        err = fmt.Errorf("unable to check invoice bucket "+
×
1170
                                "tombstone: %w", err)
×
1171
                        d.logger.Error(err)
×
1172

×
1173
                        return nil, nil, err
×
1174
                }
×
UNCOV
1175
                if ripInvoices {
×
1176
                        err = fmt.Errorf("invoices bucket tombstoned, please " +
×
1177
                                "switch back to native SQL")
×
1178
                        d.logger.Error(err)
×
1179

×
1180
                        return nil, nil, err
×
1181
                }
×
1182

UNCOV
1183
                dbs.InvoiceDB = dbs.ChanStateDB
×
1184
        }
1185

1186
        // Wrap the watchtower client DB and make sure we clean up.
UNCOV
1187
        if cfg.WtClient.Active {
×
UNCOV
1188
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
×
UNCOV
1189
                        databaseBackends.TowerClientDB,
×
UNCOV
1190
                )
×
UNCOV
1191
                if err != nil {
×
1192
                        cleanUp()
×
1193

×
1194
                        err = fmt.Errorf("unable to open %s database: %w",
×
1195
                                lncfg.NSTowerClientDB, err)
×
1196
                        d.logger.Error(err)
×
1197
                        return nil, nil, err
×
1198
                }
×
1199
        }
1200

1201
        // Wrap the watchtower server DB and make sure we clean up.
UNCOV
1202
        if cfg.Watchtower.Active {
×
UNCOV
1203
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
×
UNCOV
1204
                        databaseBackends.TowerServerDB,
×
UNCOV
1205
                )
×
UNCOV
1206
                if err != nil {
×
1207
                        cleanUp()
×
1208

×
1209
                        err = fmt.Errorf("unable to open %s database: %w",
×
1210
                                lncfg.NSTowerServerDB, err)
×
1211
                        d.logger.Error(err)
×
1212
                        return nil, nil, err
×
1213
                }
×
1214
        }
1215

UNCOV
1216
        openTime := time.Since(startOpenTime)
×
UNCOV
1217
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
×
UNCOV
1218

×
UNCOV
1219
        return dbs, cleanUp, nil
×
1220
}
1221

1222
// waitForWalletPassword blocks until a password is provided by the user to
1223
// this RPC server.
1224
func waitForWalletPassword(cfg *Config,
1225
        pwService *walletunlocker.UnlockerService,
1226
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
UNCOV
1227
        *walletunlocker.WalletUnlockParams, error) {
×
UNCOV
1228

×
UNCOV
1229
        // Wait for user to provide the password.
×
UNCOV
1230
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
×
UNCOV
1231
                "create` to create a wallet, `lncli unlock` to unlock an " +
×
UNCOV
1232
                "existing wallet, or `lncli changepassword` to change the " +
×
UNCOV
1233
                "password of an existing wallet and unlock it.")
×
UNCOV
1234

×
UNCOV
1235
        // We currently don't distinguish between getting a password to be used
×
UNCOV
1236
        // for creation or unlocking, as a new wallet db will be created if
×
UNCOV
1237
        // none exists when creating the chain control.
×
UNCOV
1238
        select {
×
1239
        // The wallet is being created for the first time, we'll check to see
1240
        // if the user provided any entropy for seed creation. If so, then
1241
        // we'll create the wallet early to load the seed.
UNCOV
1242
        case initMsg := <-pwService.InitMsgs:
×
UNCOV
1243
                password := initMsg.Passphrase
×
UNCOV
1244
                cipherSeed := initMsg.WalletSeed
×
UNCOV
1245
                extendedKey := initMsg.WalletExtendedKey
×
UNCOV
1246
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
×
UNCOV
1247
                recoveryWindow := initMsg.RecoveryWindow
×
UNCOV
1248

×
UNCOV
1249
                // Before we proceed, we'll check the internal version of the
×
UNCOV
1250
                // seed. If it's greater than the current key derivation
×
UNCOV
1251
                // version, then we'll return an error as we don't understand
×
UNCOV
1252
                // this.
×
UNCOV
1253
                if cipherSeed != nil &&
×
UNCOV
1254
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
×
1255

×
1256
                        return nil, fmt.Errorf("invalid internal "+
×
1257
                                "seed version %v, current max version is %v",
×
1258
                                cipherSeed.InternalVersion,
×
1259
                                keychain.CurrentKeyDerivationVersion)
×
1260
                }
×
1261

UNCOV
1262
                loader, err := btcwallet.NewWalletLoader(
×
UNCOV
1263
                        cfg.ActiveNetParams.Params, recoveryWindow,
×
UNCOV
1264
                        loaderOpts...,
×
UNCOV
1265
                )
×
UNCOV
1266
                if err != nil {
×
1267
                        return nil, err
×
1268
                }
×
1269

1270
                // With the seed, we can now use the wallet loader to create
1271
                // the wallet, then pass it back to avoid unlocking it again.
UNCOV
1272
                var (
×
UNCOV
1273
                        birthday  time.Time
×
UNCOV
1274
                        newWallet *wallet.Wallet
×
UNCOV
1275
                )
×
UNCOV
1276
                switch {
×
1277
                // A normal cipher seed was given, use the birthday encoded in
1278
                // it and create the wallet from that.
UNCOV
1279
                case cipherSeed != nil:
×
UNCOV
1280
                        birthday = cipherSeed.BirthdayTime()
×
UNCOV
1281
                        newWallet, err = loader.CreateNewWallet(
×
UNCOV
1282
                                password, password, cipherSeed.Entropy[:],
×
UNCOV
1283
                                birthday,
×
UNCOV
1284
                        )
×
1285

1286
                // No seed was given, we're importing a wallet from its extended
1287
                // private key.
UNCOV
1288
                case extendedKey != nil:
×
UNCOV
1289
                        birthday = initMsg.ExtendedKeyBirthday
×
UNCOV
1290
                        newWallet, err = loader.CreateNewWalletExtendedKey(
×
UNCOV
1291
                                password, password, extendedKey, birthday,
×
UNCOV
1292
                        )
×
1293

1294
                // Neither seed nor extended private key was given, so maybe the
1295
                // third option was chosen, the watch-only initialization. In
1296
                // this case we need to import each of the xpubs individually.
UNCOV
1297
                case watchOnlyAccounts != nil:
×
UNCOV
1298
                        if !cfg.RemoteSigner.Enable {
×
1299
                                return nil, fmt.Errorf("cannot initialize " +
×
1300
                                        "watch only wallet with remote " +
×
1301
                                        "signer config disabled")
×
1302
                        }
×
1303

UNCOV
1304
                        birthday = initMsg.WatchOnlyBirthday
×
UNCOV
1305
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
×
UNCOV
1306
                                password, birthday,
×
UNCOV
1307
                        )
×
UNCOV
1308
                        if err != nil {
×
1309
                                break
×
1310
                        }
1311

UNCOV
1312
                        err = importWatchOnlyAccounts(newWallet, initMsg)
×
1313

1314
                default:
×
1315
                        // The unlocker service made sure either the cipher seed
×
1316
                        // or the extended key is set so, we shouldn't get here.
×
1317
                        // The default case is just here for readability and
×
1318
                        // completeness.
×
1319
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1320
                                "nor extended key was given")
×
1321
                }
UNCOV
1322
                if err != nil {
×
1323
                        // Don't leave the file open in case the new wallet
×
1324
                        // could not be created for whatever reason.
×
1325
                        if err := loader.UnloadWallet(); err != nil {
×
1326
                                ltndLog.Errorf("Could not unload new "+
×
1327
                                        "wallet: %v", err)
×
1328
                        }
×
1329
                        return nil, err
×
1330
                }
1331

1332
                // For new wallets, the ResetWalletTransactions flag is a no-op.
UNCOV
1333
                if cfg.ResetWalletTransactions {
×
UNCOV
1334
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
×
UNCOV
1335
                                "flag for new wallet as it has no effect")
×
UNCOV
1336
                }
×
1337

UNCOV
1338
                return &walletunlocker.WalletUnlockParams{
×
UNCOV
1339
                        Password:        password,
×
UNCOV
1340
                        Birthday:        birthday,
×
UNCOV
1341
                        RecoveryWindow:  recoveryWindow,
×
UNCOV
1342
                        Wallet:          newWallet,
×
UNCOV
1343
                        ChansToRestore:  initMsg.ChanBackups,
×
UNCOV
1344
                        UnloadWallet:    loader.UnloadWallet,
×
UNCOV
1345
                        StatelessInit:   initMsg.StatelessInit,
×
UNCOV
1346
                        MacResponseChan: pwService.MacResponseChan,
×
UNCOV
1347
                        MacRootKey:      initMsg.MacRootKey,
×
UNCOV
1348
                }, nil
×
1349

1350
        // The wallet has already been created in the past, and is simply being
1351
        // unlocked. So we'll just return these passphrases.
UNCOV
1352
        case unlockMsg := <-pwService.UnlockMsgs:
×
UNCOV
1353
                // Resetting the transactions is something the user likely only
×
UNCOV
1354
                // wants to do once so we add a prominent warning to the log to
×
UNCOV
1355
                // remind the user to turn off the setting again after
×
UNCOV
1356
                // successful completion.
×
UNCOV
1357
                if cfg.ResetWalletTransactions {
×
UNCOV
1358
                        ltndLog.Warnf("Dropped all transaction history from " +
×
UNCOV
1359
                                "on-chain wallet. Remember to disable " +
×
UNCOV
1360
                                "reset-wallet-transactions flag for next " +
×
UNCOV
1361
                                "start of lnd")
×
UNCOV
1362
                }
×
1363

UNCOV
1364
                return &walletunlocker.WalletUnlockParams{
×
UNCOV
1365
                        Password:        unlockMsg.Passphrase,
×
UNCOV
1366
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
×
UNCOV
1367
                        Wallet:          unlockMsg.Wallet,
×
UNCOV
1368
                        ChansToRestore:  unlockMsg.ChanBackups,
×
UNCOV
1369
                        UnloadWallet:    unlockMsg.UnloadWallet,
×
UNCOV
1370
                        StatelessInit:   unlockMsg.StatelessInit,
×
UNCOV
1371
                        MacResponseChan: pwService.MacResponseChan,
×
UNCOV
1372
                }, nil
×
1373

1374
        // If we got a shutdown signal we just return with an error immediately
1375
        case <-shutdownChan:
×
1376
                return nil, fmt.Errorf("shutting down")
×
1377
        }
1378
}
1379

1380
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1381
// which we created as watch-only.
1382
func importWatchOnlyAccounts(wallet *wallet.Wallet,
UNCOV
1383
        initMsg *walletunlocker.WalletInitMsg) error {
×
UNCOV
1384

×
UNCOV
1385
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
×
UNCOV
1386
        for scope := range initMsg.WatchOnlyAccounts {
×
UNCOV
1387
                scopes = append(scopes, scope)
×
UNCOV
1388
        }
×
1389

1390
        // We need to import the accounts in the correct order, otherwise the
1391
        // indices will be incorrect.
UNCOV
1392
        sort.Slice(scopes, func(i, j int) bool {
×
UNCOV
1393
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
×
UNCOV
1394
                        scopes[i].Index < scopes[j].Index
×
UNCOV
1395
        })
×
1396

UNCOV
1397
        for _, scope := range scopes {
×
UNCOV
1398
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
×
UNCOV
1399

×
UNCOV
1400
                // We want witness pubkey hash by default, except for BIP49
×
UNCOV
1401
                // where we want mixed and BIP86 where we want taproot address
×
UNCOV
1402
                // formats.
×
UNCOV
1403
                switch scope.Scope.Purpose {
×
1404
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
UNCOV
1405
                        waddrmgr.KeyScopeBIP0086.Purpose:
×
UNCOV
1406

×
UNCOV
1407
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
×
1408
                }
1409

1410
                // We want a human-readable account name. But for the default
1411
                // on-chain wallet we actually need to call it "default" to make
1412
                // sure everything works correctly.
UNCOV
1413
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
×
UNCOV
1414
                if scope.Index == 0 {
×
UNCOV
1415
                        name = "default"
×
UNCOV
1416
                }
×
1417

UNCOV
1418
                _, err := wallet.ImportAccountWithScope(
×
UNCOV
1419
                        name, initMsg.WatchOnlyAccounts[scope],
×
UNCOV
1420
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
×
UNCOV
1421
                        addrSchema,
×
UNCOV
1422
                )
×
UNCOV
1423
                if err != nil {
×
1424
                        return fmt.Errorf("could not import account %v: %w",
×
1425
                                name, err)
×
1426
                }
×
1427
        }
1428

UNCOV
1429
        return nil
×
1430
}
1431

1432
// initNeutrinoBackend inits a new instance of the neutrino light client
1433
// backend given a target chain directory to store the chain state.
1434
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1435
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
UNCOV
1436
        func(), error) {
×
UNCOV
1437

×
UNCOV
1438
        // Both channel validation flags are false by default but their meaning
×
UNCOV
1439
        // is the inverse of each other. Therefore both cannot be true. For
×
UNCOV
1440
        // every other case, the neutrino.validatechannels overwrites the
×
UNCOV
1441
        // routing.assumechanvalid value.
×
UNCOV
1442
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
×
1443
                return nil, nil, fmt.Errorf("can't set both " +
×
1444
                        "neutrino.validatechannels and routing." +
×
1445
                        "assumechanvalid to true at the same time")
×
1446
        }
×
UNCOV
1447
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
UNCOV
1448

×
UNCOV
1449
        // First we'll open the database file for neutrino, creating the
×
UNCOV
1450
        // database if needed. We append the normalized network name here to
×
UNCOV
1451
        // match the behavior of btcwallet.
×
UNCOV
1452
        dbPath := filepath.Join(
×
UNCOV
1453
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
1454
        )
×
UNCOV
1455

×
UNCOV
1456
        // Ensure that the neutrino db path exists.
×
UNCOV
1457
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1458
                return nil, nil, err
×
1459
        }
×
1460

UNCOV
1461
        var (
×
UNCOV
1462
                db  walletdb.DB
×
UNCOV
1463
                err error
×
UNCOV
1464
        )
×
UNCOV
1465
        switch {
×
1466
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1467
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1468
                db, err = kvdb.Open(
×
1469
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1470
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1471
                )
×
1472

UNCOV
1473
        default:
×
UNCOV
1474
                dbName := filepath.Join(dbPath, "neutrino.db")
×
UNCOV
1475
                db, err = walletdb.Create(
×
UNCOV
1476
                        "bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
×
UNCOV
1477
                )
×
1478
        }
UNCOV
1479
        if err != nil {
×
1480
                return nil, nil, fmt.Errorf("unable to create "+
×
1481
                        "neutrino database: %v", err)
×
1482
        }
×
1483

UNCOV
1484
        headerStateAssertion, err := parseHeaderStateAssertion(
×
UNCOV
1485
                cfg.NeutrinoMode.AssertFilterHeader,
×
UNCOV
1486
        )
×
UNCOV
1487
        if err != nil {
×
1488
                db.Close()
×
1489
                return nil, nil, err
×
1490
        }
×
1491

1492
        // With the database open, we can now create an instance of the
1493
        // neutrino light client. We pass in relevant configuration parameters
1494
        // required.
UNCOV
1495
        config := neutrino.Config{
×
UNCOV
1496
                DataDir:      dbPath,
×
UNCOV
1497
                Database:     db,
×
UNCOV
1498
                ChainParams:  *cfg.ActiveNetParams.Params,
×
UNCOV
1499
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
UNCOV
1500
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
UNCOV
1501
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
UNCOV
1502
                        return cfg.net.Dial(
×
UNCOV
1503
                                addr.Network(), addr.String(),
×
UNCOV
1504
                                cfg.ConnectionTimeout,
×
UNCOV
1505
                        )
×
UNCOV
1506
                },
×
UNCOV
1507
                NameResolver: func(host string) ([]net.IP, error) {
×
UNCOV
1508
                        addrs, err := cfg.net.LookupHost(host)
×
UNCOV
1509
                        if err != nil {
×
1510
                                return nil, err
×
1511
                        }
×
1512

UNCOV
1513
                        ips := make([]net.IP, 0, len(addrs))
×
UNCOV
1514
                        for _, strIP := range addrs {
×
UNCOV
1515
                                ip := net.ParseIP(strIP)
×
UNCOV
1516
                                if ip == nil {
×
1517
                                        continue
×
1518
                                }
1519

UNCOV
1520
                                ips = append(ips, ip)
×
1521
                        }
1522

UNCOV
1523
                        return ips, nil
×
1524
                },
1525
                AssertFilterHeader: headerStateAssertion,
1526
                BlockCache:         blockCache.Cache,
1527
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1528
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1529
        }
1530

UNCOV
1531
        neutrino.MaxPeers = 8
×
UNCOV
1532
        neutrino.BanDuration = time.Hour * 48
×
UNCOV
1533
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
×
UNCOV
1534
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
×
UNCOV
1535

×
UNCOV
1536
        neutrinoCS, err := neutrino.NewChainService(config)
×
UNCOV
1537
        if err != nil {
×
1538
                db.Close()
×
1539
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1540
                        "client: %v", err)
×
1541
        }
×
1542

UNCOV
1543
        if err := neutrinoCS.Start(); err != nil {
×
1544
                db.Close()
×
1545
                return nil, nil, err
×
1546
        }
×
1547

UNCOV
1548
        cleanUp := func() {
×
UNCOV
1549
                if err := neutrinoCS.Stop(); err != nil {
×
1550
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1551
                                "%v", err)
×
1552
                }
×
UNCOV
1553
                db.Close()
×
1554
        }
1555

UNCOV
1556
        return neutrinoCS, cleanUp, nil
×
1557
}
1558

1559
// parseHeaderStateAssertion parses the user-specified neutrino header state
1560
// into a headerfs.FilterHeader.
UNCOV
1561
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
UNCOV
1562
        if len(state) == 0 {
×
UNCOV
1563
                return nil, nil
×
UNCOV
1564
        }
×
1565

1566
        split := strings.Split(state, ":")
×
1567
        if len(split) != 2 {
×
1568
                return nil, fmt.Errorf("header state assertion %v in "+
×
1569
                        "unexpected format, expected format height:hash", state)
×
1570
        }
×
1571

1572
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1573
        if err != nil {
×
1574
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1575
        }
×
1576

1577
        hash, err := chainhash.NewHashFromStr(split[1])
×
1578
        if err != nil {
×
1579
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1580
        }
×
1581

1582
        return &headerfs.FilterHeader{
×
1583
                Height:     uint32(height),
×
1584
                FilterHash: *hash,
×
1585
        }, nil
×
1586
}
1587

1588
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1589
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1590
// resides in the neutrino package to use all of its functionalities.
UNCOV
1591
func broadcastErrorMapper(err error) error {
×
UNCOV
1592
        var returnErr error
×
UNCOV
1593

×
UNCOV
1594
        // We only filter for specific backend errors which are relevant for the
×
UNCOV
1595
        // Rebroadcaster.
×
UNCOV
1596
        switch {
×
1597
        // This makes sure the tx is removed from the rebroadcaster once it is
1598
        // confirmed.
1599
        case errors.Is(err, chain.ErrTxAlreadyKnown),
UNCOV
1600
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
×
UNCOV
1601

×
UNCOV
1602
                returnErr = &pushtx.BroadcastError{
×
UNCOV
1603
                        Code:   pushtx.Confirmed,
×
UNCOV
1604
                        Reason: err.Error(),
×
UNCOV
1605
                }
×
1606

1607
        // Transactions which are still in mempool but might fall out because
1608
        // of low fees are rebroadcasted despite of their backend error.
1609
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1610
                returnErr = &pushtx.BroadcastError{
×
1611
                        Code:   pushtx.Mempool,
×
1612
                        Reason: err.Error(),
×
1613
                }
×
1614

1615
        // Transactions which are not accepted into mempool because of low fees
1616
        // in the first place are rebroadcasted despite of their backend error.
1617
        // Mempool conditions change over time so it makes sense to retry
1618
        // publishing the transaction. Moreover we log the detailed error so the
1619
        // user can intervene and increase the size of his mempool.
1620
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
×
1621
                ltndLog.Warnf("Error while broadcasting transaction: %v", err)
×
1622

×
1623
                returnErr = &pushtx.BroadcastError{
×
1624
                        Code:   pushtx.Mempool,
×
1625
                        Reason: err.Error(),
×
1626
                }
×
1627
        }
1628

UNCOV
1629
        return returnErr
×
1630
}
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