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

lightningnetwork / lnd / 14513053602

17 Apr 2025 09:56AM UTC coverage: 56.754% (-12.3%) from 69.035%
14513053602

Pull #9727

github

web-flow
Merge 5fb0f4317 into 24fdae7df
Pull Request #9727: Aux bandwidth manager: also pass HTLC blob to `ShouldHandleTraffic`

3 of 8 new or added lines in 2 files covered. (37.5%)

24357 existing lines in 290 files now uncovered.

107518 of 189445 relevant lines covered (56.75%)

22634.92 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, macaroons.IPRangeLockChecker,
×
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.KVStoreOptionModifier{
×
UNCOV
1030
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
×
UNCOV
1031
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
×
UNCOV
1032
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
×
UNCOV
1033
        }
×
UNCOV
1034

×
UNCOV
1035
        chanGraphOpts := []graphdb.ChanGraphOption{
×
UNCOV
1036
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
×
UNCOV
1037
        }
×
UNCOV
1038

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

UNCOV
1049
        dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{
×
UNCOV
1050
                KVDB:        databaseBackends.GraphDB,
×
UNCOV
1051
                KVStoreOpts: graphDBOptions,
×
UNCOV
1052
        }, chanGraphOpts...)
×
UNCOV
1053
        if err != nil {
×
1054
                cleanUp()
×
1055

×
1056
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1057
                d.logger.Error(err)
×
1058

×
1059
                return nil, nil, err
×
1060
        }
×
1061

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

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

1087
        case err != nil:
×
1088
                cleanUp()
×
1089

×
1090
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1091
                d.logger.Error(err)
×
1092
                return nil, nil, err
×
1093
        }
1094

1095
        // Instantiate a native SQL store if the flag is set.
UNCOV
1096
        if d.cfg.DB.UseNativeSQL {
×
1097
                migrations := sqldb.GetMigrations()
×
1098

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

1118
                                // Set the invoice bucket tombstone to indicate
1119
                                // that the migration has been completed.
1120
                                d.logger.Debugf("Setting invoice bucket " +
×
1121
                                        "tombstone")
×
1122

×
1123
                                return dbs.ChanStateDB.SetInvoiceBucketTombstone() //nolint:ll
×
1124
                        }
1125

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

1133
                                migrations[i].MigrationFn = migrationFn
×
1134
                        }
1135
                }
1136

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

×
1146
                        return nil, nil, err
×
1147
                }
×
1148

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

1159
                sqlInvoiceDB := invoices.NewSQLStore(
×
1160
                        executor, clock.NewDefaultClock(),
×
1161
                )
×
1162

×
1163
                dbs.InvoiceDB = sqlInvoiceDB
×
UNCOV
1164
        } else {
×
UNCOV
1165
                // Check if the invoice bucket tombstone is set. If it is, we
×
UNCOV
1166
                // need to return and ask the user switch back to using the
×
UNCOV
1167
                // native SQL store.
×
UNCOV
1168
                ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
×
UNCOV
1169
                if err != nil {
×
1170
                        err = fmt.Errorf("unable to check invoice bucket "+
×
1171
                                "tombstone: %w", err)
×
1172
                        d.logger.Error(err)
×
1173

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
1430
        return nil
×
1431
}
1432

1433
// handleNeutrinoPostgresDBMigration handles the migration of the neutrino db
1434
// to postgres. Initially we kept the neutrino db in the bolt db when running
1435
// with kvdb postgres backend. Now now move it to postgres as well. However we
1436
// need to make a distinction whether the user migrated the neutrino db to
1437
// postgres via lndinit or not. Currently if the db is not migrated we start
1438
// with a fresh db in postgres.
1439
//
1440
// TODO(ziggie): Also migrate the db to postgres in case it is still not
1441
// migrated ?
1442
func handleNeutrinoPostgresDBMigration(dbName, dbPath string,
1443
        cfg *Config) error {
×
1444

×
1445
        if !lnrpc.FileExists(dbName) {
×
1446
                return nil
×
1447
        }
×
1448

1449
        // Open bolt db to check if it is tombstoned. If it is we assume that
1450
        // the neutrino db was successfully migrated to postgres. We open it
1451
        // in read-only mode to avoid long db open times.
1452
        boltDB, err := kvdb.Open(
×
1453
                kvdb.BoltBackendName, dbName, true,
×
1454
                cfg.DB.Bolt.DBTimeout, true,
×
1455
        )
×
1456
        if err != nil {
×
1457
                return fmt.Errorf("failed to open bolt db: %w", err)
×
1458
        }
×
1459
        defer boltDB.Close()
×
1460

×
1461
        isTombstoned := false
×
1462
        err = boltDB.View(func(tx kvdb.RTx) error {
×
1463
                _, err = channeldb.CheckMarkerPresent(
×
1464
                        tx, channeldb.TombstoneKey,
×
1465
                )
×
1466

×
1467
                return err
×
1468
        }, func() {})
×
1469
        if err == nil {
×
1470
                isTombstoned = true
×
1471
        }
×
1472

1473
        if isTombstoned {
×
1474
                ltndLog.Infof("Neutrino Bolt DB is tombstoned, assuming " +
×
1475
                        "database was successfully migrated to postgres")
×
1476

×
1477
                return nil
×
1478
        }
×
1479

1480
        // If the db is not tombstoned, we remove the files and start fresh with
1481
        // postgres. This is the case when a user was running lnd with the
1482
        // postgres backend from the beginning without migrating from bolt.
1483
        ltndLog.Infof("Neutrino Bolt DB found but NOT tombstoned, removing " +
×
1484
                "it and starting fresh with postgres")
×
1485

×
1486
        filesToRemove := []string{
×
1487
                filepath.Join(dbPath, "block_headers.bin"),
×
1488
                filepath.Join(dbPath, "reg_filter_headers.bin"),
×
1489
                dbName,
×
1490
        }
×
1491

×
1492
        for _, file := range filesToRemove {
×
1493
                if err := os.Remove(file); err != nil {
×
1494
                        ltndLog.Warnf("Could not remove %s: %v", file, err)
×
1495
                }
×
1496
        }
1497

1498
        return nil
×
1499
}
1500

1501
// initNeutrinoBackend inits a new instance of the neutrino light client
1502
// backend given a target chain directory to store the chain state.
1503
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1504
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
UNCOV
1505
        func(), error) {
×
UNCOV
1506

×
UNCOV
1507
        // Both channel validation flags are false by default but their meaning
×
UNCOV
1508
        // is the inverse of each other. Therefore both cannot be true. For
×
UNCOV
1509
        // every other case, the neutrino.validatechannels overwrites the
×
UNCOV
1510
        // routing.assumechanvalid value.
×
UNCOV
1511
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
×
1512
                return nil, nil, fmt.Errorf("can't set both " +
×
1513
                        "neutrino.validatechannels and routing." +
×
1514
                        "assumechanvalid to true at the same time")
×
1515
        }
×
UNCOV
1516
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
UNCOV
1517

×
UNCOV
1518
        // First we'll open the database file for neutrino, creating the
×
UNCOV
1519
        // database if needed. We append the normalized network name here to
×
UNCOV
1520
        // match the behavior of btcwallet.
×
UNCOV
1521
        dbPath := filepath.Join(
×
UNCOV
1522
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
1523
        )
×
UNCOV
1524

×
UNCOV
1525
        // Ensure that the neutrino db path exists.
×
UNCOV
1526
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1527
                return nil, nil, err
×
1528
        }
×
1529

UNCOV
1530
        var (
×
UNCOV
1531
                db  walletdb.DB
×
UNCOV
1532
                err error
×
UNCOV
1533
        )
×
UNCOV
1534
        switch {
×
1535
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1536
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1537
                db, err = kvdb.Open(
×
1538
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1539
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1540
                )
×
1541

1542
        case cfg.DB.Backend == kvdb.PostgresBackendName:
×
1543
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
1544

×
1545
                // This code needs to be in place because we did not start
×
1546
                // the postgres backend for neutrino at the beginning. Now we
×
1547
                // are also moving it into the postgres backend so we can phase
×
1548
                // out the bolt backend.
×
1549
                err = handleNeutrinoPostgresDBMigration(dbName, dbPath, cfg)
×
1550
                if err != nil {
×
1551
                        return nil, nil, err
×
1552
                }
×
1553

1554
                postgresConfig := lncfg.GetPostgresConfigKVDB(cfg.DB.Postgres)
×
1555
                db, err = kvdb.Open(
×
1556
                        kvdb.PostgresBackendName, ctx, postgresConfig,
×
1557
                        lncfg.NSNeutrinoDB,
×
1558
                )
×
1559

UNCOV
1560
        default:
×
UNCOV
1561
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
UNCOV
1562
                db, err = walletdb.Create(
×
UNCOV
1563
                        kvdb.BoltBackendName, dbName, !cfg.SyncFreelist,
×
UNCOV
1564
                        cfg.DB.Bolt.DBTimeout, false,
×
UNCOV
1565
                )
×
1566
        }
UNCOV
1567
        if err != nil {
×
1568
                return nil, nil, fmt.Errorf("unable to create "+
×
1569
                        "neutrino database: %v", err)
×
1570
        }
×
1571

UNCOV
1572
        headerStateAssertion, err := parseHeaderStateAssertion(
×
UNCOV
1573
                cfg.NeutrinoMode.AssertFilterHeader,
×
UNCOV
1574
        )
×
UNCOV
1575
        if err != nil {
×
1576
                db.Close()
×
1577
                return nil, nil, err
×
1578
        }
×
1579

1580
        // With the database open, we can now create an instance of the
1581
        // neutrino light client. We pass in relevant configuration parameters
1582
        // required.
UNCOV
1583
        config := neutrino.Config{
×
UNCOV
1584
                DataDir:      dbPath,
×
UNCOV
1585
                Database:     db,
×
UNCOV
1586
                ChainParams:  *cfg.ActiveNetParams.Params,
×
UNCOV
1587
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
UNCOV
1588
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
UNCOV
1589
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
UNCOV
1590
                        return cfg.net.Dial(
×
UNCOV
1591
                                addr.Network(), addr.String(),
×
UNCOV
1592
                                cfg.ConnectionTimeout,
×
UNCOV
1593
                        )
×
UNCOV
1594
                },
×
UNCOV
1595
                NameResolver: func(host string) ([]net.IP, error) {
×
UNCOV
1596
                        addrs, err := cfg.net.LookupHost(host)
×
UNCOV
1597
                        if err != nil {
×
1598
                                return nil, err
×
1599
                        }
×
1600

UNCOV
1601
                        ips := make([]net.IP, 0, len(addrs))
×
UNCOV
1602
                        for _, strIP := range addrs {
×
UNCOV
1603
                                ip := net.ParseIP(strIP)
×
UNCOV
1604
                                if ip == nil {
×
1605
                                        continue
×
1606
                                }
1607

UNCOV
1608
                                ips = append(ips, ip)
×
1609
                        }
1610

UNCOV
1611
                        return ips, nil
×
1612
                },
1613
                AssertFilterHeader: headerStateAssertion,
1614
                BlockCache:         blockCache.Cache,
1615
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1616
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1617
        }
1618

UNCOV
1619
        if cfg.NeutrinoMode.MaxPeers <= 0 {
×
1620
                return nil, nil, fmt.Errorf("a non-zero number must be set " +
×
1621
                        "for neutrino max peers")
×
1622
        }
×
UNCOV
1623
        neutrino.MaxPeers = cfg.NeutrinoMode.MaxPeers
×
UNCOV
1624
        neutrino.BanDuration = time.Hour * 48
×
UNCOV
1625
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
×
UNCOV
1626
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
×
UNCOV
1627

×
UNCOV
1628
        neutrinoCS, err := neutrino.NewChainService(config)
×
UNCOV
1629
        if err != nil {
×
1630
                db.Close()
×
1631
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1632
                        "client: %v", err)
×
1633
        }
×
1634

UNCOV
1635
        if err := neutrinoCS.Start(); err != nil {
×
1636
                db.Close()
×
1637
                return nil, nil, err
×
1638
        }
×
1639

UNCOV
1640
        cleanUp := func() {
×
UNCOV
1641
                if err := neutrinoCS.Stop(); err != nil {
×
1642
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1643
                                "%v", err)
×
1644
                }
×
UNCOV
1645
                db.Close()
×
1646
        }
1647

UNCOV
1648
        return neutrinoCS, cleanUp, nil
×
1649
}
1650

1651
// parseHeaderStateAssertion parses the user-specified neutrino header state
1652
// into a headerfs.FilterHeader.
UNCOV
1653
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
UNCOV
1654
        if len(state) == 0 {
×
UNCOV
1655
                return nil, nil
×
UNCOV
1656
        }
×
1657

1658
        split := strings.Split(state, ":")
×
1659
        if len(split) != 2 {
×
1660
                return nil, fmt.Errorf("header state assertion %v in "+
×
1661
                        "unexpected format, expected format height:hash", state)
×
1662
        }
×
1663

1664
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1665
        if err != nil {
×
1666
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1667
        }
×
1668

1669
        hash, err := chainhash.NewHashFromStr(split[1])
×
1670
        if err != nil {
×
1671
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1672
        }
×
1673

1674
        return &headerfs.FilterHeader{
×
1675
                Height:     uint32(height),
×
1676
                FilterHash: *hash,
×
1677
        }, nil
×
1678
}
1679

1680
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1681
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1682
// resides in the neutrino package to use all of its functionalities.
UNCOV
1683
func broadcastErrorMapper(err error) error {
×
UNCOV
1684
        var returnErr error
×
UNCOV
1685

×
UNCOV
1686
        // We only filter for specific backend errors which are relevant for the
×
UNCOV
1687
        // Rebroadcaster.
×
UNCOV
1688
        switch {
×
1689
        // This makes sure the tx is removed from the rebroadcaster once it is
1690
        // confirmed.
1691
        case errors.Is(err, chain.ErrTxAlreadyKnown),
UNCOV
1692
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
×
UNCOV
1693

×
UNCOV
1694
                returnErr = &pushtx.BroadcastError{
×
UNCOV
1695
                        Code:   pushtx.Confirmed,
×
UNCOV
1696
                        Reason: err.Error(),
×
UNCOV
1697
                }
×
1698

1699
        // Transactions which are still in mempool but might fall out because
1700
        // of low fees are rebroadcasted despite of their backend error.
1701
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1702
                returnErr = &pushtx.BroadcastError{
×
1703
                        Code:   pushtx.Mempool,
×
1704
                        Reason: err.Error(),
×
1705
                }
×
1706

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

×
1715
                returnErr = &pushtx.BroadcastError{
×
1716
                        Code:   pushtx.Mempool,
×
1717
                        Reason: err.Error(),
×
1718
                }
×
1719
        }
1720

UNCOV
1721
        return returnErr
×
1722
}
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