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

lightningnetwork / lnd / 18140636176

30 Sep 2025 07:05PM UTC coverage: 66.58%. First build
18140636176

Pull #9147

github

web-flow
Merge ca3b10b13 into 90c96c7df
Pull Request #9147: [Part 1|3] Introduce SQL Payment schema into LND

9 of 47 new or added lines in 3 files covered. (19.15%)

136724 of 205352 relevant lines covered (66.58%)

21321.39 hits per line

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

58.57
/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
        paymentsdb "github.com/lightningnetwork/lnd/payments/db"
52
        "github.com/lightningnetwork/lnd/rpcperms"
53
        "github.com/lightningnetwork/lnd/signal"
54
        "github.com/lightningnetwork/lnd/sqldb"
55
        "github.com/lightningnetwork/lnd/sqldb/sqlc"
56
        "github.com/lightningnetwork/lnd/sweep"
57
        "github.com/lightningnetwork/lnd/walletunlocker"
58
        "github.com/lightningnetwork/lnd/watchtower"
59
        "github.com/lightningnetwork/lnd/watchtower/wtclient"
60
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
61
        "google.golang.org/grpc"
62
        "gopkg.in/macaroon-bakery.v2/bakery"
63
)
64

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

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

74
        // graphMigration is the version number for the graph migration
75
        // that migrates the KV graph to the native SQL schema.
76
        graphMigration = 10
77
)
78

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

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

102
// ExternalValidator is an interface that must be satisfied by an external
103
// macaroon validator.
104
type ExternalValidator interface {
105
        macaroons.MacaroonValidator
106

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

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

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

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

144
// ImplementationCfg is a struct that holds all configuration items for
145
// components that can be implemented outside lnd itself.
146
type ImplementationCfg struct {
147
        // GrpcRegistrar is a type that can register additional gRPC subservers
148
        // before the main gRPC server is started.
149
        GrpcRegistrar
150

151
        // RestRegistrar is a type that can register additional REST subservers
152
        // before the main REST proxy is started.
153
        RestRegistrar
154

155
        // ExternalValidator is a type that can provide external macaroon
156
        // validation.
157
        ExternalValidator
158

159
        // DatabaseBuilder is a type that can provide lnd's main database
160
        // backend instances.
161
        DatabaseBuilder
162

163
        // WalletConfigBuilder is a type that can provide a wallet configuration
164
        // with a fully loaded and unlocked wallet.
165
        WalletConfigBuilder
166

167
        // ChainControlBuilder is a type that can provide a custom wallet
168
        // implementation.
169
        ChainControlBuilder
170

171
        // AuxComponents is a set of auxiliary components that can be used by
172
        // lnd for certain custom channel types.
173
        AuxComponents
174
}
175

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

183
        // TrafficShaper is an optional traffic shaper that can be used to
184
        // control the outgoing channel of a payment.
185
        TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
186

187
        // MsgRouter is an optional message router that if set will be used in
188
        // place of a new blank default message router.
189
        MsgRouter fn.Option[msgmux.Router]
190

191
        // AuxFundingController is an optional controller that can be used to
192
        // modify the way we handle certain custom channel types. It's also
193
        // able to automatically handle new custom protocol messages related to
194
        // the funding process.
195
        AuxFundingController fn.Option[funding.AuxFundingController]
196

197
        // AuxSigner is an optional signer that can be used to sign auxiliary
198
        // leaves for certain custom channel types.
199
        AuxSigner fn.Option[lnwallet.AuxSigner]
200

201
        // AuxDataParser is an optional data parser that can be used to parse
202
        // auxiliary data for certain custom channel types.
203
        AuxDataParser fn.Option[AuxDataParser]
204

205
        // AuxChanCloser is an optional channel closer that can be used to
206
        // modify the way a coop-close transaction is constructed.
207
        AuxChanCloser fn.Option[chancloser.AuxChanCloser]
208

209
        // AuxSweeper is an optional interface that can be used to modify the
210
        // way sweep transaction are generated.
211
        AuxSweeper fn.Option[sweep.AuxSweeper]
212

213
        // AuxContractResolver is an optional interface that can be used to
214
        // modify the way contracts are resolved.
215
        AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
216

217
        // AuxChannelNegotiator is an optional interface that allows aux channel
218
        // implementations to inject and process custom records over channel
219
        // related wire messages.
220
        AuxChannelNegotiator fn.Option[lnwallet.AuxChannelNegotiator]
221
}
222

223
// DefaultWalletImpl is the default implementation of our normal, btcwallet
224
// backed configuration.
225
type DefaultWalletImpl struct {
226
        cfg         *Config
227
        logger      btclog.Logger
228
        interceptor signal.Interceptor
229

230
        watchOnly        bool
231
        migrateWatchOnly bool
232
        pwService        *walletunlocker.UnlockerService
233
}
234

235
// NewDefaultWalletImpl creates a new default wallet implementation.
236
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
237
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
3✔
238

3✔
239
        return &DefaultWalletImpl{
3✔
240
                cfg:         cfg,
3✔
241
                logger:      logger,
3✔
242
                interceptor: interceptor,
3✔
243
                watchOnly:   watchOnly,
3✔
244
                pwService:   createWalletUnlockerService(cfg),
3✔
245
        }
3✔
246
}
3✔
247

248
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
249
// instance. External subservers implementing this method can then register
250
// their own REST proxy stubs to the main server instance.
251
//
252
// NOTE: This is part of the GrpcRegistrar interface.
253
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
254
        mux *proxy.ServeMux, restProxyDest string,
255
        restDialOpts []grpc.DialOption) error {
3✔
256

3✔
257
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
3✔
258
                ctx, mux, restProxyDest, restDialOpts,
3✔
259
        )
3✔
260
}
3✔
261

262
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
263
// grpc.Server instance. External subservers implementing this method can then
264
// register their own gRPC server structs to the main server instance.
265
//
266
// NOTE: This is part of the GrpcRegistrar interface.
267
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
3✔
268
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
3✔
269

3✔
270
        return nil
3✔
271
}
3✔
272

273
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
274
// checks its signature, makes sure all specified permissions for the called
275
// method are contained within and finally ensures all caveat conditions are
276
// met. A non-nil error is returned if any of the checks fail.
277
//
278
// NOTE: This is part of the ExternalValidator interface.
279
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
280
        requiredPermissions []bakery.Op, fullMethod string) error {
×
281

×
282
        // Because the default implementation does not return any permissions,
×
283
        // we shouldn't be registered as an external validator at all and this
×
284
        // should never be invoked.
×
285
        return fmt.Errorf("default implementation does not support external " +
×
286
                "macaroon validation")
×
287
}
×
288

289
// Permissions returns the permissions that the external validator is
290
// validating. It is a map between the full HTTP URI of each RPC and its
291
// required macaroon permissions. If multiple action/entity tuples are specified
292
// per URI, they are all required. See rpcserver.go for a list of valid action
293
// and entity values.
294
//
295
// NOTE: This is part of the ExternalValidator interface.
296
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
3✔
297
        return nil
3✔
298
}
3✔
299

300
// BuildWalletConfig is responsible for creating or unlocking and then
301
// fully initializing a wallet.
302
//
303
// NOTE: This is part of the WalletConfigBuilder interface.
304
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
305
        dbs *DatabaseInstances, aux *AuxComponents,
306
        interceptorChain *rpcperms.InterceptorChain,
307
        grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
308
        *btcwallet.Config, func(), error) {
3✔
309

3✔
310
        // Keep track of our various cleanup functions. We use a defer function
3✔
311
        // as well to not repeat ourselves with every return statement.
3✔
312
        var (
3✔
313
                cleanUpTasks []func()
3✔
314
                earlyExit    = true
3✔
315
                cleanUp      = func() {
6✔
316
                        for _, fn := range cleanUpTasks {
6✔
317
                                if fn == nil {
3✔
318
                                        continue
×
319
                                }
320

321
                                fn()
3✔
322
                        }
323
                }
324
        )
325
        defer func() {
6✔
326
                if earlyExit {
3✔
327
                        cleanUp()
×
328
                }
×
329
        }()
330

331
        // Initialize a new block cache.
332
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
3✔
333

3✔
334
        // Before starting the wallet, we'll create and start our Neutrino
3✔
335
        // light client instance, if enabled, in order to allow it to sync
3✔
336
        // while the rest of the daemon continues startup.
3✔
337
        mainChain := d.cfg.Bitcoin
3✔
338
        var neutrinoCS *neutrino.ChainService
3✔
339
        if mainChain.Node == "neutrino" {
4✔
340
                neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend(
1✔
341
                        ctx, d.cfg, mainChain.ChainDir, blockCache,
1✔
342
                )
1✔
343
                if err != nil {
1✔
344
                        err := fmt.Errorf("unable to initialize neutrino "+
×
345
                                "backend: %v", err)
×
346
                        d.logger.Error(err)
×
347
                        return nil, nil, nil, err
×
348
                }
×
349
                cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp)
1✔
350
                neutrinoCS = neutrinoBackend
1✔
351
        }
352

353
        var (
3✔
354
                walletInitParams = walletunlocker.WalletUnlockParams{
3✔
355
                        // In case we do auto-unlock, we need to be able to send
3✔
356
                        // into the channel without blocking so we buffer it.
3✔
357
                        MacResponseChan: make(chan []byte, 1),
3✔
358
                }
3✔
359
                privateWalletPw = lnwallet.DefaultPrivatePassphrase
3✔
360
                publicWalletPw  = lnwallet.DefaultPublicPassphrase
3✔
361
        )
3✔
362

3✔
363
        // If the user didn't request a seed, then we'll manually assume a
3✔
364
        // wallet birthday of now, as otherwise the seed would've specified
3✔
365
        // this information.
3✔
366
        walletInitParams.Birthday = time.Now()
3✔
367

3✔
368
        d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
3✔
369
        d.pwService.SetMacaroonDB(dbs.MacaroonDB)
3✔
370
        walletExists, err := d.pwService.WalletExists()
3✔
371
        if err != nil {
3✔
372
                return nil, nil, nil, err
×
373
        }
×
374

375
        if !walletExists {
6✔
376
                interceptorChain.SetWalletNotCreated()
3✔
377
        } else {
6✔
378
                interceptorChain.SetWalletLocked()
3✔
379
        }
3✔
380

381
        // If we've started in auto unlock mode, then a wallet should already
382
        // exist because we don't want to enable the RPC unlocker in that case
383
        // for security reasons (an attacker could inject their seed since the
384
        // RPC is unauthenticated). Only if the user explicitly wants to allow
385
        // wallet creation we don't error out here.
386
        if d.cfg.WalletUnlockPasswordFile != "" && !walletExists &&
3✔
387
                !d.cfg.WalletUnlockAllowCreate {
3✔
388

×
389
                return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
×
390
                        "was specified but wallet does not exist; initialize " +
×
391
                        "the wallet before using auto unlocking")
×
392
        }
×
393

394
        // What wallet mode are we running in? We've already made sure the no
395
        // seed backup and auto unlock aren't both set during config parsing.
396
        switch {
3✔
397
        // No seed backup means we're also using the default password.
398
        case d.cfg.NoSeedBackup:
3✔
399
                // We continue normally, the default password has already been
400
                // set above.
401

402
        // A password for unlocking is provided in a file.
403
        case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
×
404
                d.logger.Infof("Attempting automatic wallet unlock with " +
×
405
                        "password provided in file")
×
406
                pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
×
407
                if err != nil {
×
408
                        return nil, nil, nil, fmt.Errorf("error reading "+
×
409
                                "password from file %s: %v",
×
410
                                d.cfg.WalletUnlockPasswordFile, err)
×
411
                }
×
412

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

×
418
                // We have the password now, we can ask the unlocker service to
×
419
                // do the unlock for us.
×
420
                unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
×
421
                        pwBytes, 0,
×
422
                )
×
423
                if err != nil {
×
424
                        return nil, nil, nil, fmt.Errorf("error unlocking "+
×
425
                                "wallet with password from file: %v", err)
×
426
                }
×
427

428
                cleanUpTasks = append(cleanUpTasks, func() {
×
429
                        if err := unloadWalletFn(); err != nil {
×
430
                                d.logger.Errorf("Could not unload wallet: %v",
×
431
                                        err)
×
432
                        }
×
433
                })
434

435
                privateWalletPw = pwBytes
×
436
                publicWalletPw = pwBytes
×
437
                walletInitParams.Wallet = unlockedWallet
×
438
                walletInitParams.UnloadWallet = unloadWalletFn
×
439

440
        // If none of the automatic startup options are selected, we fall back
441
        // to the default behavior of waiting for the wallet creation/unlocking
442
        // over RPC.
443
        default:
3✔
444
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
3✔
445
                        return nil, nil, nil, err
×
446
                }
×
447

448
                params, err := waitForWalletPassword(
3✔
449
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
3✔
450
                        d.interceptor.ShutdownChannel(),
3✔
451
                )
3✔
452
                if err != nil {
3✔
453
                        err := fmt.Errorf("unable to set up wallet password "+
×
454
                                "listeners: %v", err)
×
455
                        d.logger.Error(err)
×
456
                        return nil, nil, nil, err
×
457
                }
×
458

459
                walletInitParams = *params
3✔
460
                privateWalletPw = walletInitParams.Password
3✔
461
                publicWalletPw = walletInitParams.Password
3✔
462
                cleanUpTasks = append(cleanUpTasks, func() {
6✔
463
                        if err := walletInitParams.UnloadWallet(); err != nil {
3✔
464
                                d.logger.Errorf("Could not unload wallet: %v",
×
465
                                        err)
×
466
                        }
×
467
                })
468

469
                if walletInitParams.RecoveryWindow > 0 {
6✔
470
                        d.logger.Infof("Wallet recovery mode enabled with "+
3✔
471
                                "address lookahead of %d addresses",
3✔
472
                                walletInitParams.RecoveryWindow)
3✔
473
                }
3✔
474
        }
475

476
        var macaroonService *macaroons.Service
3✔
477
        if !d.cfg.NoMacaroons {
6✔
478
                // Create the macaroon authentication/authorization service.
3✔
479
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
3✔
480
                if err != nil {
3✔
481
                        return nil, nil, nil, err
×
482
                }
×
483
                macaroonService, err = macaroons.NewService(
3✔
484
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
3✔
485
                        macaroons.IPLockChecker, macaroons.IPRangeLockChecker,
3✔
486
                        macaroons.CustomChecker(interceptorChain),
3✔
487
                )
3✔
488
                if err != nil {
3✔
489
                        err := fmt.Errorf("unable to set up macaroon "+
×
490
                                "authentication: %v", err)
×
491
                        d.logger.Error(err)
×
492
                        return nil, nil, nil, err
×
493
                }
×
494
                cleanUpTasks = append(cleanUpTasks, func() {
6✔
495
                        if err := macaroonService.Close(); err != nil {
3✔
496
                                d.logger.Errorf("Could not close macaroon "+
×
497
                                        "service: %v", err)
×
498
                        }
×
499
                })
500

501
                // Try to unlock the macaroon store with the private password.
502
                // Ignore ErrAlreadyUnlocked since it could be unlocked by the
503
                // wallet unlocker.
504
                err = macaroonService.CreateUnlock(&privateWalletPw)
3✔
505
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
3✔
506
                        err := fmt.Errorf("unable to unlock macaroons: %w", err)
×
507
                        d.logger.Error(err)
×
508
                        return nil, nil, nil, err
×
509
                }
×
510

511
                // If we have a macaroon root key from the init wallet params,
512
                // set the root key before baking any macaroons.
513
                if len(walletInitParams.MacRootKey) > 0 {
3✔
514
                        err := macaroonService.SetRootKey(
×
515
                                walletInitParams.MacRootKey,
×
516
                        )
×
517
                        if err != nil {
×
518
                                return nil, nil, nil, err
×
519
                        }
×
520
                }
521

522
                // Send an admin macaroon to all our listeners that requested
523
                // one by setting a non-nil macaroon channel.
524
                adminMacBytes, err := bakeMacaroon(
3✔
525
                        ctx, macaroonService, adminPermissions(),
3✔
526
                )
3✔
527
                if err != nil {
3✔
528
                        return nil, nil, nil, err
×
529
                }
×
530
                for _, lis := range grpcListeners {
6✔
531
                        if lis.MacChan != nil {
3✔
532
                                lis.MacChan <- adminMacBytes
×
533
                        }
×
534
                }
535

536
                // In case we actually needed to unlock the wallet, we now need
537
                // to create an instance of the admin macaroon and send it to
538
                // the unlocker so it can forward it to the user. In no seed
539
                // backup mode, there's nobody listening on the channel and we'd
540
                // block here forever.
541
                if !d.cfg.NoSeedBackup {
6✔
542
                        // The channel is buffered by one element so writing
3✔
543
                        // should not block here.
3✔
544
                        walletInitParams.MacResponseChan <- adminMacBytes
3✔
545
                }
3✔
546

547
                // If the user requested a stateless initialization, no macaroon
548
                // files should be created.
549
                if !walletInitParams.StatelessInit {
6✔
550
                        // Create default macaroon files for lncli to use if
3✔
551
                        // they don't exist.
3✔
552
                        err = genDefaultMacaroons(
3✔
553
                                ctx, macaroonService, d.cfg.AdminMacPath,
3✔
554
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
3✔
555
                        )
3✔
556
                        if err != nil {
3✔
557
                                err := fmt.Errorf("unable to create macaroons "+
×
558
                                        "%v", err)
×
559
                                d.logger.Error(err)
×
560
                                return nil, nil, nil, err
×
561
                        }
×
562
                }
563

564
                // As a security service to the user, if they requested
565
                // stateless initialization and there are macaroon files on disk
566
                // we log a warning.
567
                if walletInitParams.StatelessInit {
6✔
568
                        msg := "Found %s macaroon on disk (%s) even though " +
3✔
569
                                "--stateless_init was requested. Unencrypted " +
3✔
570
                                "state is accessible by the host system. You " +
3✔
571
                                "should change the password and use " +
3✔
572
                                "--new_mac_root_key with --stateless_init to " +
3✔
573
                                "clean up and invalidate old macaroons."
3✔
574

3✔
575
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
3✔
576
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
577
                        }
×
578
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
3✔
579
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
580
                        }
×
581
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
3✔
582
                                d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
×
583
                        }
×
584
                }
585

586
                // We add the macaroon service to our RPC interceptor. This
587
                // will start checking macaroons against permissions on every
588
                // RPC invocation.
589
                interceptorChain.AddMacaroonService(macaroonService)
3✔
590
        }
591

592
        // Now that the wallet password has been provided, transition the RPC
593
        // state into Unlocked.
594
        interceptorChain.SetWalletUnlocked()
3✔
595

3✔
596
        // Since calls to the WalletUnlocker service wait for a response on the
3✔
597
        // macaroon channel, we close it here to make sure they return in case
3✔
598
        // we did not return the admin macaroon above. This will be the case if
3✔
599
        // --no-macaroons is used.
3✔
600
        close(walletInitParams.MacResponseChan)
3✔
601

3✔
602
        // We'll also close all the macaroon channels since lnd is done sending
3✔
603
        // macaroon data over it.
3✔
604
        for _, lis := range grpcListeners {
6✔
605
                if lis.MacChan != nil {
3✔
606
                        close(lis.MacChan)
×
607
                }
×
608
        }
609

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

644
        // Let's go ahead and create the partial chain control now that is only
645
        // dependent on our configuration and doesn't require any wallet
646
        // specific information.
647
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
3✔
648
                chainControlCfg,
3✔
649
        )
3✔
650
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
3✔
651
        if err != nil {
3✔
652
                err := fmt.Errorf("unable to create partial chain control: %w",
×
653
                        err)
×
654
                d.logger.Error(err)
×
655
                return nil, nil, nil, err
×
656
        }
×
657

658
        walletConfig := &btcwallet.Config{
3✔
659
                PrivatePass:      privateWalletPw,
3✔
660
                PublicPass:       publicWalletPw,
3✔
661
                Birthday:         walletInitParams.Birthday,
3✔
662
                RecoveryWindow:   walletInitParams.RecoveryWindow,
3✔
663
                NetParams:        d.cfg.ActiveNetParams.Params,
3✔
664
                CoinType:         d.cfg.ActiveNetParams.CoinType,
3✔
665
                Wallet:           walletInitParams.Wallet,
3✔
666
                LoaderOptions:    []btcwallet.LoaderOption{dbs.WalletDB},
3✔
667
                ChainSource:      partialChainControl.ChainSource,
3✔
668
                WatchOnly:        d.watchOnly,
3✔
669
                MigrateWatchOnly: d.migrateWatchOnly,
3✔
670
        }
3✔
671

3✔
672
        // Parse coin selection strategy.
3✔
673
        switch d.cfg.CoinSelectionStrategy {
3✔
674
        case "largest":
3✔
675
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
3✔
676

677
        case "random":
×
678
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
679

680
        default:
×
681
                return nil, nil, nil, fmt.Errorf("unknown coin selection "+
×
682
                        "strategy %v", d.cfg.CoinSelectionStrategy)
×
683
        }
684

685
        earlyExit = false
3✔
686
        return partialChainControl, walletConfig, cleanUp, nil
3✔
687
}
688

689
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
690
// rebroadcaster client.
691
func proxyBlockEpoch(
692
        notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
693
        error) {
2✔
694

2✔
695
        return func() (*blockntfns.Subscription, error) {
4✔
696
                blockEpoch, err := notifier.RegisterBlockEpochNtfn(
2✔
697
                        nil,
2✔
698
                )
2✔
699
                if err != nil {
2✔
700
                        return nil, err
×
701
                }
×
702

703
                sub := blockntfns.Subscription{
2✔
704
                        Notifications: make(chan blockntfns.BlockNtfn, 6),
2✔
705
                        Cancel:        blockEpoch.Cancel,
2✔
706
                }
2✔
707
                go func() {
4✔
708
                        for blk := range blockEpoch.Epochs {
4✔
709
                                ntfn := blockntfns.NewBlockConnected(
2✔
710
                                        *blk.BlockHeader,
2✔
711
                                        uint32(blk.Height),
2✔
712
                                )
2✔
713

2✔
714
                                sub.Notifications <- ntfn
2✔
715
                        }
2✔
716
                }()
717

718
                return &sub, nil
2✔
719
        }
720
}
721

722
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
723
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
724
type walletReBroadcaster struct {
725
        started atomic.Bool
726

727
        *pushtx.Broadcaster
728
}
729

730
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
731
func newWalletReBroadcaster(
732
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
2✔
733

2✔
734
        return &walletReBroadcaster{
2✔
735
                Broadcaster: broadcaster,
2✔
736
        }
2✔
737
}
2✔
738

739
// Start launches all goroutines the rebroadcaster needs to operate.
740
func (w *walletReBroadcaster) Start() error {
2✔
741
        defer w.started.Store(true)
2✔
742

2✔
743
        return w.Broadcaster.Start()
2✔
744
}
2✔
745

746
// Started returns true if the broadcaster is already active.
747
func (w *walletReBroadcaster) Started() bool {
2✔
748
        return w.started.Load()
2✔
749
}
2✔
750

751
// BuildChainControl is responsible for creating a fully populated chain
752
// control instance from a wallet.
753
//
754
// NOTE: This is part of the ChainControlBuilder interface.
755
func (d *DefaultWalletImpl) BuildChainControl(
756
        partialChainControl *chainreg.PartialChainControl,
757
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
758

3✔
759
        walletController, err := btcwallet.New(
3✔
760
                *walletConfig, partialChainControl.Cfg.BlockCache,
3✔
761
        )
3✔
762
        if err != nil {
3✔
763
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
764
                d.logger.Error(err)
×
765
                return nil, nil, err
×
766
        }
×
767

768
        keyRing := keychain.NewBtcWalletKeyRing(
3✔
769
                walletController.InternalWallet(), walletConfig.CoinType,
3✔
770
        )
3✔
771

3✔
772
        // Create, and start the lnwallet, which handles the core payment
3✔
773
        // channel logic, and exposes control via proxy state machines.
3✔
774
        lnWalletConfig := lnwallet.Config{
3✔
775
                Database:              partialChainControl.Cfg.ChanStateDB,
3✔
776
                Notifier:              partialChainControl.ChainNotifier,
3✔
777
                WalletController:      walletController,
3✔
778
                Signer:                walletController,
3✔
779
                FeeEstimator:          partialChainControl.FeeEstimator,
3✔
780
                SecretKeyRing:         keyRing,
3✔
781
                ChainIO:               walletController,
3✔
782
                NetParams:             *walletConfig.NetParams,
3✔
783
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
3✔
784
                AuxLeafStore:          partialChainControl.Cfg.AuxLeafStore,
3✔
785
                AuxSigner:             partialChainControl.Cfg.AuxSigner,
3✔
786
        }
3✔
787

3✔
788
        // The broadcast is already always active for neutrino nodes, so we
3✔
789
        // don't want to create a rebroadcast loop.
3✔
790
        if partialChainControl.Cfg.NeutrinoCS == nil {
5✔
791
                cs := partialChainControl.ChainSource
2✔
792
                broadcastCfg := pushtx.Config{
2✔
793
                        Broadcast: func(tx *wire.MsgTx) error {
4✔
794
                                _, err := cs.SendRawTransaction(
2✔
795
                                        tx, true,
2✔
796
                                )
2✔
797

2✔
798
                                return err
2✔
799
                        },
2✔
800
                        SubscribeBlocks: proxyBlockEpoch(
801
                                partialChainControl.ChainNotifier,
802
                        ),
803
                        RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
804
                        // In case the backend is different from neutrino we
805
                        // make sure that broadcast backend errors are mapped
806
                        // to the neutrino broadcastErr.
807
                        MapCustomBroadcastError: func(err error) error {
2✔
808
                                rpcErr := cs.MapRPCErr(err)
2✔
809
                                return broadcastErrorMapper(rpcErr)
2✔
810
                        },
2✔
811
                }
812

813
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
2✔
814
                        pushtx.NewBroadcaster(&broadcastCfg),
2✔
815
                )
2✔
816
        }
817

818
        // We've created the wallet configuration now, so we can finish
819
        // initializing the main chain control.
820
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
3✔
821
                lnWalletConfig, walletController, partialChainControl,
3✔
822
        )
3✔
823
        if err != nil {
3✔
824
                err := fmt.Errorf("unable to create chain control: %w", err)
×
825
                d.logger.Error(err)
×
826
                return nil, nil, err
×
827
        }
×
828

829
        return activeChainControl, cleanUp, nil
3✔
830
}
831

832
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
833
// an RPC interface.
834
type RPCSignerWalletImpl struct {
835
        // DefaultWalletImpl is the embedded instance of the default
836
        // implementation that the remote signer uses as its watch-only wallet
837
        // for keeping track of addresses and UTXOs.
838
        *DefaultWalletImpl
839
}
840

841
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
842
// implementation.
843
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
844
        interceptor signal.Interceptor,
845
        migrateWatchOnly bool) *RPCSignerWalletImpl {
3✔
846

3✔
847
        return &RPCSignerWalletImpl{
3✔
848
                DefaultWalletImpl: &DefaultWalletImpl{
3✔
849
                        cfg:              cfg,
3✔
850
                        logger:           logger,
3✔
851
                        interceptor:      interceptor,
3✔
852
                        watchOnly:        true,
3✔
853
                        migrateWatchOnly: migrateWatchOnly,
3✔
854
                        pwService:        createWalletUnlockerService(cfg),
3✔
855
                },
3✔
856
        }
3✔
857
}
3✔
858

859
// BuildChainControl is responsible for creating or unlocking and then fully
860
// initializing a wallet and returning it as part of a fully populated chain
861
// control instance.
862
//
863
// NOTE: This is part of the ChainControlBuilder interface.
864
func (d *RPCSignerWalletImpl) BuildChainControl(
865
        partialChainControl *chainreg.PartialChainControl,
866
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
867

3✔
868
        walletController, err := btcwallet.New(
3✔
869
                *walletConfig, partialChainControl.Cfg.BlockCache,
3✔
870
        )
3✔
871
        if err != nil {
3✔
872
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
873
                d.logger.Error(err)
×
874
                return nil, nil, err
×
875
        }
×
876

877
        baseKeyRing := keychain.NewBtcWalletKeyRing(
3✔
878
                walletController.InternalWallet(), walletConfig.CoinType,
3✔
879
        )
3✔
880

3✔
881
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
3✔
882
                baseKeyRing, walletController,
3✔
883
                d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
3✔
884
        )
3✔
885
        if err != nil {
3✔
886
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
887
                        "%v", err)
×
888
                d.logger.Error(err)
×
889
                return nil, nil, err
×
890
        }
×
891

892
        // Create, and start the lnwallet, which handles the core payment
893
        // channel logic, and exposes control via proxy state machines.
894
        lnWalletConfig := lnwallet.Config{
3✔
895
                Database:              partialChainControl.Cfg.ChanStateDB,
3✔
896
                Notifier:              partialChainControl.ChainNotifier,
3✔
897
                WalletController:      rpcKeyRing,
3✔
898
                Signer:                rpcKeyRing,
3✔
899
                FeeEstimator:          partialChainControl.FeeEstimator,
3✔
900
                SecretKeyRing:         rpcKeyRing,
3✔
901
                ChainIO:               walletController,
3✔
902
                NetParams:             *walletConfig.NetParams,
3✔
903
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
3✔
904
        }
3✔
905

3✔
906
        // We've created the wallet configuration now, so we can finish
3✔
907
        // initializing the main chain control.
3✔
908
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
3✔
909
                lnWalletConfig, rpcKeyRing, partialChainControl,
3✔
910
        )
3✔
911
        if err != nil {
3✔
912
                err := fmt.Errorf("unable to create chain control: %w", err)
×
913
                d.logger.Error(err)
×
914
                return nil, nil, err
×
915
        }
×
916

917
        return activeChainControl, cleanUp, nil
3✔
918
}
919

920
// DatabaseInstances is a struct that holds all instances to the actual
921
// databases that are used in lnd.
922
type DatabaseInstances struct {
923
        // GraphDB is the database that stores the channel graph used for path
924
        // finding.
925
        GraphDB *graphdb.ChannelGraph
926

927
        // ChanStateDB is the database that stores all of our node's channel
928
        // state.
929
        ChanStateDB *channeldb.DB
930

931
        // HeightHintDB is the database that stores height hints for spends.
932
        HeightHintDB kvdb.Backend
933

934
        // InvoiceDB is the database that stores information about invoices.
935
        InvoiceDB invoices.InvoiceDB
936

937
        // PaymentsDB is the database that stores all payment related
938
        // information.
939
        PaymentsDB paymentsdb.DB
940

941
        // MacaroonDB is the database that stores macaroon root keys.
942
        MacaroonDB kvdb.Backend
943

944
        // DecayedLogDB is the database that stores p2p related encryption
945
        // information.
946
        DecayedLogDB kvdb.Backend
947

948
        // TowerClientDB is the database that stores the watchtower client's
949
        // configuration.
950
        TowerClientDB wtclient.DB
951

952
        // TowerServerDB is the database that stores the watchtower server's
953
        // configuration.
954
        TowerServerDB watchtower.DB
955

956
        // WalletDB is the configuration for loading the wallet database using
957
        // the btcwallet's loader.
958
        WalletDB btcwallet.LoaderOption
959

960
        // NativeSQLStore holds a reference to the native SQL store that can
961
        // be used for native SQL queries for tables that already support it.
962
        // This may be nil if the use-native-sql flag was not set.
963
        NativeSQLStore sqldb.DB
964
}
965

966
// DefaultDatabaseBuilder is a type that builds the default database backends
967
// for lnd, using the given configuration to decide what actual implementation
968
// to use.
969
type DefaultDatabaseBuilder struct {
970
        cfg    *Config
971
        logger btclog.Logger
972
}
973

974
// NewDefaultDatabaseBuilder returns a new instance of the default database
975
// builder.
976
func NewDefaultDatabaseBuilder(cfg *Config,
977
        logger btclog.Logger) *DefaultDatabaseBuilder {
3✔
978

3✔
979
        return &DefaultDatabaseBuilder{
3✔
980
                cfg:    cfg,
3✔
981
                logger: logger,
3✔
982
        }
3✔
983
}
3✔
984

985
// BuildDatabase extracts the current databases that we'll use for normal
986
// operation in the daemon. A function closure that closes all opened databases
987
// is also returned.
988
func (d *DefaultDatabaseBuilder) BuildDatabase(
989
        ctx context.Context) (*DatabaseInstances, func(), error) {
3✔
990

3✔
991
        d.logger.Infof("Opening the main database, this might take a few " +
3✔
992
                "minutes...")
3✔
993

3✔
994
        cfg := d.cfg
3✔
995
        if cfg.DB.Backend == lncfg.BoltBackend {
6✔
996
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
3✔
997
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
3✔
998
                        cfg.DB.Bolt.AutoCompact)
3✔
999
        }
3✔
1000

1001
        startOpenTime := time.Now()
3✔
1002

3✔
1003
        databaseBackends, err := cfg.DB.GetBackends(
3✔
1004
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
3✔
1005
                        cfg.Watchtower.TowerDir, BitcoinChainName,
3✔
1006
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1007
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
3✔
1008
        )
3✔
1009
        if err != nil {
3✔
1010
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
1011
                        "backends: %v", err)
×
1012
        }
×
1013

1014
        // With the full remote mode we made sure both the graph and channel
1015
        // state DB point to the same local or remote DB and the same namespace
1016
        // within that DB.
1017
        dbs := &DatabaseInstances{
3✔
1018
                HeightHintDB:   databaseBackends.HeightHintDB,
3✔
1019
                MacaroonDB:     databaseBackends.MacaroonDB,
3✔
1020
                DecayedLogDB:   databaseBackends.DecayedLogDB,
3✔
1021
                WalletDB:       databaseBackends.WalletDB,
3✔
1022
                NativeSQLStore: databaseBackends.NativeSQLStore,
3✔
1023
        }
3✔
1024
        cleanUp := func() {
6✔
1025
                // We can just close the returned close functions directly. Even
3✔
1026
                // if we decorate the channel DB with an additional struct, its
3✔
1027
                // close function still just points to the kvdb backend.
3✔
1028
                for name, closeFunc := range databaseBackends.CloseFuncs {
6✔
1029
                        if err := closeFunc(); err != nil {
3✔
1030
                                d.logger.Errorf("Error closing %s "+
×
1031
                                        "database: %v", name, err)
×
1032
                        }
×
1033
                }
1034
        }
1035
        if databaseBackends.Remote {
3✔
1036
                d.logger.Infof("Using remote %v database! Creating "+
×
1037
                        "graph and channel state DB instances", cfg.DB.Backend)
×
1038
        } else {
3✔
1039
                d.logger.Infof("Creating local graph and channel state DB " +
3✔
1040
                        "instances")
3✔
1041
        }
3✔
1042

1043
        graphDBOptions := []graphdb.StoreOptionModifier{
3✔
1044
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
3✔
1045
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
3✔
1046
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
3✔
1047
        }
3✔
1048

3✔
1049
        chanGraphOpts := []graphdb.ChanGraphOption{
3✔
1050
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
3✔
1051
        }
3✔
1052

3✔
1053
        // We want to pre-allocate the channel graph cache according to what we
3✔
1054
        // expect for mainnet to speed up memory allocation.
3✔
1055
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
3✔
1056
                chanGraphOpts = append(
×
1057
                        chanGraphOpts, graphdb.WithPreAllocCacheNumNodes(
×
1058
                                graphdb.DefaultPreAllocCacheNumNodes,
×
1059
                        ),
×
1060
                )
×
1061
        }
×
1062

1063
        dbOptions := []channeldb.OptionModifier{
3✔
1064
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
3✔
1065
                channeldb.OptionStoreFinalHtlcResolutions(
3✔
1066
                        cfg.StoreFinalHtlcResolutions,
3✔
1067
                ),
3✔
1068
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
3✔
1069
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
3✔
1070
                channeldb.OptionGcDecayedLog(cfg.DB.NoGcDecayedLog),
3✔
1071
                channeldb.OptionWithDecayedLogDB(dbs.DecayedLogDB),
3✔
1072
        }
3✔
1073

3✔
1074
        // Otherwise, we'll open two instances, one for the state we only need
3✔
1075
        // locally, and the other for things we want to ensure are replicated.
3✔
1076
        dbs.ChanStateDB, err = channeldb.CreateWithBackend(
3✔
1077
                databaseBackends.ChanStateDB, dbOptions...,
3✔
1078
        )
3✔
1079
        switch {
3✔
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
        // The graph store implementation we will use depends on whether
1096
        // native SQL is enabled or not.
1097
        var graphStore graphdb.V1Store
3✔
1098

3✔
1099
        // Instantiate a native SQL store if the flag is set.
3✔
1100
        if d.cfg.DB.UseNativeSQL {
3✔
1101
                migrations := sqldb.GetMigrations()
×
1102

×
1103
                queryCfg := &d.cfg.DB.Sqlite.QueryConfig
×
1104
                if d.cfg.DB.Backend == lncfg.PostgresBackend {
×
1105
                        queryCfg = &d.cfg.DB.Postgres.QueryConfig
×
1106
                }
×
1107

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

1127
                                // Set the invoice bucket tombstone to indicate
1128
                                // that the migration has been completed.
1129
                                d.logger.Debugf("Setting invoice bucket " +
×
1130
                                        "tombstone")
×
1131

×
1132
                                //nolint:ll
×
1133
                                return dbs.ChanStateDB.SetInvoiceBucketTombstone()
×
1134
                        }
1135

1136
                        graphMig := func(tx *sqlc.Queries) error {
×
1137
                                cfg := &graphdb.SQLStoreConfig{
×
1138
                                        //nolint:ll
×
1139
                                        ChainHash: *d.cfg.ActiveNetParams.GenesisHash,
×
1140
                                        QueryCfg:  queryCfg,
×
1141
                                }
×
1142
                                err := graphdb.MigrateGraphToSQL(
×
1143
                                        ctx, cfg, dbs.ChanStateDB.Backend, tx,
×
1144
                                )
×
1145
                                if err != nil {
×
1146
                                        return fmt.Errorf("failed to migrate "+
×
1147
                                                "graph to SQL: %w", err)
×
1148
                                }
×
1149

1150
                                return nil
×
1151
                        }
1152

1153
                        // Make sure we attach the custom migration function to
1154
                        // the correct migration version.
1155
                        for i := 0; i < len(migrations); i++ {
×
1156
                                version := migrations[i].Version
×
1157
                                switch version {
×
1158
                                case invoiceMigration:
×
1159
                                        migrations[i].MigrationFn = invoiceMig
×
1160

×
1161
                                        continue
×
1162
                                case graphMigration:
×
1163
                                        migrations[i].MigrationFn = graphMig
×
1164

×
1165
                                        continue
×
1166

1167
                                default:
×
1168
                                }
1169

1170
                                migFn, ok := d.getSQLMigration(
×
1171
                                        ctx, version, dbs.ChanStateDB.Backend,
×
1172
                                )
×
1173
                                if !ok {
×
1174
                                        continue
×
1175
                                }
1176

1177
                                migrations[i].MigrationFn = migFn
×
1178
                        }
1179
                }
1180

1181
                // We need to apply all migrations to the native SQL store
1182
                // before we can use it.
1183
                err = dbs.NativeSQLStore.ApplyAllMigrations(ctx, migrations)
×
1184
                if err != nil {
×
1185
                        cleanUp()
×
1186
                        err = fmt.Errorf("faild to run migrations for the "+
×
1187
                                "native SQL store: %w", err)
×
1188
                        d.logger.Error(err)
×
1189

×
1190
                        return nil, nil, err
×
1191
                }
×
1192

1193
                // With the DB ready and migrations applied, we can now create
1194
                // the base DB and transaction executor for the native SQL
1195
                // invoice store.
1196
                baseDB := dbs.NativeSQLStore.GetBaseDB()
×
1197
                invoiceExecutor := sqldb.NewTransactionExecutor(
×
1198
                        baseDB, func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1199
                                return baseDB.WithTx(tx)
×
1200
                        },
×
1201
                )
1202

1203
                sqlInvoiceDB := invoices.NewSQLStore(
×
1204
                        invoiceExecutor, clock.NewDefaultClock(),
×
1205
                )
×
1206

×
1207
                dbs.InvoiceDB = sqlInvoiceDB
×
1208

×
1209
                graphExecutor := sqldb.NewTransactionExecutor(
×
1210
                        baseDB, func(tx *sql.Tx) graphdb.SQLQueries {
×
1211
                                return baseDB.WithTx(tx)
×
1212
                        },
×
1213
                )
1214

1215
                graphStore, err = graphdb.NewSQLStore(
×
1216
                        &graphdb.SQLStoreConfig{
×
1217
                                ChainHash: *d.cfg.ActiveNetParams.GenesisHash,
×
1218
                                QueryCfg:  queryCfg,
×
1219
                        },
×
1220
                        graphExecutor, graphDBOptions...,
×
1221
                )
×
1222
                if err != nil {
×
1223
                        err = fmt.Errorf("unable to get graph store: %w", err)
×
1224
                        d.logger.Error(err)
×
1225

×
1226
                        return nil, nil, err
×
1227
                }
×
1228

1229
                // Create the payments DB.
1230
                //
1231
                // NOTE: Only available via the test_native_sql build tag.
NEW
1232
                sqlPaymentsDB, err := d.getPaymentsStore(
×
NEW
1233
                        baseDB, dbs.ChanStateDB.Backend,
×
NEW
1234
                        paymentsdb.WithKeepFailedPaymentAttempts(
×
NEW
1235
                                cfg.KeepFailedPaymentAttempts,
×
NEW
1236
                        ),
×
NEW
1237
                )
×
NEW
1238
                if err != nil {
×
NEW
1239
                        err = fmt.Errorf("unable to get payments store: %w",
×
NEW
1240
                                err)
×
NEW
1241

×
NEW
1242
                        return nil, nil, err
×
NEW
1243
                }
×
1244

NEW
1245
                dbs.PaymentsDB = sqlPaymentsDB
×
1246
        } else {
3✔
1247
                // Check if the invoice bucket tombstone is set. If it is, we
3✔
1248
                // need to return and ask the user switch back to using the
3✔
1249
                // native SQL store.
3✔
1250
                ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
3✔
1251
                if err != nil {
3✔
1252
                        err = fmt.Errorf("unable to check invoice bucket "+
×
1253
                                "tombstone: %w", err)
×
1254
                        d.logger.Error(err)
×
1255

×
1256
                        return nil, nil, err
×
1257
                }
×
1258
                if ripInvoices {
3✔
1259
                        err = fmt.Errorf("invoices bucket tombstoned, please " +
×
1260
                                "switch back to native SQL")
×
1261
                        d.logger.Error(err)
×
1262

×
1263
                        return nil, nil, err
×
1264
                }
×
1265

1266
                dbs.InvoiceDB = dbs.ChanStateDB
3✔
1267

3✔
1268
                graphStore, err = graphdb.NewKVStore(
3✔
1269
                        databaseBackends.GraphDB, graphDBOptions...,
3✔
1270
                )
3✔
1271
                if err != nil {
3✔
1272
                        return nil, nil, err
×
1273
                }
×
1274

1275
                // Create the payments DB.
1276
                kvPaymentsDB, err := paymentsdb.NewKVStore(
3✔
1277
                        dbs.ChanStateDB,
3✔
1278
                        paymentsdb.WithKeepFailedPaymentAttempts(
3✔
1279
                                cfg.KeepFailedPaymentAttempts,
3✔
1280
                        ),
3✔
1281
                )
3✔
1282
                if err != nil {
3✔
NEW
1283
                        cleanUp()
×
1284

×
NEW
1285
                        err = fmt.Errorf("unable to open payments DB: %w", err)
×
NEW
1286
                        d.logger.Error(err)
×
1287

×
NEW
1288
                        return nil, nil, err
×
NEW
1289
                }
×
1290

1291
                dbs.PaymentsDB = kvPaymentsDB
3✔
1292
        }
1293

1294
        dbs.GraphDB, err = graphdb.NewChannelGraph(graphStore, chanGraphOpts...)
3✔
1295
        if err != nil {
3✔
1296
                cleanUp()
×
1297

×
NEW
1298
                err = fmt.Errorf("unable to open channel graph DB: %w", err)
×
1299
                d.logger.Error(err)
×
1300

×
1301
                return nil, nil, err
×
1302
        }
×
1303

1304
        // Wrap the watchtower client DB and make sure we clean up.
1305
        if cfg.WtClient.Active {
6✔
1306
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
3✔
1307
                        databaseBackends.TowerClientDB,
3✔
1308
                )
3✔
1309
                if err != nil {
3✔
1310
                        cleanUp()
×
1311

×
1312
                        err = fmt.Errorf("unable to open %s database: %w",
×
1313
                                lncfg.NSTowerClientDB, err)
×
1314
                        d.logger.Error(err)
×
1315
                        return nil, nil, err
×
1316
                }
×
1317
        }
1318

1319
        // Wrap the watchtower server DB and make sure we clean up.
1320
        if cfg.Watchtower.Active {
6✔
1321
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
3✔
1322
                        databaseBackends.TowerServerDB,
3✔
1323
                )
3✔
1324
                if err != nil {
3✔
1325
                        cleanUp()
×
1326

×
1327
                        err = fmt.Errorf("unable to open %s database: %w",
×
1328
                                lncfg.NSTowerServerDB, err)
×
1329
                        d.logger.Error(err)
×
1330
                        return nil, nil, err
×
1331
                }
×
1332
        }
1333

1334
        openTime := time.Since(startOpenTime)
3✔
1335
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
3✔
1336

3✔
1337
        return dbs, cleanUp, nil
3✔
1338
}
1339

1340
// waitForWalletPassword blocks until a password is provided by the user to
1341
// this RPC server.
1342
func waitForWalletPassword(cfg *Config,
1343
        pwService *walletunlocker.UnlockerService,
1344
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1345
        *walletunlocker.WalletUnlockParams, error) {
3✔
1346

3✔
1347
        // Wait for user to provide the password.
3✔
1348
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
3✔
1349
                "create` to create a wallet, `lncli unlock` to unlock an " +
3✔
1350
                "existing wallet, or `lncli changepassword` to change the " +
3✔
1351
                "password of an existing wallet and unlock it.")
3✔
1352

3✔
1353
        // We currently don't distinguish between getting a password to be used
3✔
1354
        // for creation or unlocking, as a new wallet db will be created if
3✔
1355
        // none exists when creating the chain control.
3✔
1356
        select {
3✔
1357
        // The wallet is being created for the first time, we'll check to see
1358
        // if the user provided any entropy for seed creation. If so, then
1359
        // we'll create the wallet early to load the seed.
1360
        case initMsg := <-pwService.InitMsgs:
3✔
1361
                password := initMsg.Passphrase
3✔
1362
                cipherSeed := initMsg.WalletSeed
3✔
1363
                extendedKey := initMsg.WalletExtendedKey
3✔
1364
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
3✔
1365
                recoveryWindow := initMsg.RecoveryWindow
3✔
1366

3✔
1367
                // Before we proceed, we'll check the internal version of the
3✔
1368
                // seed. If it's greater than the current key derivation
3✔
1369
                // version, then we'll return an error as we don't understand
3✔
1370
                // this.
3✔
1371
                if cipherSeed != nil &&
3✔
1372
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
3✔
1373

×
1374
                        return nil, fmt.Errorf("invalid internal "+
×
1375
                                "seed version %v, current max version is %v",
×
1376
                                cipherSeed.InternalVersion,
×
1377
                                keychain.CurrentKeyDerivationVersion)
×
1378
                }
×
1379

1380
                loader, err := btcwallet.NewWalletLoader(
3✔
1381
                        cfg.ActiveNetParams.Params, recoveryWindow,
3✔
1382
                        loaderOpts...,
3✔
1383
                )
3✔
1384
                if err != nil {
3✔
1385
                        return nil, err
×
1386
                }
×
1387

1388
                // With the seed, we can now use the wallet loader to create
1389
                // the wallet, then pass it back to avoid unlocking it again.
1390
                var (
3✔
1391
                        birthday  time.Time
3✔
1392
                        newWallet *wallet.Wallet
3✔
1393
                )
3✔
1394
                switch {
3✔
1395
                // A normal cipher seed was given, use the birthday encoded in
1396
                // it and create the wallet from that.
1397
                case cipherSeed != nil:
3✔
1398
                        birthday = cipherSeed.BirthdayTime()
3✔
1399
                        newWallet, err = loader.CreateNewWallet(
3✔
1400
                                password, password, cipherSeed.Entropy[:],
3✔
1401
                                birthday,
3✔
1402
                        )
3✔
1403

1404
                // No seed was given, we're importing a wallet from its extended
1405
                // private key.
1406
                case extendedKey != nil:
3✔
1407
                        birthday = initMsg.ExtendedKeyBirthday
3✔
1408
                        newWallet, err = loader.CreateNewWalletExtendedKey(
3✔
1409
                                password, password, extendedKey, birthday,
3✔
1410
                        )
3✔
1411

1412
                // Neither seed nor extended private key was given, so maybe the
1413
                // third option was chosen, the watch-only initialization. In
1414
                // this case we need to import each of the xpubs individually.
1415
                case watchOnlyAccounts != nil:
3✔
1416
                        if !cfg.RemoteSigner.Enable {
3✔
1417
                                return nil, fmt.Errorf("cannot initialize " +
×
1418
                                        "watch only wallet with remote " +
×
1419
                                        "signer config disabled")
×
1420
                        }
×
1421

1422
                        birthday = initMsg.WatchOnlyBirthday
3✔
1423
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
3✔
1424
                                password, birthday,
3✔
1425
                        )
3✔
1426
                        if err != nil {
3✔
1427
                                break
×
1428
                        }
1429

1430
                        err = importWatchOnlyAccounts(newWallet, initMsg)
3✔
1431

1432
                default:
×
1433
                        // The unlocker service made sure either the cipher seed
×
1434
                        // or the extended key is set so, we shouldn't get here.
×
1435
                        // The default case is just here for readability and
×
1436
                        // completeness.
×
1437
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1438
                                "nor extended key was given")
×
1439
                }
1440
                if err != nil {
3✔
1441
                        // Don't leave the file open in case the new wallet
×
1442
                        // could not be created for whatever reason.
×
1443
                        if err := loader.UnloadWallet(); err != nil {
×
1444
                                ltndLog.Errorf("Could not unload new "+
×
1445
                                        "wallet: %v", err)
×
1446
                        }
×
1447
                        return nil, err
×
1448
                }
1449

1450
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1451
                if cfg.ResetWalletTransactions {
6✔
1452
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
3✔
1453
                                "flag for new wallet as it has no effect")
3✔
1454
                }
3✔
1455

1456
                return &walletunlocker.WalletUnlockParams{
3✔
1457
                        Password:        password,
3✔
1458
                        Birthday:        birthday,
3✔
1459
                        RecoveryWindow:  recoveryWindow,
3✔
1460
                        Wallet:          newWallet,
3✔
1461
                        ChansToRestore:  initMsg.ChanBackups,
3✔
1462
                        UnloadWallet:    loader.UnloadWallet,
3✔
1463
                        StatelessInit:   initMsg.StatelessInit,
3✔
1464
                        MacResponseChan: pwService.MacResponseChan,
3✔
1465
                        MacRootKey:      initMsg.MacRootKey,
3✔
1466
                }, nil
3✔
1467

1468
        // The wallet has already been created in the past, and is simply being
1469
        // unlocked. So we'll just return these passphrases.
1470
        case unlockMsg := <-pwService.UnlockMsgs:
3✔
1471
                // Resetting the transactions is something the user likely only
3✔
1472
                // wants to do once so we add a prominent warning to the log to
3✔
1473
                // remind the user to turn off the setting again after
3✔
1474
                // successful completion.
3✔
1475
                if cfg.ResetWalletTransactions {
6✔
1476
                        ltndLog.Warnf("Dropped all transaction history from " +
3✔
1477
                                "on-chain wallet. Remember to disable " +
3✔
1478
                                "reset-wallet-transactions flag for next " +
3✔
1479
                                "start of lnd")
3✔
1480
                }
3✔
1481

1482
                return &walletunlocker.WalletUnlockParams{
3✔
1483
                        Password:        unlockMsg.Passphrase,
3✔
1484
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
3✔
1485
                        Wallet:          unlockMsg.Wallet,
3✔
1486
                        ChansToRestore:  unlockMsg.ChanBackups,
3✔
1487
                        UnloadWallet:    unlockMsg.UnloadWallet,
3✔
1488
                        StatelessInit:   unlockMsg.StatelessInit,
3✔
1489
                        MacResponseChan: pwService.MacResponseChan,
3✔
1490
                }, nil
3✔
1491

1492
        // If we got a shutdown signal we just return with an error immediately
1493
        case <-shutdownChan:
×
1494
                return nil, fmt.Errorf("shutting down")
×
1495
        }
1496
}
1497

1498
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1499
// which we created as watch-only.
1500
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1501
        initMsg *walletunlocker.WalletInitMsg) error {
3✔
1502

3✔
1503
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
3✔
1504
        for scope := range initMsg.WatchOnlyAccounts {
6✔
1505
                scopes = append(scopes, scope)
3✔
1506
        }
3✔
1507

1508
        // We need to import the accounts in the correct order, otherwise the
1509
        // indices will be incorrect.
1510
        sort.Slice(scopes, func(i, j int) bool {
6✔
1511
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
3✔
1512
                        scopes[i].Index < scopes[j].Index
3✔
1513
        })
3✔
1514

1515
        for _, scope := range scopes {
6✔
1516
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
3✔
1517

3✔
1518
                // We want witness pubkey hash by default, except for BIP49
3✔
1519
                // where we want mixed and BIP86 where we want taproot address
3✔
1520
                // formats.
3✔
1521
                switch scope.Scope.Purpose {
3✔
1522
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1523
                        waddrmgr.KeyScopeBIP0086.Purpose:
3✔
1524

3✔
1525
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
3✔
1526
                }
1527

1528
                // We want a human-readable account name. But for the default
1529
                // on-chain wallet we actually need to call it "default" to make
1530
                // sure everything works correctly.
1531
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
3✔
1532
                if scope.Index == 0 {
6✔
1533
                        name = "default"
3✔
1534
                }
3✔
1535

1536
                _, err := wallet.ImportAccountWithScope(
3✔
1537
                        name, initMsg.WatchOnlyAccounts[scope],
3✔
1538
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
3✔
1539
                        addrSchema,
3✔
1540
                )
3✔
1541
                if err != nil {
3✔
1542
                        return fmt.Errorf("could not import account %v: %w",
×
1543
                                name, err)
×
1544
                }
×
1545
        }
1546

1547
        return nil
3✔
1548
}
1549

1550
// handleNeutrinoPostgresDBMigration handles the migration of the neutrino db
1551
// to postgres. Initially we kept the neutrino db in the bolt db when running
1552
// with kvdb postgres backend. Now now move it to postgres as well. However we
1553
// need to make a distinction whether the user migrated the neutrino db to
1554
// postgres via lndinit or not. Currently if the db is not migrated we start
1555
// with a fresh db in postgres.
1556
//
1557
// TODO(ziggie): Also migrate the db to postgres in case it is still not
1558
// migrated ?
1559
func handleNeutrinoPostgresDBMigration(dbName, dbPath string,
1560
        cfg *Config) error {
×
1561

×
1562
        if !lnrpc.FileExists(dbName) {
×
1563
                return nil
×
1564
        }
×
1565

1566
        // Open bolt db to check if it is tombstoned. If it is we assume that
1567
        // the neutrino db was successfully migrated to postgres. We open it
1568
        // in read-only mode to avoid long db open times.
1569
        boltDB, err := kvdb.Open(
×
1570
                kvdb.BoltBackendName, dbName, true,
×
1571
                cfg.DB.Bolt.DBTimeout, true,
×
1572
        )
×
1573
        if err != nil {
×
1574
                return fmt.Errorf("failed to open bolt db: %w", err)
×
1575
        }
×
1576
        defer boltDB.Close()
×
1577

×
1578
        isTombstoned := false
×
1579
        err = boltDB.View(func(tx kvdb.RTx) error {
×
1580
                _, err = channeldb.CheckMarkerPresent(
×
1581
                        tx, channeldb.TombstoneKey,
×
1582
                )
×
1583

×
1584
                return err
×
1585
        }, func() {})
×
1586
        if err == nil {
×
1587
                isTombstoned = true
×
1588
        }
×
1589

1590
        if isTombstoned {
×
1591
                ltndLog.Infof("Neutrino Bolt DB is tombstoned, assuming " +
×
1592
                        "database was successfully migrated to postgres")
×
1593

×
1594
                return nil
×
1595
        }
×
1596

1597
        // If the db is not tombstoned, we remove the files and start fresh with
1598
        // postgres. This is the case when a user was running lnd with the
1599
        // postgres backend from the beginning without migrating from bolt.
1600
        ltndLog.Infof("Neutrino Bolt DB found but NOT tombstoned, removing " +
×
1601
                "it and starting fresh with postgres")
×
1602

×
1603
        filesToRemove := []string{
×
1604
                filepath.Join(dbPath, "block_headers.bin"),
×
1605
                filepath.Join(dbPath, "reg_filter_headers.bin"),
×
1606
                dbName,
×
1607
        }
×
1608

×
1609
        for _, file := range filesToRemove {
×
1610
                if err := os.Remove(file); err != nil {
×
1611
                        ltndLog.Warnf("Could not remove %s: %v", file, err)
×
1612
                }
×
1613
        }
1614

1615
        return nil
×
1616
}
1617

1618
// initNeutrinoBackend inits a new instance of the neutrino light client
1619
// backend given a target chain directory to store the chain state.
1620
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1621
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
1622
        func(), error) {
1✔
1623

1✔
1624
        // Both channel validation flags are false by default but their meaning
1✔
1625
        // is the inverse of each other. Therefore both cannot be true. For
1✔
1626
        // every other case, the neutrino.validatechannels overwrites the
1✔
1627
        // routing.assumechanvalid value.
1✔
1628
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
1✔
1629
                return nil, nil, fmt.Errorf("can't set both " +
×
1630
                        "neutrino.validatechannels and routing." +
×
1631
                        "assumechanvalid to true at the same time")
×
1632
        }
×
1633
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
1✔
1634

1✔
1635
        // First we'll open the database file for neutrino, creating the
1✔
1636
        // database if needed. We append the normalized network name here to
1✔
1637
        // match the behavior of btcwallet.
1✔
1638
        dbPath := filepath.Join(
1✔
1639
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
1✔
1640
        )
1✔
1641

1✔
1642
        // Ensure that the neutrino db path exists.
1✔
1643
        if err := os.MkdirAll(dbPath, 0700); err != nil {
1✔
1644
                return nil, nil, err
×
1645
        }
×
1646

1647
        var (
1✔
1648
                db  walletdb.DB
1✔
1649
                err error
1✔
1650
        )
1✔
1651
        switch {
1✔
1652
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1653
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1654
                db, err = kvdb.Open(
×
1655
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1656
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1657
                )
×
1658

1659
        case cfg.DB.Backend == kvdb.PostgresBackendName:
×
1660
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
1661

×
1662
                // This code needs to be in place because we did not start
×
1663
                // the postgres backend for neutrino at the beginning. Now we
×
1664
                // are also moving it into the postgres backend so we can phase
×
1665
                // out the bolt backend.
×
1666
                err = handleNeutrinoPostgresDBMigration(dbName, dbPath, cfg)
×
1667
                if err != nil {
×
1668
                        return nil, nil, err
×
1669
                }
×
1670

1671
                postgresConfig := lncfg.GetPostgresConfigKVDB(cfg.DB.Postgres)
×
1672
                db, err = kvdb.Open(
×
1673
                        kvdb.PostgresBackendName, ctx, postgresConfig,
×
1674
                        lncfg.NSNeutrinoDB,
×
1675
                )
×
1676

1677
        default:
1✔
1678
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
1✔
1679
                db, err = walletdb.Create(
1✔
1680
                        kvdb.BoltBackendName, dbName, !cfg.SyncFreelist,
1✔
1681
                        cfg.DB.Bolt.DBTimeout, false,
1✔
1682
                )
1✔
1683
        }
1684
        if err != nil {
1✔
1685
                return nil, nil, fmt.Errorf("unable to create "+
×
1686
                        "neutrino database: %v", err)
×
1687
        }
×
1688

1689
        headerStateAssertion, err := parseHeaderStateAssertion(
1✔
1690
                cfg.NeutrinoMode.AssertFilterHeader,
1✔
1691
        )
1✔
1692
        if err != nil {
1✔
1693
                db.Close()
×
1694
                return nil, nil, err
×
1695
        }
×
1696

1697
        // With the database open, we can now create an instance of the
1698
        // neutrino light client. We pass in relevant configuration parameters
1699
        // required.
1700
        config := neutrino.Config{
1✔
1701
                DataDir:      dbPath,
1✔
1702
                Database:     db,
1✔
1703
                ChainParams:  *cfg.ActiveNetParams.Params,
1✔
1704
                AddPeers:     cfg.NeutrinoMode.AddPeers,
1✔
1705
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
1✔
1706
                Dialer: func(addr net.Addr) (net.Conn, error) {
2✔
1707
                        return cfg.net.Dial(
1✔
1708
                                addr.Network(), addr.String(),
1✔
1709
                                cfg.ConnectionTimeout,
1✔
1710
                        )
1✔
1711
                },
1✔
1712
                NameResolver: func(host string) ([]net.IP, error) {
1✔
1713
                        addrs, err := cfg.net.LookupHost(host)
1✔
1714
                        if err != nil {
1✔
1715
                                return nil, err
×
1716
                        }
×
1717

1718
                        ips := make([]net.IP, 0, len(addrs))
1✔
1719
                        for _, strIP := range addrs {
2✔
1720
                                ip := net.ParseIP(strIP)
1✔
1721
                                if ip == nil {
1✔
1722
                                        continue
×
1723
                                }
1724

1725
                                ips = append(ips, ip)
1✔
1726
                        }
1727

1728
                        return ips, nil
1✔
1729
                },
1730
                AssertFilterHeader: headerStateAssertion,
1731
                BlockCache:         blockCache.Cache,
1732
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1733
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1734
        }
1735

1736
        if cfg.NeutrinoMode.MaxPeers <= 0 {
1✔
1737
                return nil, nil, fmt.Errorf("a non-zero number must be set " +
×
1738
                        "for neutrino max peers")
×
1739
        }
×
1740
        neutrino.MaxPeers = cfg.NeutrinoMode.MaxPeers
1✔
1741
        neutrino.BanDuration = time.Hour * 48
1✔
1742
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
1✔
1743
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
1✔
1744

1✔
1745
        neutrinoCS, err := neutrino.NewChainService(config)
1✔
1746
        if err != nil {
1✔
1747
                db.Close()
×
1748
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1749
                        "client: %v", err)
×
1750
        }
×
1751

1752
        if err := neutrinoCS.Start(); err != nil {
1✔
1753
                db.Close()
×
1754
                return nil, nil, err
×
1755
        }
×
1756

1757
        cleanUp := func() {
2✔
1758
                if err := neutrinoCS.Stop(); err != nil {
1✔
1759
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1760
                                "%v", err)
×
1761
                }
×
1762
                db.Close()
1✔
1763
        }
1764

1765
        return neutrinoCS, cleanUp, nil
1✔
1766
}
1767

1768
// parseHeaderStateAssertion parses the user-specified neutrino header state
1769
// into a headerfs.FilterHeader.
1770
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
1✔
1771
        if len(state) == 0 {
2✔
1772
                return nil, nil
1✔
1773
        }
1✔
1774

1775
        split := strings.Split(state, ":")
×
1776
        if len(split) != 2 {
×
1777
                return nil, fmt.Errorf("header state assertion %v in "+
×
1778
                        "unexpected format, expected format height:hash", state)
×
1779
        }
×
1780

1781
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1782
        if err != nil {
×
1783
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1784
        }
×
1785

1786
        hash, err := chainhash.NewHashFromStr(split[1])
×
1787
        if err != nil {
×
1788
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1789
        }
×
1790

1791
        return &headerfs.FilterHeader{
×
1792
                Height:     uint32(height),
×
1793
                FilterHash: *hash,
×
1794
        }, nil
×
1795
}
1796

1797
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1798
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1799
// resides in the neutrino package to use all of its functionalities.
1800
func broadcastErrorMapper(err error) error {
2✔
1801
        var returnErr error
2✔
1802

2✔
1803
        // We only filter for specific backend errors which are relevant for the
2✔
1804
        // Rebroadcaster.
2✔
1805
        switch {
2✔
1806
        // This makes sure the tx is removed from the rebroadcaster once it is
1807
        // confirmed.
1808
        case errors.Is(err, chain.ErrTxAlreadyKnown),
1809
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
1✔
1810

1✔
1811
                returnErr = &pushtx.BroadcastError{
1✔
1812
                        Code:   pushtx.Confirmed,
1✔
1813
                        Reason: err.Error(),
1✔
1814
                }
1✔
1815

1816
        // Transactions which are still in mempool but might fall out because
1817
        // of low fees are rebroadcasted despite of their backend error.
1818
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1819
                returnErr = &pushtx.BroadcastError{
×
1820
                        Code:   pushtx.Mempool,
×
1821
                        Reason: err.Error(),
×
1822
                }
×
1823

1824
        // Transactions which are not accepted into mempool because of low fees
1825
        // in the first place are rebroadcasted despite of their backend error.
1826
        // Mempool conditions change over time so it makes sense to retry
1827
        // publishing the transaction. Moreover we log the detailed error so the
1828
        // user can intervene and increase the size of his mempool or increase
1829
        // his min relay fee configuration.
1830
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet),
1831
                errors.Is(err, chain.ErrMinRelayFeeNotMet):
×
1832

×
1833
                ltndLog.Warnf("Error while broadcasting transaction: %v", err)
×
1834

×
1835
                returnErr = &pushtx.BroadcastError{
×
1836
                        Code:   pushtx.Mempool,
×
1837
                        Reason: err.Error(),
×
1838
                }
×
1839
        }
1840

1841
        return returnErr
2✔
1842
}
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