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

lightningnetwork / lnd / 10182608679

31 Jul 2024 02:55PM UTC coverage: 58.459% (-0.2%) from 58.625%
10182608679

push

github

web-flow
Merge pull request #8845 from bitbandi/multiple-etcd-host

Allow multiple etcd host

124794 of 213473 relevant lines covered (58.46%)

27889.07 hits per line

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

59.37
/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"
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/invoices"
37
        "github.com/lightningnetwork/lnd/keychain"
38
        "github.com/lightningnetwork/lnd/kvdb"
39
        "github.com/lightningnetwork/lnd/lncfg"
40
        "github.com/lightningnetwork/lnd/lnrpc"
41
        "github.com/lightningnetwork/lnd/lnwallet"
42
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
43
        "github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
44
        "github.com/lightningnetwork/lnd/macaroons"
45
        "github.com/lightningnetwork/lnd/rpcperms"
46
        "github.com/lightningnetwork/lnd/signal"
47
        "github.com/lightningnetwork/lnd/sqldb"
48
        "github.com/lightningnetwork/lnd/walletunlocker"
49
        "github.com/lightningnetwork/lnd/watchtower"
50
        "github.com/lightningnetwork/lnd/watchtower/wtclient"
51
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
52
        "google.golang.org/grpc"
53
        "gopkg.in/macaroon-bakery.v2/bakery"
54
)
55

56
// GrpcRegistrar is an interface that must be satisfied by an external subserver
57
// that wants to be able to register its own gRPC server onto lnd's main
58
// grpc.Server instance.
59
type GrpcRegistrar interface {
60
        // RegisterGrpcSubserver is called for each net.Listener on which lnd
61
        // creates a grpc.Server instance. External subservers implementing this
62
        // method can then register their own gRPC server structs to the main
63
        // server instance.
64
        RegisterGrpcSubserver(*grpc.Server) error
65
}
66

67
// RestRegistrar is an interface that must be satisfied by an external subserver
68
// that wants to be able to register its own REST mux onto lnd's main
69
// proxy.ServeMux instance.
70
type RestRegistrar interface {
71
        // RegisterRestSubserver is called after lnd creates the main
72
        // proxy.ServeMux instance. External subservers implementing this method
73
        // can then register their own REST proxy stubs to the main server
74
        // instance.
75
        RegisterRestSubserver(context.Context, *proxy.ServeMux, string,
76
                []grpc.DialOption) error
77
}
78

79
// ExternalValidator is an interface that must be satisfied by an external
80
// macaroon validator.
81
type ExternalValidator interface {
82
        macaroons.MacaroonValidator
83

84
        // Permissions returns the permissions that the external validator is
85
        // validating. It is a map between the full HTTP URI of each RPC and its
86
        // required macaroon permissions. If multiple action/entity tuples are
87
        // specified per URI, they are all required. See rpcserver.go for a list
88
        // of valid action and entity values.
89
        Permissions() map[string][]bakery.Op
90
}
91

92
// DatabaseBuilder is an interface that must be satisfied by the implementation
93
// that provides lnd's main database backend instances.
94
type DatabaseBuilder interface {
95
        // BuildDatabase extracts the current databases that we'll use for
96
        // normal operation in the daemon. A function closure that closes all
97
        // opened databases is also returned.
98
        BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error)
99
}
100

101
// WalletConfigBuilder is an interface that must be satisfied by a custom wallet
102
// implementation.
103
type WalletConfigBuilder interface {
104
        // BuildWalletConfig is responsible for creating or unlocking and then
105
        // fully initializing a wallet.
106
        BuildWalletConfig(context.Context, *DatabaseInstances,
107
                *rpcperms.InterceptorChain,
108
                []*ListenerWithSignal) (*chainreg.PartialChainControl,
109
                *btcwallet.Config, func(), error)
110
}
111

112
// ChainControlBuilder is an interface that must be satisfied by a custom wallet
113
// implementation.
114
type ChainControlBuilder interface {
115
        // BuildChainControl is responsible for creating a fully populated chain
116
        // control instance from a wallet.
117
        BuildChainControl(*chainreg.PartialChainControl,
118
                *btcwallet.Config) (*chainreg.ChainControl, func(), error)
119
}
120

121
// ImplementationCfg is a struct that holds all configuration items for
122
// components that can be implemented outside lnd itself.
123
type ImplementationCfg struct {
124
        // GrpcRegistrar is a type that can register additional gRPC subservers
125
        // before the main gRPC server is started.
126
        GrpcRegistrar
127

128
        // RestRegistrar is a type that can register additional REST subservers
129
        // before the main REST proxy is started.
130
        RestRegistrar
131

132
        // ExternalValidator is a type that can provide external macaroon
133
        // validation.
134
        ExternalValidator
135

136
        // DatabaseBuilder is a type that can provide lnd's main database
137
        // backend instances.
138
        DatabaseBuilder
139

140
        // WalletConfigBuilder is a type that can provide a wallet configuration
141
        // with a fully loaded and unlocked wallet.
142
        WalletConfigBuilder
143

144
        // ChainControlBuilder is a type that can provide a custom wallet
145
        // implementation.
146
        ChainControlBuilder
147
}
148

149
// DefaultWalletImpl is the default implementation of our normal, btcwallet
150
// backed configuration.
151
type DefaultWalletImpl struct {
152
        cfg         *Config
153
        logger      btclog.Logger
154
        interceptor signal.Interceptor
155

156
        watchOnly        bool
157
        migrateWatchOnly bool
158
        pwService        *walletunlocker.UnlockerService
159
}
160

161
// NewDefaultWalletImpl creates a new default wallet implementation.
162
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
163
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
3✔
164

3✔
165
        return &DefaultWalletImpl{
3✔
166
                cfg:         cfg,
3✔
167
                logger:      logger,
3✔
168
                interceptor: interceptor,
3✔
169
                watchOnly:   watchOnly,
3✔
170
                pwService:   createWalletUnlockerService(cfg),
3✔
171
        }
3✔
172
}
3✔
173

174
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
175
// instance. External subservers implementing this method can then register
176
// their own REST proxy stubs to the main server instance.
177
//
178
// NOTE: This is part of the GrpcRegistrar interface.
179
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
180
        mux *proxy.ServeMux, restProxyDest string,
181
        restDialOpts []grpc.DialOption) error {
3✔
182

3✔
183
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
3✔
184
                ctx, mux, restProxyDest, restDialOpts,
3✔
185
        )
3✔
186
}
3✔
187

188
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
189
// grpc.Server instance. External subservers implementing this method can then
190
// register their own gRPC server structs to the main server instance.
191
//
192
// NOTE: This is part of the GrpcRegistrar interface.
193
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
3✔
194
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
3✔
195

3✔
196
        return nil
3✔
197
}
3✔
198

199
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
200
// checks its signature, makes sure all specified permissions for the called
201
// method are contained within and finally ensures all caveat conditions are
202
// met. A non-nil error is returned if any of the checks fail.
203
//
204
// NOTE: This is part of the ExternalValidator interface.
205
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
206
        requiredPermissions []bakery.Op, fullMethod string) error {
×
207

×
208
        // Because the default implementation does not return any permissions,
×
209
        // we shouldn't be registered as an external validator at all and this
×
210
        // should never be invoked.
×
211
        return fmt.Errorf("default implementation does not support external " +
×
212
                "macaroon validation")
×
213
}
×
214

215
// Permissions returns the permissions that the external validator is
216
// validating. It is a map between the full HTTP URI of each RPC and its
217
// required macaroon permissions. If multiple action/entity tuples are specified
218
// per URI, they are all required. See rpcserver.go for a list of valid action
219
// and entity values.
220
//
221
// NOTE: This is part of the ExternalValidator interface.
222
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
3✔
223
        return nil
3✔
224
}
3✔
225

226
// BuildWalletConfig is responsible for creating or unlocking and then
227
// fully initializing a wallet.
228
//
229
// NOTE: This is part of the WalletConfigBuilder interface.
230
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
231
        dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain,
232
        grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
233
        *btcwallet.Config, func(), error) {
3✔
234

3✔
235
        // Keep track of our various cleanup functions. We use a defer function
3✔
236
        // as well to not repeat ourselves with every return statement.
3✔
237
        var (
3✔
238
                cleanUpTasks []func()
3✔
239
                earlyExit    = true
3✔
240
                cleanUp      = func() {
6✔
241
                        for _, fn := range cleanUpTasks {
6✔
242
                                if fn == nil {
3✔
243
                                        continue
×
244
                                }
245

246
                                fn()
3✔
247
                        }
248
                }
249
        )
250
        defer func() {
6✔
251
                if earlyExit {
3✔
252
                        cleanUp()
×
253
                }
×
254
        }()
255

256
        // Initialize a new block cache.
257
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
3✔
258

3✔
259
        // Before starting the wallet, we'll create and start our Neutrino
3✔
260
        // light client instance, if enabled, in order to allow it to sync
3✔
261
        // while the rest of the daemon continues startup.
3✔
262
        mainChain := d.cfg.Bitcoin
3✔
263
        var neutrinoCS *neutrino.ChainService
3✔
264
        if mainChain.Node == "neutrino" {
3✔
265
                neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend(
×
266
                        ctx, d.cfg, mainChain.ChainDir, blockCache,
×
267
                )
×
268
                if err != nil {
×
269
                        err := fmt.Errorf("unable to initialize neutrino "+
×
270
                                "backend: %v", err)
×
271
                        d.logger.Error(err)
×
272
                        return nil, nil, nil, err
×
273
                }
×
274
                cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp)
×
275
                neutrinoCS = neutrinoBackend
×
276
        }
277

278
        var (
3✔
279
                walletInitParams = walletunlocker.WalletUnlockParams{
3✔
280
                        // In case we do auto-unlock, we need to be able to send
3✔
281
                        // into the channel without blocking so we buffer it.
3✔
282
                        MacResponseChan: make(chan []byte, 1),
3✔
283
                }
3✔
284
                privateWalletPw = lnwallet.DefaultPrivatePassphrase
3✔
285
                publicWalletPw  = lnwallet.DefaultPublicPassphrase
3✔
286
        )
3✔
287

3✔
288
        // If the user didn't request a seed, then we'll manually assume a
3✔
289
        // wallet birthday of now, as otherwise the seed would've specified
3✔
290
        // this information.
3✔
291
        walletInitParams.Birthday = time.Now()
3✔
292

3✔
293
        d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
3✔
294
        d.pwService.SetMacaroonDB(dbs.MacaroonDB)
3✔
295
        walletExists, err := d.pwService.WalletExists()
3✔
296
        if err != nil {
3✔
297
                return nil, nil, nil, err
×
298
        }
×
299

300
        if !walletExists {
6✔
301
                interceptorChain.SetWalletNotCreated()
3✔
302
        } else {
6✔
303
                interceptorChain.SetWalletLocked()
3✔
304
        }
3✔
305

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

×
314
                return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
×
315
                        "was specified but wallet does not exist; initialize " +
×
316
                        "the wallet before using auto unlocking")
×
317
        }
×
318

319
        // What wallet mode are we running in? We've already made sure the no
320
        // seed backup and auto unlock aren't both set during config parsing.
321
        switch {
3✔
322
        // No seed backup means we're also using the default password.
323
        case d.cfg.NoSeedBackup:
3✔
324
                // We continue normally, the default password has already been
325
                // set above.
326

327
        // A password for unlocking is provided in a file.
328
        case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
×
329
                d.logger.Infof("Attempting automatic wallet unlock with " +
×
330
                        "password provided in file")
×
331
                pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
×
332
                if err != nil {
×
333
                        return nil, nil, nil, fmt.Errorf("error reading "+
×
334
                                "password from file %s: %v",
×
335
                                d.cfg.WalletUnlockPasswordFile, err)
×
336
                }
×
337

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

×
343
                // We have the password now, we can ask the unlocker service to
×
344
                // do the unlock for us.
×
345
                unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
×
346
                        pwBytes, 0,
×
347
                )
×
348
                if err != nil {
×
349
                        return nil, nil, nil, fmt.Errorf("error unlocking "+
×
350
                                "wallet with password from file: %v", err)
×
351
                }
×
352

353
                cleanUpTasks = append(cleanUpTasks, func() {
×
354
                        if err := unloadWalletFn(); err != nil {
×
355
                                d.logger.Errorf("Could not unload wallet: %v",
×
356
                                        err)
×
357
                        }
×
358
                })
359

360
                privateWalletPw = pwBytes
×
361
                publicWalletPw = pwBytes
×
362
                walletInitParams.Wallet = unlockedWallet
×
363
                walletInitParams.UnloadWallet = unloadWalletFn
×
364

365
        // If none of the automatic startup options are selected, we fall back
366
        // to the default behavior of waiting for the wallet creation/unlocking
367
        // over RPC.
368
        default:
3✔
369
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
3✔
370
                        return nil, nil, nil, err
×
371
                }
×
372

373
                params, err := waitForWalletPassword(
3✔
374
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
3✔
375
                        d.interceptor.ShutdownChannel(),
3✔
376
                )
3✔
377
                if err != nil {
3✔
378
                        err := fmt.Errorf("unable to set up wallet password "+
×
379
                                "listeners: %v", err)
×
380
                        d.logger.Error(err)
×
381
                        return nil, nil, nil, err
×
382
                }
×
383

384
                walletInitParams = *params
3✔
385
                privateWalletPw = walletInitParams.Password
3✔
386
                publicWalletPw = walletInitParams.Password
3✔
387
                cleanUpTasks = append(cleanUpTasks, func() {
6✔
388
                        if err := walletInitParams.UnloadWallet(); err != nil {
3✔
389
                                d.logger.Errorf("Could not unload wallet: %v",
×
390
                                        err)
×
391
                        }
×
392
                })
393

394
                if walletInitParams.RecoveryWindow > 0 {
6✔
395
                        d.logger.Infof("Wallet recovery mode enabled with "+
3✔
396
                                "address lookahead of %d addresses",
3✔
397
                                walletInitParams.RecoveryWindow)
3✔
398
                }
3✔
399
        }
400

401
        var macaroonService *macaroons.Service
3✔
402
        if !d.cfg.NoMacaroons {
6✔
403
                // Create the macaroon authentication/authorization service.
3✔
404
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
3✔
405
                if err != nil {
3✔
406
                        return nil, nil, nil, err
×
407
                }
×
408
                macaroonService, err = macaroons.NewService(
3✔
409
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
3✔
410
                        macaroons.IPLockChecker,
3✔
411
                        macaroons.CustomChecker(interceptorChain),
3✔
412
                )
3✔
413
                if err != nil {
3✔
414
                        err := fmt.Errorf("unable to set up macaroon "+
×
415
                                "authentication: %v", err)
×
416
                        d.logger.Error(err)
×
417
                        return nil, nil, nil, err
×
418
                }
×
419
                cleanUpTasks = append(cleanUpTasks, func() {
6✔
420
                        if err := macaroonService.Close(); err != nil {
3✔
421
                                d.logger.Errorf("Could not close macaroon "+
×
422
                                        "service: %v", err)
×
423
                        }
×
424
                })
425

426
                // Try to unlock the macaroon store with the private password.
427
                // Ignore ErrAlreadyUnlocked since it could be unlocked by the
428
                // wallet unlocker.
429
                err = macaroonService.CreateUnlock(&privateWalletPw)
3✔
430
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
3✔
431
                        err := fmt.Errorf("unable to unlock macaroons: %w", err)
×
432
                        d.logger.Error(err)
×
433
                        return nil, nil, nil, err
×
434
                }
×
435

436
                // If we have a macaroon root key from the init wallet params,
437
                // set the root key before baking any macaroons.
438
                if len(walletInitParams.MacRootKey) > 0 {
3✔
439
                        err := macaroonService.SetRootKey(
×
440
                                walletInitParams.MacRootKey,
×
441
                        )
×
442
                        if err != nil {
×
443
                                return nil, nil, nil, err
×
444
                        }
×
445
                }
446

447
                // Send an admin macaroon to all our listeners that requested
448
                // one by setting a non-nil macaroon channel.
449
                adminMacBytes, err := bakeMacaroon(
3✔
450
                        ctx, macaroonService, adminPermissions(),
3✔
451
                )
3✔
452
                if err != nil {
3✔
453
                        return nil, nil, nil, err
×
454
                }
×
455
                for _, lis := range grpcListeners {
6✔
456
                        if lis.MacChan != nil {
3✔
457
                                lis.MacChan <- adminMacBytes
×
458
                        }
×
459
                }
460

461
                // In case we actually needed to unlock the wallet, we now need
462
                // to create an instance of the admin macaroon and send it to
463
                // the unlocker so it can forward it to the user. In no seed
464
                // backup mode, there's nobody listening on the channel and we'd
465
                // block here forever.
466
                if !d.cfg.NoSeedBackup {
6✔
467
                        // The channel is buffered by one element so writing
3✔
468
                        // should not block here.
3✔
469
                        walletInitParams.MacResponseChan <- adminMacBytes
3✔
470
                }
3✔
471

472
                // If the user requested a stateless initialization, no macaroon
473
                // files should be created.
474
                if !walletInitParams.StatelessInit {
6✔
475
                        // Create default macaroon files for lncli to use if
3✔
476
                        // they don't exist.
3✔
477
                        err = genDefaultMacaroons(
3✔
478
                                ctx, macaroonService, d.cfg.AdminMacPath,
3✔
479
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
3✔
480
                        )
3✔
481
                        if err != nil {
3✔
482
                                err := fmt.Errorf("unable to create macaroons "+
×
483
                                        "%v", err)
×
484
                                d.logger.Error(err)
×
485
                                return nil, nil, nil, err
×
486
                        }
×
487
                }
488

489
                // As a security service to the user, if they requested
490
                // stateless initialization and there are macaroon files on disk
491
                // we log a warning.
492
                if walletInitParams.StatelessInit {
6✔
493
                        msg := "Found %s macaroon on disk (%s) even though " +
3✔
494
                                "--stateless_init was requested. Unencrypted " +
3✔
495
                                "state is accessible by the host system. You " +
3✔
496
                                "should change the password and use " +
3✔
497
                                "--new_mac_root_key with --stateless_init to " +
3✔
498
                                "clean up and invalidate old macaroons."
3✔
499

3✔
500
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
3✔
501
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
502
                        }
×
503
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
3✔
504
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
505
                        }
×
506
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
3✔
507
                                d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
×
508
                        }
×
509
                }
510

511
                // We add the macaroon service to our RPC interceptor. This
512
                // will start checking macaroons against permissions on every
513
                // RPC invocation.
514
                interceptorChain.AddMacaroonService(macaroonService)
3✔
515
        }
516

517
        // Now that the wallet password has been provided, transition the RPC
518
        // state into Unlocked.
519
        interceptorChain.SetWalletUnlocked()
3✔
520

3✔
521
        // Since calls to the WalletUnlocker service wait for a response on the
3✔
522
        // macaroon channel, we close it here to make sure they return in case
3✔
523
        // we did not return the admin macaroon above. This will be the case if
3✔
524
        // --no-macaroons is used.
3✔
525
        close(walletInitParams.MacResponseChan)
3✔
526

3✔
527
        // We'll also close all the macaroon channels since lnd is done sending
3✔
528
        // macaroon data over it.
3✔
529
        for _, lis := range grpcListeners {
6✔
530
                if lis.MacChan != nil {
3✔
531
                        close(lis.MacChan)
×
532
                }
×
533
        }
534

535
        // With the information parsed from the configuration, create valid
536
        // instances of the pertinent interfaces required to operate the
537
        // Lightning Network Daemon.
538
        //
539
        // When we create the chain control, we need storage for the height
540
        // hints and also the wallet itself, for these two we want them to be
541
        // replicated, so we'll pass in the remote channel DB instance.
542
        chainControlCfg := &chainreg.Config{
3✔
543
                Bitcoin:                     d.cfg.Bitcoin,
3✔
544
                HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable,
3✔
545
                NeutrinoMode:                d.cfg.NeutrinoMode,
3✔
546
                BitcoindMode:                d.cfg.BitcoindMode,
3✔
547
                BtcdMode:                    d.cfg.BtcdMode,
3✔
548
                HeightHintDB:                dbs.HeightHintDB,
3✔
549
                ChanStateDB:                 dbs.ChanStateDB.ChannelStateDB(),
3✔
550
                NeutrinoCS:                  neutrinoCS,
3✔
551
                ActiveNetParams:             d.cfg.ActiveNetParams,
3✔
552
                FeeURL:                      d.cfg.FeeURL,
3✔
553
                Fee: &lncfg.Fee{
3✔
554
                        URL:              d.cfg.Fee.URL,
3✔
555
                        MinUpdateTimeout: d.cfg.Fee.MinUpdateTimeout,
3✔
556
                        MaxUpdateTimeout: d.cfg.Fee.MaxUpdateTimeout,
3✔
557
                },
3✔
558
                Dialer: func(addr string) (net.Conn, error) {
3✔
559
                        return d.cfg.net.Dial(
×
560
                                "tcp", addr, d.cfg.ConnectionTimeout,
×
561
                        )
×
562
                },
×
563
                BlockCache:         blockCache,
564
                WalletUnlockParams: &walletInitParams,
565
        }
566

567
        // Let's go ahead and create the partial chain control now that is only
568
        // dependent on our configuration and doesn't require any wallet
569
        // specific information.
570
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
3✔
571
                chainControlCfg,
3✔
572
        )
3✔
573
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
3✔
574
        if err != nil {
3✔
575
                err := fmt.Errorf("unable to create partial chain control: %w",
×
576
                        err)
×
577
                d.logger.Error(err)
×
578
                return nil, nil, nil, err
×
579
        }
×
580

581
        walletConfig := &btcwallet.Config{
3✔
582
                PrivatePass:      privateWalletPw,
3✔
583
                PublicPass:       publicWalletPw,
3✔
584
                Birthday:         walletInitParams.Birthday,
3✔
585
                RecoveryWindow:   walletInitParams.RecoveryWindow,
3✔
586
                NetParams:        d.cfg.ActiveNetParams.Params,
3✔
587
                CoinType:         d.cfg.ActiveNetParams.CoinType,
3✔
588
                Wallet:           walletInitParams.Wallet,
3✔
589
                LoaderOptions:    []btcwallet.LoaderOption{dbs.WalletDB},
3✔
590
                ChainSource:      partialChainControl.ChainSource,
3✔
591
                WatchOnly:        d.watchOnly,
3✔
592
                MigrateWatchOnly: d.migrateWatchOnly,
3✔
593
        }
3✔
594

3✔
595
        // Parse coin selection strategy.
3✔
596
        switch d.cfg.CoinSelectionStrategy {
3✔
597
        case "largest":
3✔
598
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
3✔
599

600
        case "random":
×
601
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
602

603
        default:
×
604
                return nil, nil, nil, fmt.Errorf("unknown coin selection "+
×
605
                        "strategy %v", d.cfg.CoinSelectionStrategy)
×
606
        }
607

608
        earlyExit = false
3✔
609
        return partialChainControl, walletConfig, cleanUp, nil
3✔
610
}
611

612
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
613
// rebroadcaster client.
614
func proxyBlockEpoch(notifier chainntnfs.ChainNotifier,
615
) func() (*blockntfns.Subscription, error) {
3✔
616

3✔
617
        return func() (*blockntfns.Subscription, error) {
6✔
618
                blockEpoch, err := notifier.RegisterBlockEpochNtfn(
3✔
619
                        nil,
3✔
620
                )
3✔
621
                if err != nil {
3✔
622
                        return nil, err
×
623
                }
×
624

625
                sub := blockntfns.Subscription{
3✔
626
                        Notifications: make(chan blockntfns.BlockNtfn, 6),
3✔
627
                        Cancel:        blockEpoch.Cancel,
3✔
628
                }
3✔
629
                go func() {
6✔
630
                        for blk := range blockEpoch.Epochs {
6✔
631
                                ntfn := blockntfns.NewBlockConnected(
3✔
632
                                        *blk.BlockHeader,
3✔
633
                                        uint32(blk.Height),
3✔
634
                                )
3✔
635

3✔
636
                                sub.Notifications <- ntfn
3✔
637
                        }
3✔
638
                }()
639

640
                return &sub, nil
3✔
641
        }
642
}
643

644
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
645
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
646
type walletReBroadcaster struct {
647
        started atomic.Bool
648

649
        *pushtx.Broadcaster
650
}
651

652
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
653
func newWalletReBroadcaster(
654
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
3✔
655

3✔
656
        return &walletReBroadcaster{
3✔
657
                Broadcaster: broadcaster,
3✔
658
        }
3✔
659
}
3✔
660

661
// Start launches all goroutines the rebroadcaster needs to operate.
662
func (w *walletReBroadcaster) Start() error {
3✔
663
        defer w.started.Store(true)
3✔
664

3✔
665
        return w.Broadcaster.Start()
3✔
666
}
3✔
667

668
// Started returns true if the broadcaster is already active.
669
func (w *walletReBroadcaster) Started() bool {
3✔
670
        return w.started.Load()
3✔
671
}
3✔
672

673
// BuildChainControl is responsible for creating a fully populated chain
674
// control instance from a wallet.
675
//
676
// NOTE: This is part of the ChainControlBuilder interface.
677
func (d *DefaultWalletImpl) BuildChainControl(
678
        partialChainControl *chainreg.PartialChainControl,
679
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
680

3✔
681
        walletController, err := btcwallet.New(
3✔
682
                *walletConfig, partialChainControl.Cfg.BlockCache,
3✔
683
        )
3✔
684
        if err != nil {
3✔
685
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
686
                d.logger.Error(err)
×
687
                return nil, nil, err
×
688
        }
×
689

690
        keyRing := keychain.NewBtcWalletKeyRing(
3✔
691
                walletController.InternalWallet(), walletConfig.CoinType,
3✔
692
        )
3✔
693

3✔
694
        // Create, and start the lnwallet, which handles the core payment
3✔
695
        // channel logic, and exposes control via proxy state machines.
3✔
696
        lnWalletConfig := lnwallet.Config{
3✔
697
                Database:              partialChainControl.Cfg.ChanStateDB,
3✔
698
                Notifier:              partialChainControl.ChainNotifier,
3✔
699
                WalletController:      walletController,
3✔
700
                Signer:                walletController,
3✔
701
                FeeEstimator:          partialChainControl.FeeEstimator,
3✔
702
                SecretKeyRing:         keyRing,
3✔
703
                ChainIO:               walletController,
3✔
704
                NetParams:             *walletConfig.NetParams,
3✔
705
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
3✔
706
        }
3✔
707

3✔
708
        // The broadcast is already always active for neutrino nodes, so we
3✔
709
        // don't want to create a rebroadcast loop.
3✔
710
        if partialChainControl.Cfg.NeutrinoCS == nil {
6✔
711
                cs := partialChainControl.ChainSource
3✔
712
                broadcastCfg := pushtx.Config{
3✔
713
                        Broadcast: func(tx *wire.MsgTx) error {
6✔
714
                                _, err := cs.SendRawTransaction(
3✔
715
                                        tx, true,
3✔
716
                                )
3✔
717

3✔
718
                                return err
3✔
719
                        },
3✔
720
                        SubscribeBlocks: proxyBlockEpoch(
721
                                partialChainControl.ChainNotifier,
722
                        ),
723
                        RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
724
                        // In case the backend is different from neutrino we
725
                        // make sure that broadcast backend errors are mapped
726
                        // to the neutrino broadcastErr.
727
                        MapCustomBroadcastError: func(err error) error {
3✔
728
                                rpcErr := cs.MapRPCErr(err)
3✔
729
                                return broadcastErrorMapper(rpcErr)
3✔
730
                        },
3✔
731
                }
732

733
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
3✔
734
                        pushtx.NewBroadcaster(&broadcastCfg),
3✔
735
                )
3✔
736
        }
737

738
        // We've created the wallet configuration now, so we can finish
739
        // initializing the main chain control.
740
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
3✔
741
                lnWalletConfig, walletController, partialChainControl,
3✔
742
        )
3✔
743
        if err != nil {
3✔
744
                err := fmt.Errorf("unable to create chain control: %w", err)
×
745
                d.logger.Error(err)
×
746
                return nil, nil, err
×
747
        }
×
748

749
        return activeChainControl, cleanUp, nil
3✔
750
}
751

752
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
753
// an RPC interface.
754
type RPCSignerWalletImpl struct {
755
        // DefaultWalletImpl is the embedded instance of the default
756
        // implementation that the remote signer uses as its watch-only wallet
757
        // for keeping track of addresses and UTXOs.
758
        *DefaultWalletImpl
759
}
760

761
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
762
// implementation.
763
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
764
        interceptor signal.Interceptor,
765
        migrateWatchOnly bool) *RPCSignerWalletImpl {
3✔
766

3✔
767
        return &RPCSignerWalletImpl{
3✔
768
                DefaultWalletImpl: &DefaultWalletImpl{
3✔
769
                        cfg:              cfg,
3✔
770
                        logger:           logger,
3✔
771
                        interceptor:      interceptor,
3✔
772
                        watchOnly:        true,
3✔
773
                        migrateWatchOnly: migrateWatchOnly,
3✔
774
                        pwService:        createWalletUnlockerService(cfg),
3✔
775
                },
3✔
776
        }
3✔
777
}
3✔
778

779
// BuildChainControl is responsible for creating or unlocking and then fully
780
// initializing a wallet and returning it as part of a fully populated chain
781
// control instance.
782
//
783
// NOTE: This is part of the ChainControlBuilder interface.
784
func (d *RPCSignerWalletImpl) BuildChainControl(
785
        partialChainControl *chainreg.PartialChainControl,
786
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
787

3✔
788
        walletController, err := btcwallet.New(
3✔
789
                *walletConfig, partialChainControl.Cfg.BlockCache,
3✔
790
        )
3✔
791
        if err != nil {
3✔
792
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
793
                d.logger.Error(err)
×
794
                return nil, nil, err
×
795
        }
×
796

797
        baseKeyRing := keychain.NewBtcWalletKeyRing(
3✔
798
                walletController.InternalWallet(), walletConfig.CoinType,
3✔
799
        )
3✔
800

3✔
801
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
3✔
802
                baseKeyRing, walletController,
3✔
803
                d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
3✔
804
        )
3✔
805
        if err != nil {
3✔
806
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
807
                        "%v", err)
×
808
                d.logger.Error(err)
×
809
                return nil, nil, err
×
810
        }
×
811

812
        // Create, and start the lnwallet, which handles the core payment
813
        // channel logic, and exposes control via proxy state machines.
814
        lnWalletConfig := lnwallet.Config{
3✔
815
                Database:              partialChainControl.Cfg.ChanStateDB,
3✔
816
                Notifier:              partialChainControl.ChainNotifier,
3✔
817
                WalletController:      rpcKeyRing,
3✔
818
                Signer:                rpcKeyRing,
3✔
819
                FeeEstimator:          partialChainControl.FeeEstimator,
3✔
820
                SecretKeyRing:         rpcKeyRing,
3✔
821
                ChainIO:               walletController,
3✔
822
                NetParams:             *walletConfig.NetParams,
3✔
823
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
3✔
824
        }
3✔
825

3✔
826
        // We've created the wallet configuration now, so we can finish
3✔
827
        // initializing the main chain control.
3✔
828
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
3✔
829
                lnWalletConfig, rpcKeyRing, partialChainControl,
3✔
830
        )
3✔
831
        if err != nil {
3✔
832
                err := fmt.Errorf("unable to create chain control: %w", err)
×
833
                d.logger.Error(err)
×
834
                return nil, nil, err
×
835
        }
×
836

837
        return activeChainControl, cleanUp, nil
3✔
838
}
839

840
// DatabaseInstances is a struct that holds all instances to the actual
841
// databases that are used in lnd.
842
type DatabaseInstances struct {
843
        // GraphDB is the database that stores the channel graph used for path
844
        // finding.
845
        //
846
        // NOTE/TODO: This currently _needs_ to be the same instance as the
847
        // ChanStateDB below until the separation of the two databases is fully
848
        // complete!
849
        GraphDB *channeldb.DB
850

851
        // ChanStateDB is the database that stores all of our node's channel
852
        // state.
853
        //
854
        // NOTE/TODO: This currently _needs_ to be the same instance as the
855
        // GraphDB above until the separation of the two databases is fully
856
        // complete!
857
        ChanStateDB *channeldb.DB
858

859
        // HeightHintDB is the database that stores height hints for spends.
860
        HeightHintDB kvdb.Backend
861

862
        // InvoiceDB is the database that stores information about invoices.
863
        InvoiceDB invoices.InvoiceDB
864

865
        // MacaroonDB is the database that stores macaroon root keys.
866
        MacaroonDB kvdb.Backend
867

868
        // DecayedLogDB is the database that stores p2p related encryption
869
        // information.
870
        DecayedLogDB kvdb.Backend
871

872
        // TowerClientDB is the database that stores the watchtower client's
873
        // configuration.
874
        TowerClientDB wtclient.DB
875

876
        // TowerServerDB is the database that stores the watchtower server's
877
        // configuration.
878
        TowerServerDB watchtower.DB
879

880
        // WalletDB is the configuration for loading the wallet database using
881
        // the btcwallet's loader.
882
        WalletDB btcwallet.LoaderOption
883

884
        // NativeSQLStore is a pointer to a native SQL store that can be used
885
        // for native SQL queries for tables that already support it. This may
886
        // be nil if the use-native-sql flag was not set.
887
        NativeSQLStore *sqldb.BaseDB
888
}
889

890
// DefaultDatabaseBuilder is a type that builds the default database backends
891
// for lnd, using the given configuration to decide what actual implementation
892
// to use.
893
type DefaultDatabaseBuilder struct {
894
        cfg    *Config
895
        logger btclog.Logger
896
}
897

898
// NewDefaultDatabaseBuilder returns a new instance of the default database
899
// builder.
900
func NewDefaultDatabaseBuilder(cfg *Config,
901
        logger btclog.Logger) *DefaultDatabaseBuilder {
3✔
902

3✔
903
        return &DefaultDatabaseBuilder{
3✔
904
                cfg:    cfg,
3✔
905
                logger: logger,
3✔
906
        }
3✔
907
}
3✔
908

909
// BuildDatabase extracts the current databases that we'll use for normal
910
// operation in the daemon. A function closure that closes all opened databases
911
// is also returned.
912
func (d *DefaultDatabaseBuilder) BuildDatabase(
913
        ctx context.Context) (*DatabaseInstances, func(), error) {
3✔
914

3✔
915
        d.logger.Infof("Opening the main database, this might take a few " +
3✔
916
                "minutes...")
3✔
917

3✔
918
        cfg := d.cfg
3✔
919
        if cfg.DB.Backend == lncfg.BoltBackend {
6✔
920
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
3✔
921
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
3✔
922
                        cfg.DB.Bolt.AutoCompact)
3✔
923
        }
3✔
924

925
        startOpenTime := time.Now()
3✔
926

3✔
927
        databaseBackends, err := cfg.DB.GetBackends(
3✔
928
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
3✔
929
                        cfg.Watchtower.TowerDir, BitcoinChainName,
3✔
930
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
931
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
3✔
932
        )
3✔
933
        if err != nil {
3✔
934
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
935
                        "backends: %v", err)
×
936
        }
×
937

938
        // With the full remote mode we made sure both the graph and channel
939
        // state DB point to the same local or remote DB and the same namespace
940
        // within that DB.
941
        dbs := &DatabaseInstances{
3✔
942
                HeightHintDB:   databaseBackends.HeightHintDB,
3✔
943
                MacaroonDB:     databaseBackends.MacaroonDB,
3✔
944
                DecayedLogDB:   databaseBackends.DecayedLogDB,
3✔
945
                WalletDB:       databaseBackends.WalletDB,
3✔
946
                NativeSQLStore: databaseBackends.NativeSQLStore,
3✔
947
        }
3✔
948
        cleanUp := func() {
6✔
949
                // We can just close the returned close functions directly. Even
3✔
950
                // if we decorate the channel DB with an additional struct, its
3✔
951
                // close function still just points to the kvdb backend.
3✔
952
                for name, closeFunc := range databaseBackends.CloseFuncs {
6✔
953
                        if err := closeFunc(); err != nil {
3✔
954
                                d.logger.Errorf("Error closing %s "+
×
955
                                        "database: %v", name, err)
×
956
                        }
×
957
                }
958
        }
959
        if databaseBackends.Remote {
3✔
960
                d.logger.Infof("Using remote %v database! Creating "+
×
961
                        "graph and channel state DB instances", cfg.DB.Backend)
×
962
        } else {
3✔
963
                d.logger.Infof("Creating local graph and channel state DB " +
3✔
964
                        "instances")
3✔
965
        }
3✔
966

967
        dbOptions := []channeldb.OptionModifier{
3✔
968
                channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize),
3✔
969
                channeldb.OptionSetChannelCacheSize(
3✔
970
                        cfg.Caches.ChannelCacheSize,
3✔
971
                ),
3✔
972
                channeldb.OptionSetBatchCommitInterval(
3✔
973
                        cfg.DB.BatchCommitInterval,
3✔
974
                ),
3✔
975
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
3✔
976
                channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache),
3✔
977
                channeldb.OptionKeepFailedPaymentAttempts(
3✔
978
                        cfg.KeepFailedPaymentAttempts,
3✔
979
                ),
3✔
980
                channeldb.OptionStoreFinalHtlcResolutions(
3✔
981
                        cfg.StoreFinalHtlcResolutions,
3✔
982
                ),
3✔
983
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
3✔
984
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
3✔
985
        }
3✔
986

3✔
987
        // We want to pre-allocate the channel graph cache according to what we
3✔
988
        // expect for mainnet to speed up memory allocation.
3✔
989
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
3✔
990
                dbOptions = append(
×
991
                        dbOptions, channeldb.OptionSetPreAllocCacheNumNodes(
×
992
                                channeldb.DefaultPreAllocCacheNumNodes,
×
993
                        ),
×
994
                )
×
995
        }
×
996

997
        // Otherwise, we'll open two instances, one for the state we only need
998
        // locally, and the other for things we want to ensure are replicated.
999
        dbs.GraphDB, err = channeldb.CreateWithBackend(
3✔
1000
                databaseBackends.GraphDB, dbOptions...,
3✔
1001
        )
3✔
1002
        switch {
3✔
1003
        // Give the DB a chance to dry run the migration. Since we know that
1004
        // both the channel state and graph DBs are still always behind the same
1005
        // backend, we know this would be applied to both of those DBs.
1006
        case err == channeldb.ErrDryRunMigrationOK:
×
1007
                d.logger.Infof("Graph DB dry run migration successful")
×
1008
                return nil, nil, err
×
1009

1010
        case err != nil:
×
1011
                cleanUp()
×
1012

×
1013
                err := fmt.Errorf("unable to open graph DB: %w", err)
×
1014
                d.logger.Error(err)
×
1015
                return nil, nil, err
×
1016
        }
1017

1018
        // For now, we don't _actually_ split the graph and channel state DBs on
1019
        // the code level. Since they both are based upon the *channeldb.DB
1020
        // struct it will require more refactoring to fully separate them. With
1021
        // the full remote mode we at least know for now that they both point to
1022
        // the same DB backend (and also namespace within that) so we only need
1023
        // to apply any migration once.
1024
        //
1025
        // TODO(guggero): Once the full separation of anything graph related
1026
        // from the channeldb.DB is complete, the decorated instance of the
1027
        // channel state DB should be created here individually instead of just
1028
        // using the same struct (and DB backend) instance.
1029
        dbs.ChanStateDB = dbs.GraphDB
3✔
1030

3✔
1031
        // Instantiate a native SQL invoice store if the flag is set.
3✔
1032
        if d.cfg.DB.UseNativeSQL {
3✔
1033
                // KV invoice db resides in the same database as the graph and
×
1034
                // channel state DB. Let's query the database to see if we have
×
1035
                // any invoices there. If we do, we won't allow the user to
×
1036
                // start lnd with native SQL enabled, as we don't currently
×
1037
                // migrate the invoices to the new database schema.
×
1038
                invoiceSlice, err := dbs.GraphDB.QueryInvoices(
×
1039
                        ctx, invoices.InvoiceQuery{
×
1040
                                NumMaxInvoices: 1,
×
1041
                        },
×
1042
                )
×
1043
                if err != nil {
×
1044
                        cleanUp()
×
1045
                        d.logger.Errorf("Unable to query KV invoice DB: %v",
×
1046
                                err)
×
1047

×
1048
                        return nil, nil, err
×
1049
                }
×
1050

1051
                if len(invoiceSlice.Invoices) > 0 {
×
1052
                        cleanUp()
×
1053
                        err := fmt.Errorf("found invoices in the KV invoice " +
×
1054
                                "DB, migration to native SQL is not yet " +
×
1055
                                "supported")
×
1056
                        d.logger.Error(err)
×
1057

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

1061
                executor := sqldb.NewTransactionExecutor(
×
1062
                        dbs.NativeSQLStore,
×
1063
                        func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1064
                                return dbs.NativeSQLStore.WithTx(tx)
×
1065
                        },
×
1066
                )
1067

1068
                dbs.InvoiceDB = invoices.NewSQLStore(
×
1069
                        executor, clock.NewDefaultClock(),
×
1070
                )
×
1071
        } else {
3✔
1072
                dbs.InvoiceDB = dbs.GraphDB
3✔
1073
        }
3✔
1074

1075
        // Wrap the watchtower client DB and make sure we clean up.
1076
        if cfg.WtClient.Active {
6✔
1077
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
3✔
1078
                        databaseBackends.TowerClientDB,
3✔
1079
                )
3✔
1080
                if err != nil {
3✔
1081
                        cleanUp()
×
1082

×
1083
                        err := fmt.Errorf("unable to open %s database: %w",
×
1084
                                lncfg.NSTowerClientDB, err)
×
1085
                        d.logger.Error(err)
×
1086
                        return nil, nil, err
×
1087
                }
×
1088
        }
1089

1090
        // Wrap the watchtower server DB and make sure we clean up.
1091
        if cfg.Watchtower.Active {
6✔
1092
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
3✔
1093
                        databaseBackends.TowerServerDB,
3✔
1094
                )
3✔
1095
                if err != nil {
3✔
1096
                        cleanUp()
×
1097

×
1098
                        err := fmt.Errorf("unable to open %s database: %w",
×
1099
                                lncfg.NSTowerServerDB, err)
×
1100
                        d.logger.Error(err)
×
1101
                        return nil, nil, err
×
1102
                }
×
1103
        }
1104

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

3✔
1108
        return dbs, cleanUp, nil
3✔
1109
}
1110

1111
// waitForWalletPassword blocks until a password is provided by the user to
1112
// this RPC server.
1113
func waitForWalletPassword(cfg *Config,
1114
        pwService *walletunlocker.UnlockerService,
1115
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1116
        *walletunlocker.WalletUnlockParams, error) {
3✔
1117

3✔
1118
        // Wait for user to provide the password.
3✔
1119
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
3✔
1120
                "create` to create a wallet, `lncli unlock` to unlock an " +
3✔
1121
                "existing wallet, or `lncli changepassword` to change the " +
3✔
1122
                "password of an existing wallet and unlock it.")
3✔
1123

3✔
1124
        // We currently don't distinguish between getting a password to be used
3✔
1125
        // for creation or unlocking, as a new wallet db will be created if
3✔
1126
        // none exists when creating the chain control.
3✔
1127
        select {
3✔
1128
        // The wallet is being created for the first time, we'll check to see
1129
        // if the user provided any entropy for seed creation. If so, then
1130
        // we'll create the wallet early to load the seed.
1131
        case initMsg := <-pwService.InitMsgs:
3✔
1132
                password := initMsg.Passphrase
3✔
1133
                cipherSeed := initMsg.WalletSeed
3✔
1134
                extendedKey := initMsg.WalletExtendedKey
3✔
1135
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
3✔
1136
                recoveryWindow := initMsg.RecoveryWindow
3✔
1137

3✔
1138
                // Before we proceed, we'll check the internal version of the
3✔
1139
                // seed. If it's greater than the current key derivation
3✔
1140
                // version, then we'll return an error as we don't understand
3✔
1141
                // this.
3✔
1142
                if cipherSeed != nil &&
3✔
1143
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
3✔
1144

×
1145
                        return nil, fmt.Errorf("invalid internal "+
×
1146
                                "seed version %v, current max version is %v",
×
1147
                                cipherSeed.InternalVersion,
×
1148
                                keychain.CurrentKeyDerivationVersion)
×
1149
                }
×
1150

1151
                loader, err := btcwallet.NewWalletLoader(
3✔
1152
                        cfg.ActiveNetParams.Params, recoveryWindow,
3✔
1153
                        loaderOpts...,
3✔
1154
                )
3✔
1155
                if err != nil {
3✔
1156
                        return nil, err
×
1157
                }
×
1158

1159
                // With the seed, we can now use the wallet loader to create
1160
                // the wallet, then pass it back to avoid unlocking it again.
1161
                var (
3✔
1162
                        birthday  time.Time
3✔
1163
                        newWallet *wallet.Wallet
3✔
1164
                )
3✔
1165
                switch {
3✔
1166
                // A normal cipher seed was given, use the birthday encoded in
1167
                // it and create the wallet from that.
1168
                case cipherSeed != nil:
3✔
1169
                        birthday = cipherSeed.BirthdayTime()
3✔
1170
                        newWallet, err = loader.CreateNewWallet(
3✔
1171
                                password, password, cipherSeed.Entropy[:],
3✔
1172
                                birthday,
3✔
1173
                        )
3✔
1174

1175
                // No seed was given, we're importing a wallet from its extended
1176
                // private key.
1177
                case extendedKey != nil:
3✔
1178
                        birthday = initMsg.ExtendedKeyBirthday
3✔
1179
                        newWallet, err = loader.CreateNewWalletExtendedKey(
3✔
1180
                                password, password, extendedKey, birthday,
3✔
1181
                        )
3✔
1182

1183
                // Neither seed nor extended private key was given, so maybe the
1184
                // third option was chosen, the watch-only initialization. In
1185
                // this case we need to import each of the xpubs individually.
1186
                case watchOnlyAccounts != nil:
3✔
1187
                        if !cfg.RemoteSigner.Enable {
3✔
1188
                                return nil, fmt.Errorf("cannot initialize " +
×
1189
                                        "watch only wallet with remote " +
×
1190
                                        "signer config disabled")
×
1191
                        }
×
1192

1193
                        birthday = initMsg.WatchOnlyBirthday
3✔
1194
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
3✔
1195
                                password, birthday,
3✔
1196
                        )
3✔
1197
                        if err != nil {
3✔
1198
                                break
×
1199
                        }
1200

1201
                        err = importWatchOnlyAccounts(newWallet, initMsg)
3✔
1202

1203
                default:
×
1204
                        // The unlocker service made sure either the cipher seed
×
1205
                        // or the extended key is set so, we shouldn't get here.
×
1206
                        // The default case is just here for readability and
×
1207
                        // completeness.
×
1208
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1209
                                "nor extended key was given")
×
1210
                }
1211
                if err != nil {
3✔
1212
                        // Don't leave the file open in case the new wallet
×
1213
                        // could not be created for whatever reason.
×
1214
                        if err := loader.UnloadWallet(); err != nil {
×
1215
                                ltndLog.Errorf("Could not unload new "+
×
1216
                                        "wallet: %v", err)
×
1217
                        }
×
1218
                        return nil, err
×
1219
                }
1220

1221
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1222
                if cfg.ResetWalletTransactions {
6✔
1223
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
3✔
1224
                                "flag for new wallet as it has no effect")
3✔
1225
                }
3✔
1226

1227
                return &walletunlocker.WalletUnlockParams{
3✔
1228
                        Password:        password,
3✔
1229
                        Birthday:        birthday,
3✔
1230
                        RecoveryWindow:  recoveryWindow,
3✔
1231
                        Wallet:          newWallet,
3✔
1232
                        ChansToRestore:  initMsg.ChanBackups,
3✔
1233
                        UnloadWallet:    loader.UnloadWallet,
3✔
1234
                        StatelessInit:   initMsg.StatelessInit,
3✔
1235
                        MacResponseChan: pwService.MacResponseChan,
3✔
1236
                        MacRootKey:      initMsg.MacRootKey,
3✔
1237
                }, nil
3✔
1238

1239
        // The wallet has already been created in the past, and is simply being
1240
        // unlocked. So we'll just return these passphrases.
1241
        case unlockMsg := <-pwService.UnlockMsgs:
3✔
1242
                // Resetting the transactions is something the user likely only
3✔
1243
                // wants to do once so we add a prominent warning to the log to
3✔
1244
                // remind the user to turn off the setting again after
3✔
1245
                // successful completion.
3✔
1246
                if cfg.ResetWalletTransactions {
6✔
1247
                        ltndLog.Warnf("Dropped all transaction history from " +
3✔
1248
                                "on-chain wallet. Remember to disable " +
3✔
1249
                                "reset-wallet-transactions flag for next " +
3✔
1250
                                "start of lnd")
3✔
1251
                }
3✔
1252

1253
                return &walletunlocker.WalletUnlockParams{
3✔
1254
                        Password:        unlockMsg.Passphrase,
3✔
1255
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
3✔
1256
                        Wallet:          unlockMsg.Wallet,
3✔
1257
                        ChansToRestore:  unlockMsg.ChanBackups,
3✔
1258
                        UnloadWallet:    unlockMsg.UnloadWallet,
3✔
1259
                        StatelessInit:   unlockMsg.StatelessInit,
3✔
1260
                        MacResponseChan: pwService.MacResponseChan,
3✔
1261
                }, nil
3✔
1262

1263
        // If we got a shutdown signal we just return with an error immediately
1264
        case <-shutdownChan:
×
1265
                return nil, fmt.Errorf("shutting down")
×
1266
        }
1267
}
1268

1269
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1270
// which we created as watch-only.
1271
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1272
        initMsg *walletunlocker.WalletInitMsg) error {
3✔
1273

3✔
1274
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
3✔
1275
        for scope := range initMsg.WatchOnlyAccounts {
6✔
1276
                scopes = append(scopes, scope)
3✔
1277
        }
3✔
1278

1279
        // We need to import the accounts in the correct order, otherwise the
1280
        // indices will be incorrect.
1281
        sort.Slice(scopes, func(i, j int) bool {
6✔
1282
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
3✔
1283
                        scopes[i].Index < scopes[j].Index
3✔
1284
        })
3✔
1285

1286
        for _, scope := range scopes {
6✔
1287
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
3✔
1288

3✔
1289
                // We want witness pubkey hash by default, except for BIP49
3✔
1290
                // where we want mixed and BIP86 where we want taproot address
3✔
1291
                // formats.
3✔
1292
                switch scope.Scope.Purpose {
3✔
1293
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1294
                        waddrmgr.KeyScopeBIP0086.Purpose:
3✔
1295

3✔
1296
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
3✔
1297
                }
1298

1299
                // We want a human-readable account name. But for the default
1300
                // on-chain wallet we actually need to call it "default" to make
1301
                // sure everything works correctly.
1302
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
3✔
1303
                if scope.Index == 0 {
6✔
1304
                        name = "default"
3✔
1305
                }
3✔
1306

1307
                _, err := wallet.ImportAccountWithScope(
3✔
1308
                        name, initMsg.WatchOnlyAccounts[scope],
3✔
1309
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
3✔
1310
                        addrSchema,
3✔
1311
                )
3✔
1312
                if err != nil {
3✔
1313
                        return fmt.Errorf("could not import account %v: %w",
×
1314
                                name, err)
×
1315
                }
×
1316
        }
1317

1318
        return nil
3✔
1319
}
1320

1321
// initNeutrinoBackend inits a new instance of the neutrino light client
1322
// backend given a target chain directory to store the chain state.
1323
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1324
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
1325
        func(), error) {
×
1326

×
1327
        // Both channel validation flags are false by default but their meaning
×
1328
        // is the inverse of each other. Therefore both cannot be true. For
×
1329
        // every other case, the neutrino.validatechannels overwrites the
×
1330
        // routing.assumechanvalid value.
×
1331
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
×
1332
                return nil, nil, fmt.Errorf("can't set both " +
×
1333
                        "neutrino.validatechannels and routing." +
×
1334
                        "assumechanvalid to true at the same time")
×
1335
        }
×
1336
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
1337

×
1338
        // First we'll open the database file for neutrino, creating the
×
1339
        // database if needed. We append the normalized network name here to
×
1340
        // match the behavior of btcwallet.
×
1341
        dbPath := filepath.Join(
×
1342
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
1343
        )
×
1344

×
1345
        // Ensure that the neutrino db path exists.
×
1346
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1347
                return nil, nil, err
×
1348
        }
×
1349

1350
        var (
×
1351
                db  walletdb.DB
×
1352
                err error
×
1353
        )
×
1354
        switch {
×
1355
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1356
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1357
                db, err = kvdb.Open(
×
1358
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1359
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1360
                )
×
1361

1362
        default:
×
1363
                dbName := filepath.Join(dbPath, "neutrino.db")
×
1364
                db, err = walletdb.Create(
×
1365
                        "bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
×
1366
                )
×
1367
        }
1368
        if err != nil {
×
1369
                return nil, nil, fmt.Errorf("unable to create "+
×
1370
                        "neutrino database: %v", err)
×
1371
        }
×
1372

1373
        headerStateAssertion, err := parseHeaderStateAssertion(
×
1374
                cfg.NeutrinoMode.AssertFilterHeader,
×
1375
        )
×
1376
        if err != nil {
×
1377
                db.Close()
×
1378
                return nil, nil, err
×
1379
        }
×
1380

1381
        // With the database open, we can now create an instance of the
1382
        // neutrino light client. We pass in relevant configuration parameters
1383
        // required.
1384
        config := neutrino.Config{
×
1385
                DataDir:      dbPath,
×
1386
                Database:     db,
×
1387
                ChainParams:  *cfg.ActiveNetParams.Params,
×
1388
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
1389
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
1390
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
1391
                        return cfg.net.Dial(
×
1392
                                addr.Network(), addr.String(),
×
1393
                                cfg.ConnectionTimeout,
×
1394
                        )
×
1395
                },
×
1396
                NameResolver: func(host string) ([]net.IP, error) {
×
1397
                        addrs, err := cfg.net.LookupHost(host)
×
1398
                        if err != nil {
×
1399
                                return nil, err
×
1400
                        }
×
1401

1402
                        ips := make([]net.IP, 0, len(addrs))
×
1403
                        for _, strIP := range addrs {
×
1404
                                ip := net.ParseIP(strIP)
×
1405
                                if ip == nil {
×
1406
                                        continue
×
1407
                                }
1408

1409
                                ips = append(ips, ip)
×
1410
                        }
1411

1412
                        return ips, nil
×
1413
                },
1414
                AssertFilterHeader: headerStateAssertion,
1415
                BlockCache:         blockCache.Cache,
1416
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1417
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1418
        }
1419

1420
        neutrino.MaxPeers = 8
×
1421
        neutrino.BanDuration = time.Hour * 48
×
1422
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
×
1423
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
×
1424

×
1425
        neutrinoCS, err := neutrino.NewChainService(config)
×
1426
        if err != nil {
×
1427
                db.Close()
×
1428
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1429
                        "client: %v", err)
×
1430
        }
×
1431

1432
        if err := neutrinoCS.Start(); err != nil {
×
1433
                db.Close()
×
1434
                return nil, nil, err
×
1435
        }
×
1436

1437
        cleanUp := func() {
×
1438
                if err := neutrinoCS.Stop(); err != nil {
×
1439
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1440
                                "%v", err)
×
1441
                }
×
1442
                db.Close()
×
1443
        }
1444

1445
        return neutrinoCS, cleanUp, nil
×
1446
}
1447

1448
// parseHeaderStateAssertion parses the user-specified neutrino header state
1449
// into a headerfs.FilterHeader.
1450
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
1451
        if len(state) == 0 {
×
1452
                return nil, nil
×
1453
        }
×
1454

1455
        split := strings.Split(state, ":")
×
1456
        if len(split) != 2 {
×
1457
                return nil, fmt.Errorf("header state assertion %v in "+
×
1458
                        "unexpected format, expected format height:hash", state)
×
1459
        }
×
1460

1461
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1462
        if err != nil {
×
1463
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1464
        }
×
1465

1466
        hash, err := chainhash.NewHashFromStr(split[1])
×
1467
        if err != nil {
×
1468
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1469
        }
×
1470

1471
        return &headerfs.FilterHeader{
×
1472
                Height:     uint32(height),
×
1473
                FilterHash: *hash,
×
1474
        }, nil
×
1475
}
1476

1477
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1478
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1479
// resides in the neutrino package to use all of its functionalities.
1480
func broadcastErrorMapper(err error) error {
3✔
1481
        var returnErr error
3✔
1482

3✔
1483
        // We only filter for specific backend errors which are relevant for the
3✔
1484
        // Rebroadcaster.
3✔
1485
        switch {
3✔
1486
        // This makes sure the tx is removed from the rebroadcaster once it is
1487
        // confirmed.
1488
        case errors.Is(err, chain.ErrTxAlreadyKnown),
1489
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
2✔
1490

2✔
1491
                returnErr = &pushtx.BroadcastError{
2✔
1492
                        Code:   pushtx.Confirmed,
2✔
1493
                        Reason: err.Error(),
2✔
1494
                }
2✔
1495

1496
        // Transactions which are still in mempool but might fall out because
1497
        // of low fees are rebroadcasted despite of their backend error.
1498
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1499
                returnErr = &pushtx.BroadcastError{
×
1500
                        Code:   pushtx.Mempool,
×
1501
                        Reason: err.Error(),
×
1502
                }
×
1503

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

×
1512
                returnErr = &pushtx.BroadcastError{
×
1513
                        Code:   pushtx.Mempool,
×
1514
                        Reason: err.Error(),
×
1515
                }
×
1516
        }
1517

1518
        return returnErr
3✔
1519
}
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