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

lightningnetwork / lnd / 15155511119

21 May 2025 06:52AM UTC coverage: 57.389% (-11.6%) from 68.996%
15155511119

Pull #9844

github

web-flow
Merge 8658c8597 into c52a6ddeb
Pull Request #9844: Refactor Payment PR 3

346 of 493 new or added lines in 4 files covered. (70.18%)

30172 existing lines in 456 files now uncovered.

95441 of 166305 relevant lines covered (57.39%)

0.61 hits per line

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

54.03
/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
        pymtpkg "github.com/lightningnetwork/lnd/payments"
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
230
        return &DefaultWalletImpl{
1✔
231
                cfg:         cfg,
1✔
232
                logger:      logger,
1✔
233
                interceptor: interceptor,
1✔
234
                watchOnly:   watchOnly,
1✔
235
                pwService:   createWalletUnlockerService(cfg),
1✔
236
        }
1✔
237
}
1✔
238

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

1✔
248
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
1✔
249
                ctx, mux, restProxyDest, restDialOpts,
1✔
250
        )
1✔
251
}
1✔
252

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

1✔
261
        return nil
1✔
262
}
1✔
263

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

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

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

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

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

312
                                fn()
1✔
313
                        }
314
                }
315
        )
316
        defer func() {
2✔
317
                if earlyExit {
1✔
318
                        cleanUp()
×
319
                }
×
320
        }()
321

322
        // Initialize a new block cache.
323
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
1✔
324

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

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

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

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

366
        if !walletExists {
2✔
367
                interceptorChain.SetWalletNotCreated()
1✔
368
        } else {
2✔
369
                interceptorChain.SetWalletLocked()
1✔
370
        }
1✔
371

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

583
        // Now that the wallet password has been provided, transition the RPC
584
        // state into Unlocked.
585
        interceptorChain.SetWalletUnlocked()
1✔
586

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

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

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

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

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

1✔
663
        // Parse coin selection strategy.
1✔
664
        switch d.cfg.CoinSelectionStrategy {
1✔
665
        case "largest":
1✔
666
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
1✔
667

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

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

676
        earlyExit = false
1✔
677
        return partialChainControl, walletConfig, cleanUp, nil
1✔
678
}
679

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

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

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

1✔
705
                                sub.Notifications <- ntfn
1✔
706
                        }
1✔
707
                }()
708

709
                return &sub, nil
1✔
710
        }
711
}
712

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

718
        *pushtx.Broadcaster
719
}
720

721
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
722
func newWalletReBroadcaster(
723
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
1✔
724

1✔
725
        return &walletReBroadcaster{
1✔
726
                Broadcaster: broadcaster,
1✔
727
        }
1✔
728
}
1✔
729

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

1✔
734
        return w.Broadcaster.Start()
1✔
735
}
1✔
736

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

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

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

759
        keyRing := keychain.NewBtcWalletKeyRing(
1✔
760
                walletController.InternalWallet(), walletConfig.CoinType,
1✔
761
        )
1✔
762

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

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

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

804
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
1✔
805
                        pushtx.NewBroadcaster(&broadcastCfg),
1✔
806
                )
1✔
807
        }
808

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

820
        return activeChainControl, cleanUp, nil
1✔
821
}
822

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

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

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

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

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

868
        baseKeyRing := keychain.NewBtcWalletKeyRing(
1✔
869
                walletController.InternalWallet(), walletConfig.CoinType,
1✔
870
        )
1✔
871

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

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

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

908
        return activeChainControl, cleanUp, nil
1✔
909
}
910

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

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

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

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

928
        // PaymentDB is the database that stores all payment related
929
        // information.
930
        PaymentDB pymtpkg.PaymentDB
931

932
        // MacaroonDB is the database that stores macaroon root keys.
933
        MacaroonDB kvdb.Backend
934

935
        // DecayedLogDB is the database that stores p2p related encryption
936
        // information.
937
        DecayedLogDB kvdb.Backend
938

939
        // TowerClientDB is the database that stores the watchtower client's
940
        // configuration.
941
        TowerClientDB wtclient.DB
942

943
        // TowerServerDB is the database that stores the watchtower server's
944
        // configuration.
945
        TowerServerDB watchtower.DB
946

947
        // WalletDB is the configuration for loading the wallet database using
948
        // the btcwallet's loader.
949
        WalletDB btcwallet.LoaderOption
950

951
        // NativeSQLStore holds a reference to the native SQL store that can
952
        // be used for native SQL queries for tables that already support it.
953
        // This may be nil if the use-native-sql flag was not set.
954
        NativeSQLStore sqldb.DB
955
}
956

957
// DefaultDatabaseBuilder is a type that builds the default database backends
958
// for lnd, using the given configuration to decide what actual implementation
959
// to use.
960
type DefaultDatabaseBuilder struct {
961
        cfg    *Config
962
        logger btclog.Logger
963
}
964

965
// NewDefaultDatabaseBuilder returns a new instance of the default database
966
// builder.
967
func NewDefaultDatabaseBuilder(cfg *Config,
968
        logger btclog.Logger) *DefaultDatabaseBuilder {
1✔
969

1✔
970
        return &DefaultDatabaseBuilder{
1✔
971
                cfg:    cfg,
1✔
972
                logger: logger,
1✔
973
        }
1✔
974
}
1✔
975

976
// BuildDatabase extracts the current databases that we'll use for normal
977
// operation in the daemon. A function closure that closes all opened databases
978
// is also returned.
979
func (d *DefaultDatabaseBuilder) BuildDatabase(
980
        ctx context.Context) (*DatabaseInstances, func(), error) {
1✔
981

1✔
982
        d.logger.Infof("Opening the main database, this might take a few " +
1✔
983
                "minutes...")
1✔
984

1✔
985
        cfg := d.cfg
1✔
986
        if cfg.DB.Backend == lncfg.BoltBackend {
2✔
987
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
1✔
988
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
1✔
989
                        cfg.DB.Bolt.AutoCompact)
1✔
990
        }
1✔
991

992
        startOpenTime := time.Now()
1✔
993

1✔
994
        databaseBackends, err := cfg.DB.GetBackends(
1✔
995
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
1✔
996
                        cfg.Watchtower.TowerDir, BitcoinChainName,
1✔
997
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
1✔
998
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
1✔
999
        )
1✔
1000
        if err != nil {
1✔
1001
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
1002
                        "backends: %v", err)
×
1003
        }
×
1004

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

1034
        graphDBOptions := []graphdb.KVStoreOptionModifier{
1✔
1035
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
1✔
1036
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
1✔
1037
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
1✔
1038
        }
1✔
1039

1✔
1040
        chanGraphOpts := []graphdb.ChanGraphOption{
1✔
1041
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
1✔
1042
        }
1✔
1043

1✔
1044
        // We want to pre-allocate the channel graph cache according to what we
1✔
1045
        // expect for mainnet to speed up memory allocation.
1✔
1046
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
1✔
1047
                chanGraphOpts = append(
×
1048
                        chanGraphOpts, graphdb.WithPreAllocCacheNumNodes(
×
1049
                                graphdb.DefaultPreAllocCacheNumNodes,
×
1050
                        ),
×
1051
                )
×
1052
        }
×
1053

1054
        dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{
1✔
1055
                KVDB:        databaseBackends.GraphDB,
1✔
1056
                KVStoreOpts: graphDBOptions,
1✔
1057
        }, chanGraphOpts...)
1✔
1058
        if err != nil {
1✔
1059
                cleanUp()
×
1060

×
1061
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1062
                d.logger.Error(err)
×
1063

×
1064
                return nil, nil, err
×
1065
        }
×
1066

1067
        dbOptions := []channeldb.OptionModifier{
1✔
1068
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
1✔
1069
                channeldb.OptionKeepFailedPaymentAttempts(
1✔
1070
                        cfg.KeepFailedPaymentAttempts,
1✔
1071
                ),
1✔
1072
                channeldb.OptionStoreFinalHtlcResolutions(
1✔
1073
                        cfg.StoreFinalHtlcResolutions,
1✔
1074
                ),
1✔
1075
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
1✔
1076
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
1✔
1077
        }
1✔
1078

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

1092
        case err != nil:
×
1093
                cleanUp()
×
1094

×
1095
                err = fmt.Errorf("unable to open graph DB: %w", err)
×
1096
                d.logger.Error(err)
×
1097
                return nil, nil, err
×
1098
        }
1099

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

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

1123
                                // Set the invoice bucket tombstone to indicate
1124
                                // that the migration has been completed.
1125
                                d.logger.Debugf("Setting invoice bucket " +
×
1126
                                        "tombstone")
×
1127

×
1128
                                return dbs.ChanStateDB.SetInvoiceBucketTombstone() //nolint:ll
×
1129
                        }
1130

1131
                        // Make sure we attach the custom migration function to
1132
                        // the correct migration version.
1133
                        for i := 0; i < len(migrations); i++ {
×
1134
                                if migrations[i].Version != invoiceMigration {
×
1135
                                        continue
×
1136
                                }
1137

1138
                                migrations[i].MigrationFn = migrationFn
×
1139
                        }
1140
                }
1141

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

×
1151
                        return nil, nil, err
×
1152
                }
×
1153

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

1164
                sqlInvoiceDB := invoices.NewSQLStore(
×
1165
                        executor, clock.NewDefaultClock(),
×
1166
                )
×
1167

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

×
1179
                        return nil, nil, err
×
1180
                }
×
1181
                if ripInvoices {
1✔
1182
                        err = fmt.Errorf("invoices bucket tombstoned, please " +
×
1183
                                "switch back to native SQL")
×
1184
                        d.logger.Error(err)
×
1185

×
1186
                        return nil, nil, err
×
1187
                }
×
1188

1189
                dbs.InvoiceDB = dbs.ChanStateDB
1✔
1190
        }
1191

1192
        // Mount the payments DB which is only KV for now.
1193
        //
1194
        // TODO(ziggie): Add support for SQL payments DB.
1195
        kvPaymentsDB := channeldb.NewKVPaymentsDB(
1✔
1196
                dbs.ChanStateDB,
1✔
1197
        )
1✔
1198
        dbs.PaymentDB = kvPaymentsDB
1✔
1199

1✔
1200
        // Wrap the watchtower client DB and make sure we clean up.
1✔
1201
        if cfg.WtClient.Active {
2✔
1202
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
1✔
1203
                        databaseBackends.TowerClientDB,
1✔
1204
                )
1✔
1205
                if err != nil {
1✔
1206
                        cleanUp()
×
1207

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

1215
        // Wrap the watchtower server DB and make sure we clean up.
1216
        if cfg.Watchtower.Active {
2✔
1217
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
1✔
1218
                        databaseBackends.TowerServerDB,
1✔
1219
                )
1✔
1220
                if err != nil {
1✔
1221
                        cleanUp()
×
1222

×
1223
                        err = fmt.Errorf("unable to open %s database: %w",
×
1224
                                lncfg.NSTowerServerDB, err)
×
1225
                        d.logger.Error(err)
×
1226
                        return nil, nil, err
×
1227
                }
×
1228
        }
1229

1230
        openTime := time.Since(startOpenTime)
1✔
1231
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
1✔
1232

1✔
1233
        return dbs, cleanUp, nil
1✔
1234
}
1235

1236
// waitForWalletPassword blocks until a password is provided by the user to
1237
// this RPC server.
1238
func waitForWalletPassword(cfg *Config,
1239
        pwService *walletunlocker.UnlockerService,
1240
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1241
        *walletunlocker.WalletUnlockParams, error) {
1✔
1242

1✔
1243
        // Wait for user to provide the password.
1✔
1244
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
1✔
1245
                "create` to create a wallet, `lncli unlock` to unlock an " +
1✔
1246
                "existing wallet, or `lncli changepassword` to change the " +
1✔
1247
                "password of an existing wallet and unlock it.")
1✔
1248

1✔
1249
        // We currently don't distinguish between getting a password to be used
1✔
1250
        // for creation or unlocking, as a new wallet db will be created if
1✔
1251
        // none exists when creating the chain control.
1✔
1252
        select {
1✔
1253
        // The wallet is being created for the first time, we'll check to see
1254
        // if the user provided any entropy for seed creation. If so, then
1255
        // we'll create the wallet early to load the seed.
1256
        case initMsg := <-pwService.InitMsgs:
1✔
1257
                password := initMsg.Passphrase
1✔
1258
                cipherSeed := initMsg.WalletSeed
1✔
1259
                extendedKey := initMsg.WalletExtendedKey
1✔
1260
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
1✔
1261
                recoveryWindow := initMsg.RecoveryWindow
1✔
1262

1✔
1263
                // Before we proceed, we'll check the internal version of the
1✔
1264
                // seed. If it's greater than the current key derivation
1✔
1265
                // version, then we'll return an error as we don't understand
1✔
1266
                // this.
1✔
1267
                if cipherSeed != nil &&
1✔
1268
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
1✔
1269

×
1270
                        return nil, fmt.Errorf("invalid internal "+
×
1271
                                "seed version %v, current max version is %v",
×
1272
                                cipherSeed.InternalVersion,
×
1273
                                keychain.CurrentKeyDerivationVersion)
×
1274
                }
×
1275

1276
                loader, err := btcwallet.NewWalletLoader(
1✔
1277
                        cfg.ActiveNetParams.Params, recoveryWindow,
1✔
1278
                        loaderOpts...,
1✔
1279
                )
1✔
1280
                if err != nil {
1✔
1281
                        return nil, err
×
1282
                }
×
1283

1284
                // With the seed, we can now use the wallet loader to create
1285
                // the wallet, then pass it back to avoid unlocking it again.
1286
                var (
1✔
1287
                        birthday  time.Time
1✔
1288
                        newWallet *wallet.Wallet
1✔
1289
                )
1✔
1290
                switch {
1✔
1291
                // A normal cipher seed was given, use the birthday encoded in
1292
                // it and create the wallet from that.
1293
                case cipherSeed != nil:
1✔
1294
                        birthday = cipherSeed.BirthdayTime()
1✔
1295
                        newWallet, err = loader.CreateNewWallet(
1✔
1296
                                password, password, cipherSeed.Entropy[:],
1✔
1297
                                birthday,
1✔
1298
                        )
1✔
1299

1300
                // No seed was given, we're importing a wallet from its extended
1301
                // private key.
1302
                case extendedKey != nil:
1✔
1303
                        birthday = initMsg.ExtendedKeyBirthday
1✔
1304
                        newWallet, err = loader.CreateNewWalletExtendedKey(
1✔
1305
                                password, password, extendedKey, birthday,
1✔
1306
                        )
1✔
1307

1308
                // Neither seed nor extended private key was given, so maybe the
1309
                // third option was chosen, the watch-only initialization. In
1310
                // this case we need to import each of the xpubs individually.
1311
                case watchOnlyAccounts != nil:
1✔
1312
                        if !cfg.RemoteSigner.Enable {
1✔
1313
                                return nil, fmt.Errorf("cannot initialize " +
×
1314
                                        "watch only wallet with remote " +
×
1315
                                        "signer config disabled")
×
1316
                        }
×
1317

1318
                        birthday = initMsg.WatchOnlyBirthday
1✔
1319
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
1✔
1320
                                password, birthday,
1✔
1321
                        )
1✔
1322
                        if err != nil {
1✔
1323
                                break
×
1324
                        }
1325

1326
                        err = importWatchOnlyAccounts(newWallet, initMsg)
1✔
1327

1328
                default:
×
1329
                        // The unlocker service made sure either the cipher seed
×
1330
                        // or the extended key is set so, we shouldn't get here.
×
1331
                        // The default case is just here for readability and
×
1332
                        // completeness.
×
1333
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1334
                                "nor extended key was given")
×
1335
                }
1336
                if err != nil {
1✔
1337
                        // Don't leave the file open in case the new wallet
×
1338
                        // could not be created for whatever reason.
×
1339
                        if err := loader.UnloadWallet(); err != nil {
×
1340
                                ltndLog.Errorf("Could not unload new "+
×
1341
                                        "wallet: %v", err)
×
1342
                        }
×
1343
                        return nil, err
×
1344
                }
1345

1346
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1347
                if cfg.ResetWalletTransactions {
2✔
1348
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
1✔
1349
                                "flag for new wallet as it has no effect")
1✔
1350
                }
1✔
1351

1352
                return &walletunlocker.WalletUnlockParams{
1✔
1353
                        Password:        password,
1✔
1354
                        Birthday:        birthday,
1✔
1355
                        RecoveryWindow:  recoveryWindow,
1✔
1356
                        Wallet:          newWallet,
1✔
1357
                        ChansToRestore:  initMsg.ChanBackups,
1✔
1358
                        UnloadWallet:    loader.UnloadWallet,
1✔
1359
                        StatelessInit:   initMsg.StatelessInit,
1✔
1360
                        MacResponseChan: pwService.MacResponseChan,
1✔
1361
                        MacRootKey:      initMsg.MacRootKey,
1✔
1362
                }, nil
1✔
1363

1364
        // The wallet has already been created in the past, and is simply being
1365
        // unlocked. So we'll just return these passphrases.
1366
        case unlockMsg := <-pwService.UnlockMsgs:
1✔
1367
                // Resetting the transactions is something the user likely only
1✔
1368
                // wants to do once so we add a prominent warning to the log to
1✔
1369
                // remind the user to turn off the setting again after
1✔
1370
                // successful completion.
1✔
1371
                if cfg.ResetWalletTransactions {
2✔
1372
                        ltndLog.Warnf("Dropped all transaction history from " +
1✔
1373
                                "on-chain wallet. Remember to disable " +
1✔
1374
                                "reset-wallet-transactions flag for next " +
1✔
1375
                                "start of lnd")
1✔
1376
                }
1✔
1377

1378
                return &walletunlocker.WalletUnlockParams{
1✔
1379
                        Password:        unlockMsg.Passphrase,
1✔
1380
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
1✔
1381
                        Wallet:          unlockMsg.Wallet,
1✔
1382
                        ChansToRestore:  unlockMsg.ChanBackups,
1✔
1383
                        UnloadWallet:    unlockMsg.UnloadWallet,
1✔
1384
                        StatelessInit:   unlockMsg.StatelessInit,
1✔
1385
                        MacResponseChan: pwService.MacResponseChan,
1✔
1386
                }, nil
1✔
1387

1388
        // If we got a shutdown signal we just return with an error immediately
1389
        case <-shutdownChan:
×
1390
                return nil, fmt.Errorf("shutting down")
×
1391
        }
1392
}
1393

1394
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1395
// which we created as watch-only.
1396
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1397
        initMsg *walletunlocker.WalletInitMsg) error {
1✔
1398

1✔
1399
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
1✔
1400
        for scope := range initMsg.WatchOnlyAccounts {
2✔
1401
                scopes = append(scopes, scope)
1✔
1402
        }
1✔
1403

1404
        // We need to import the accounts in the correct order, otherwise the
1405
        // indices will be incorrect.
1406
        sort.Slice(scopes, func(i, j int) bool {
2✔
1407
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
1✔
1408
                        scopes[i].Index < scopes[j].Index
1✔
1409
        })
1✔
1410

1411
        for _, scope := range scopes {
2✔
1412
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
1✔
1413

1✔
1414
                // We want witness pubkey hash by default, except for BIP49
1✔
1415
                // where we want mixed and BIP86 where we want taproot address
1✔
1416
                // formats.
1✔
1417
                switch scope.Scope.Purpose {
1✔
1418
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1419
                        waddrmgr.KeyScopeBIP0086.Purpose:
1✔
1420

1✔
1421
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
1✔
1422
                }
1423

1424
                // We want a human-readable account name. But for the default
1425
                // on-chain wallet we actually need to call it "default" to make
1426
                // sure everything works correctly.
1427
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
1✔
1428
                if scope.Index == 0 {
2✔
1429
                        name = "default"
1✔
1430
                }
1✔
1431

1432
                _, err := wallet.ImportAccountWithScope(
1✔
1433
                        name, initMsg.WatchOnlyAccounts[scope],
1✔
1434
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
1✔
1435
                        addrSchema,
1✔
1436
                )
1✔
1437
                if err != nil {
1✔
1438
                        return fmt.Errorf("could not import account %v: %w",
×
1439
                                name, err)
×
1440
                }
×
1441
        }
1442

1443
        return nil
1✔
1444
}
1445

1446
// handleNeutrinoPostgresDBMigration handles the migration of the neutrino db
1447
// to postgres. Initially we kept the neutrino db in the bolt db when running
1448
// with kvdb postgres backend. Now now move it to postgres as well. However we
1449
// need to make a distinction whether the user migrated the neutrino db to
1450
// postgres via lndinit or not. Currently if the db is not migrated we start
1451
// with a fresh db in postgres.
1452
//
1453
// TODO(ziggie): Also migrate the db to postgres in case it is still not
1454
// migrated ?
1455
func handleNeutrinoPostgresDBMigration(dbName, dbPath string,
1456
        cfg *Config) error {
×
1457

×
1458
        if !lnrpc.FileExists(dbName) {
×
1459
                return nil
×
1460
        }
×
1461

1462
        // Open bolt db to check if it is tombstoned. If it is we assume that
1463
        // the neutrino db was successfully migrated to postgres. We open it
1464
        // in read-only mode to avoid long db open times.
1465
        boltDB, err := kvdb.Open(
×
1466
                kvdb.BoltBackendName, dbName, true,
×
1467
                cfg.DB.Bolt.DBTimeout, true,
×
1468
        )
×
1469
        if err != nil {
×
1470
                return fmt.Errorf("failed to open bolt db: %w", err)
×
1471
        }
×
1472
        defer boltDB.Close()
×
1473

×
1474
        isTombstoned := false
×
1475
        err = boltDB.View(func(tx kvdb.RTx) error {
×
1476
                _, err = channeldb.CheckMarkerPresent(
×
1477
                        tx, channeldb.TombstoneKey,
×
1478
                )
×
1479

×
1480
                return err
×
1481
        }, func() {})
×
1482
        if err == nil {
×
1483
                isTombstoned = true
×
1484
        }
×
1485

1486
        if isTombstoned {
×
1487
                ltndLog.Infof("Neutrino Bolt DB is tombstoned, assuming " +
×
1488
                        "database was successfully migrated to postgres")
×
1489

×
1490
                return nil
×
1491
        }
×
1492

1493
        // If the db is not tombstoned, we remove the files and start fresh with
1494
        // postgres. This is the case when a user was running lnd with the
1495
        // postgres backend from the beginning without migrating from bolt.
1496
        ltndLog.Infof("Neutrino Bolt DB found but NOT tombstoned, removing " +
×
1497
                "it and starting fresh with postgres")
×
1498

×
1499
        filesToRemove := []string{
×
1500
                filepath.Join(dbPath, "block_headers.bin"),
×
1501
                filepath.Join(dbPath, "reg_filter_headers.bin"),
×
1502
                dbName,
×
1503
        }
×
1504

×
1505
        for _, file := range filesToRemove {
×
1506
                if err := os.Remove(file); err != nil {
×
1507
                        ltndLog.Warnf("Could not remove %s: %v", file, err)
×
1508
                }
×
1509
        }
1510

1511
        return nil
×
1512
}
1513

1514
// initNeutrinoBackend inits a new instance of the neutrino light client
1515
// backend given a target chain directory to store the chain state.
1516
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1517
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
UNCOV
1518
        func(), error) {
×
UNCOV
1519

×
UNCOV
1520
        // Both channel validation flags are false by default but their meaning
×
UNCOV
1521
        // is the inverse of each other. Therefore both cannot be true. For
×
UNCOV
1522
        // every other case, the neutrino.validatechannels overwrites the
×
UNCOV
1523
        // routing.assumechanvalid value.
×
UNCOV
1524
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
×
1525
                return nil, nil, fmt.Errorf("can't set both " +
×
1526
                        "neutrino.validatechannels and routing." +
×
1527
                        "assumechanvalid to true at the same time")
×
1528
        }
×
UNCOV
1529
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
UNCOV
1530

×
UNCOV
1531
        // First we'll open the database file for neutrino, creating the
×
UNCOV
1532
        // database if needed. We append the normalized network name here to
×
UNCOV
1533
        // match the behavior of btcwallet.
×
UNCOV
1534
        dbPath := filepath.Join(
×
UNCOV
1535
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
1536
        )
×
UNCOV
1537

×
UNCOV
1538
        // Ensure that the neutrino db path exists.
×
UNCOV
1539
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1540
                return nil, nil, err
×
1541
        }
×
1542

UNCOV
1543
        var (
×
UNCOV
1544
                db  walletdb.DB
×
UNCOV
1545
                err error
×
UNCOV
1546
        )
×
UNCOV
1547
        switch {
×
1548
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1549
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1550
                db, err = kvdb.Open(
×
1551
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1552
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1553
                )
×
1554

1555
        case cfg.DB.Backend == kvdb.PostgresBackendName:
×
1556
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
1557

×
1558
                // This code needs to be in place because we did not start
×
1559
                // the postgres backend for neutrino at the beginning. Now we
×
1560
                // are also moving it into the postgres backend so we can phase
×
1561
                // out the bolt backend.
×
1562
                err = handleNeutrinoPostgresDBMigration(dbName, dbPath, cfg)
×
1563
                if err != nil {
×
1564
                        return nil, nil, err
×
1565
                }
×
1566

1567
                postgresConfig := lncfg.GetPostgresConfigKVDB(cfg.DB.Postgres)
×
1568
                db, err = kvdb.Open(
×
1569
                        kvdb.PostgresBackendName, ctx, postgresConfig,
×
1570
                        lncfg.NSNeutrinoDB,
×
1571
                )
×
1572

UNCOV
1573
        default:
×
UNCOV
1574
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
UNCOV
1575
                db, err = walletdb.Create(
×
UNCOV
1576
                        kvdb.BoltBackendName, dbName, !cfg.SyncFreelist,
×
UNCOV
1577
                        cfg.DB.Bolt.DBTimeout, false,
×
UNCOV
1578
                )
×
1579
        }
UNCOV
1580
        if err != nil {
×
1581
                return nil, nil, fmt.Errorf("unable to create "+
×
1582
                        "neutrino database: %v", err)
×
1583
        }
×
1584

UNCOV
1585
        headerStateAssertion, err := parseHeaderStateAssertion(
×
UNCOV
1586
                cfg.NeutrinoMode.AssertFilterHeader,
×
UNCOV
1587
        )
×
UNCOV
1588
        if err != nil {
×
1589
                db.Close()
×
1590
                return nil, nil, err
×
1591
        }
×
1592

1593
        // With the database open, we can now create an instance of the
1594
        // neutrino light client. We pass in relevant configuration parameters
1595
        // required.
UNCOV
1596
        config := neutrino.Config{
×
UNCOV
1597
                DataDir:      dbPath,
×
UNCOV
1598
                Database:     db,
×
UNCOV
1599
                ChainParams:  *cfg.ActiveNetParams.Params,
×
UNCOV
1600
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
UNCOV
1601
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
UNCOV
1602
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
UNCOV
1603
                        return cfg.net.Dial(
×
UNCOV
1604
                                addr.Network(), addr.String(),
×
UNCOV
1605
                                cfg.ConnectionTimeout,
×
UNCOV
1606
                        )
×
UNCOV
1607
                },
×
UNCOV
1608
                NameResolver: func(host string) ([]net.IP, error) {
×
UNCOV
1609
                        addrs, err := cfg.net.LookupHost(host)
×
UNCOV
1610
                        if err != nil {
×
1611
                                return nil, err
×
1612
                        }
×
1613

UNCOV
1614
                        ips := make([]net.IP, 0, len(addrs))
×
UNCOV
1615
                        for _, strIP := range addrs {
×
UNCOV
1616
                                ip := net.ParseIP(strIP)
×
UNCOV
1617
                                if ip == nil {
×
1618
                                        continue
×
1619
                                }
1620

UNCOV
1621
                                ips = append(ips, ip)
×
1622
                        }
1623

UNCOV
1624
                        return ips, nil
×
1625
                },
1626
                AssertFilterHeader: headerStateAssertion,
1627
                BlockCache:         blockCache.Cache,
1628
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1629
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1630
        }
1631

UNCOV
1632
        if cfg.NeutrinoMode.MaxPeers <= 0 {
×
1633
                return nil, nil, fmt.Errorf("a non-zero number must be set " +
×
1634
                        "for neutrino max peers")
×
1635
        }
×
UNCOV
1636
        neutrino.MaxPeers = cfg.NeutrinoMode.MaxPeers
×
UNCOV
1637
        neutrino.BanDuration = time.Hour * 48
×
UNCOV
1638
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
×
UNCOV
1639
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
×
UNCOV
1640

×
UNCOV
1641
        neutrinoCS, err := neutrino.NewChainService(config)
×
UNCOV
1642
        if err != nil {
×
1643
                db.Close()
×
1644
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1645
                        "client: %v", err)
×
1646
        }
×
1647

UNCOV
1648
        if err := neutrinoCS.Start(); err != nil {
×
1649
                db.Close()
×
1650
                return nil, nil, err
×
1651
        }
×
1652

UNCOV
1653
        cleanUp := func() {
×
UNCOV
1654
                if err := neutrinoCS.Stop(); err != nil {
×
1655
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1656
                                "%v", err)
×
1657
                }
×
UNCOV
1658
                db.Close()
×
1659
        }
1660

UNCOV
1661
        return neutrinoCS, cleanUp, nil
×
1662
}
1663

1664
// parseHeaderStateAssertion parses the user-specified neutrino header state
1665
// into a headerfs.FilterHeader.
UNCOV
1666
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
UNCOV
1667
        if len(state) == 0 {
×
UNCOV
1668
                return nil, nil
×
UNCOV
1669
        }
×
1670

1671
        split := strings.Split(state, ":")
×
1672
        if len(split) != 2 {
×
1673
                return nil, fmt.Errorf("header state assertion %v in "+
×
1674
                        "unexpected format, expected format height:hash", state)
×
1675
        }
×
1676

1677
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1678
        if err != nil {
×
1679
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1680
        }
×
1681

1682
        hash, err := chainhash.NewHashFromStr(split[1])
×
1683
        if err != nil {
×
1684
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1685
        }
×
1686

1687
        return &headerfs.FilterHeader{
×
1688
                Height:     uint32(height),
×
1689
                FilterHash: *hash,
×
1690
        }, nil
×
1691
}
1692

1693
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1694
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1695
// resides in the neutrino package to use all of its functionalities.
1696
func broadcastErrorMapper(err error) error {
1✔
1697
        var returnErr error
1✔
1698

1✔
1699
        // We only filter for specific backend errors which are relevant for the
1✔
1700
        // Rebroadcaster.
1✔
1701
        switch {
1✔
1702
        // This makes sure the tx is removed from the rebroadcaster once it is
1703
        // confirmed.
1704
        case errors.Is(err, chain.ErrTxAlreadyKnown),
UNCOV
1705
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
×
UNCOV
1706

×
UNCOV
1707
                returnErr = &pushtx.BroadcastError{
×
UNCOV
1708
                        Code:   pushtx.Confirmed,
×
UNCOV
1709
                        Reason: err.Error(),
×
UNCOV
1710
                }
×
1711

1712
        // Transactions which are still in mempool but might fall out because
1713
        // of low fees are rebroadcasted despite of their backend error.
1714
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1715
                returnErr = &pushtx.BroadcastError{
×
1716
                        Code:   pushtx.Mempool,
×
1717
                        Reason: err.Error(),
×
1718
                }
×
1719

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

×
1728
                returnErr = &pushtx.BroadcastError{
×
1729
                        Code:   pushtx.Mempool,
×
1730
                        Reason: err.Error(),
×
1731
                }
×
1732
        }
1733

1734
        return returnErr
1✔
1735
}
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