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

lightningnetwork / lnd / 19924300449

04 Dec 2025 09:35AM UTC coverage: 53.479% (-1.9%) from 55.404%
19924300449

Pull #10419

github

web-flow
Merge f811805c6 into 20473482d
Pull Request #10419: [docs] Document use-native-sql=true for SQL migration step 2

110496 of 206616 relevant lines covered (53.48%)

21221.61 hits per line

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

0.0
/config_builder.go
1
package lnd
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

157
        // ExternalValidator is a type that can provide external macaroon
158
        // validation.
159
        ExternalValidator
160

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

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

169
        // ChainControlBuilder is a type that can provide a custom wallet
170
        // implementation.
171
        ChainControlBuilder
172

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

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

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

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

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

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

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

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

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

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

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

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

232
        watchOnly        bool
233
        migrateWatchOnly bool
234
        pwService        *walletunlocker.UnlockerService
235
}
236

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

×
241
        return &DefaultWalletImpl{
×
242
                cfg:         cfg,
×
243
                logger:      logger,
×
244
                interceptor: interceptor,
×
245
                watchOnly:   watchOnly,
×
246
                pwService:   createWalletUnlockerService(cfg),
×
247
        }
×
248
}
×
249

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

×
259
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
×
260
                ctx, mux, restProxyDest, restDialOpts,
×
261
        )
×
262
}
×
263

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

×
272
        return nil
×
273
}
×
274

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

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

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

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

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

323
                                fn()
×
324
                        }
325
                }
326
        )
327
        defer func() {
×
328
                if earlyExit {
×
329
                        cleanUp()
×
330
                }
×
331
        }()
332

333
        // Initialize a new block cache.
334
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
×
335

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

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

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

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

377
        if !walletExists {
×
378
                interceptorChain.SetWalletNotCreated()
×
379
        } else {
×
380
                interceptorChain.SetWalletLocked()
×
381
        }
×
382

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

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

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

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

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

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

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

437
                privateWalletPw = pwBytes
×
438
                publicWalletPw = pwBytes
×
439
                walletInitParams.Wallet = unlockedWallet
×
440
                walletInitParams.UnloadWallet = unloadWalletFn
×
441

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

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

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

471
                if walletInitParams.RecoveryWindow > 0 {
×
472
                        d.logger.Infof("Wallet recovery mode enabled with "+
×
473
                                "address lookahead of %d addresses",
×
474
                                walletInitParams.RecoveryWindow)
×
475
                }
×
476
        }
477

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

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

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

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

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

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

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

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

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

594
        // Now that the wallet password has been provided, transition the RPC
595
        // state into Unlocked.
596
        interceptorChain.SetWalletUnlocked()
×
597

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

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

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

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

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

×
674
        // Parse coin selection strategy.
×
675
        switch d.cfg.CoinSelectionStrategy {
×
676
        case "largest":
×
677
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
×
678

679
        case "random":
×
680
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
681

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

687
        earlyExit = false
×
688
        return partialChainControl, walletConfig, cleanUp, nil
×
689
}
690

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

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

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

×
716
                                sub.Notifications <- ntfn
×
717
                        }
×
718
                }()
719

720
                return &sub, nil
×
721
        }
722
}
723

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

729
        *pushtx.Broadcaster
730
}
731

732
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
733
func newWalletReBroadcaster(
734
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
×
735

×
736
        return &walletReBroadcaster{
×
737
                Broadcaster: broadcaster,
×
738
        }
×
739
}
×
740

741
// Start launches all goroutines the rebroadcaster needs to operate.
742
func (w *walletReBroadcaster) Start() error {
×
743
        defer w.started.Store(true)
×
744

×
745
        return w.Broadcaster.Start()
×
746
}
×
747

748
// Started returns true if the broadcaster is already active.
749
func (w *walletReBroadcaster) Started() bool {
×
750
        return w.started.Load()
×
751
}
×
752

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

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

770
        keyRing := keychain.NewBtcWalletKeyRing(
×
771
                walletController.InternalWallet(), walletConfig.CoinType,
×
772
        )
×
773

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

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

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

815
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
×
816
                        pushtx.NewBroadcaster(&broadcastCfg),
×
817
                )
×
818
        }
819

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

831
        return activeChainControl, cleanUp, nil
×
832
}
833

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

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

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

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

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

879
        baseKeyRing := keychain.NewBtcWalletKeyRing(
×
880
                walletController.InternalWallet(), walletConfig.CoinType,
×
881
        )
×
882

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

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

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

919
        return activeChainControl, cleanUp, nil
×
920
}
921

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

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

933
        // HeightHintDB is the database that stores height hints for spends.
934
        HeightHintDB kvdb.Backend
935

936
        // InvoiceDB is the database that stores information about invoices.
937
        InvoiceDB invoices.InvoiceDB
938

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

943
        // MacaroonDB is the database that stores macaroon root keys.
944
        MacaroonDB kvdb.Backend
945

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

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

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

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

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

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

976
// NewDefaultDatabaseBuilder returns a new instance of the default database
977
// builder.
978
func NewDefaultDatabaseBuilder(cfg *Config,
979
        logger btclog.Logger) *DefaultDatabaseBuilder {
×
980

×
981
        return &DefaultDatabaseBuilder{
×
982
                cfg:    cfg,
×
983
                logger: logger,
×
984
        }
×
985
}
×
986

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

×
993
        d.logger.Infof("Opening the main database, this might take a few " +
×
994
                "minutes...")
×
995

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

1003
        startOpenTime := time.Now()
×
1004

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

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

1045
        graphDBOptions := []graphdb.StoreOptionModifier{
×
1046
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
×
1047
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
×
1048
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
×
1049
        }
×
1050

×
1051
        chanGraphOpts := []graphdb.ChanGraphOption{
×
1052
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
×
1053
        }
×
1054

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

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

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

1089
        case err != nil:
×
1090
                cleanUp()
×
1091

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

1097
        // The graph store implementation we will use depends on whether
1098
        // native SQL is enabled or not.
1099
        var graphStore graphdb.V1Store
×
1100

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

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

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

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

×
1134
                                //nolint:ll
×
1135
                                return dbs.ChanStateDB.SetInvoiceBucketTombstone()
×
1136
                        }
1137

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

1153
                                return nil
×
1154
                        }
1155

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

×
1164
                                        continue
×
1165
                                case graphMigration:
×
1166
                                        migrations[i].MigrationFn = graphMig
×
1167

×
1168
                                        continue
×
1169

1170
                                default:
×
1171
                                }
1172

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

1180
                                migrations[i].MigrationFn = migFn
×
1181
                        }
1182
                }
1183

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

×
1193
                        return nil, nil, err
×
1194
                }
×
1195

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

1206
                sqlInvoiceDB := invoices.NewSQLStore(
×
1207
                        invoiceExecutor, clock.NewDefaultClock(),
×
1208
                )
×
1209

×
1210
                dbs.InvoiceDB = sqlInvoiceDB
×
1211

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

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

×
1229
                        return nil, nil, err
×
1230
                }
×
1231
        } else {
×
1232
                // Check if the invoice bucket tombstone is set. If it is, we
×
1233
                // need to return and ask the user switch back to using the
×
1234
                // native SQL store.
×
1235
                ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
×
1236
                if err != nil {
×
1237
                        err = fmt.Errorf("unable to check invoice bucket "+
×
1238
                                "tombstone: %w", err)
×
1239
                        d.logger.Error(err)
×
1240

×
1241
                        return nil, nil, err
×
1242
                }
×
1243
                if ripInvoices {
×
1244
                        err = fmt.Errorf("invoices bucket tombstoned, please " +
×
1245
                                "switch back to native SQL")
×
1246
                        d.logger.Error(err)
×
1247

×
1248
                        return nil, nil, err
×
1249
                }
×
1250

1251
                dbs.InvoiceDB = dbs.ChanStateDB
×
1252

×
1253
                graphStore, err = graphdb.NewKVStore(
×
1254
                        databaseBackends.GraphDB, graphDBOptions...,
×
1255
                )
×
1256
                if err != nil {
×
1257
                        return nil, nil, err
×
1258
                }
×
1259
        }
1260

1261
        dbs.GraphDB, err = graphdb.NewChannelGraph(graphStore, chanGraphOpts...)
×
1262
        if err != nil {
×
1263
                cleanUp()
×
1264

×
1265
                err = fmt.Errorf("unable to open channel graph DB: %w", err)
×
1266
                d.logger.Error(err)
×
1267

×
1268
                return nil, nil, err
×
1269
        }
×
1270

1271
        // Mount the payments DB which is only KV for now.
1272
        //
1273
        // TODO(ziggie): Add support for SQL payments DB.
1274
        // Mount the payments DB for the KV store.
1275
        paymentsDBOptions := []paymentsdb.OptionModifier{
×
1276
                paymentsdb.WithKeepFailedPaymentAttempts(
×
1277
                        cfg.KeepFailedPaymentAttempts,
×
1278
                ),
×
1279
        }
×
1280
        kvPaymentsDB, err := paymentsdb.NewKVStore(
×
1281
                dbs.ChanStateDB,
×
1282
                paymentsDBOptions...,
×
1283
        )
×
1284
        if err != nil {
×
1285
                cleanUp()
×
1286

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

×
1290
                return nil, nil, err
×
1291
        }
×
1292
        dbs.PaymentsDB = kvPaymentsDB
×
1293

×
1294
        // Wrap the watchtower client DB and make sure we clean up.
×
1295
        if cfg.WtClient.Active {
×
1296
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
×
1297
                        databaseBackends.TowerClientDB,
×
1298
                )
×
1299
                if err != nil {
×
1300
                        cleanUp()
×
1301

×
1302
                        err = fmt.Errorf("unable to open %s database: %w",
×
1303
                                lncfg.NSTowerClientDB, err)
×
1304
                        d.logger.Error(err)
×
1305
                        return nil, nil, err
×
1306
                }
×
1307
        }
1308

1309
        // Wrap the watchtower server DB and make sure we clean up.
1310
        if cfg.Watchtower.Active {
×
1311
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
×
1312
                        databaseBackends.TowerServerDB,
×
1313
                )
×
1314
                if err != nil {
×
1315
                        cleanUp()
×
1316

×
1317
                        err = fmt.Errorf("unable to open %s database: %w",
×
1318
                                lncfg.NSTowerServerDB, err)
×
1319
                        d.logger.Error(err)
×
1320
                        return nil, nil, err
×
1321
                }
×
1322
        }
1323

1324
        openTime := time.Since(startOpenTime)
×
1325
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
×
1326

×
1327
        return dbs, cleanUp, nil
×
1328
}
1329

1330
// waitForWalletPassword blocks until a password is provided by the user to
1331
// this RPC server.
1332
func waitForWalletPassword(cfg *Config,
1333
        pwService *walletunlocker.UnlockerService,
1334
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1335
        *walletunlocker.WalletUnlockParams, error) {
×
1336

×
1337
        // Wait for user to provide the password.
×
1338
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
×
1339
                "create` to create a wallet, `lncli unlock` to unlock an " +
×
1340
                "existing wallet, or `lncli changepassword` to change the " +
×
1341
                "password of an existing wallet and unlock it.")
×
1342

×
1343
        // We currently don't distinguish between getting a password to be used
×
1344
        // for creation or unlocking, as a new wallet db will be created if
×
1345
        // none exists when creating the chain control.
×
1346
        select {
×
1347
        // The wallet is being created for the first time, we'll check to see
1348
        // if the user provided any entropy for seed creation. If so, then
1349
        // we'll create the wallet early to load the seed.
1350
        case initMsg := <-pwService.InitMsgs:
×
1351
                password := initMsg.Passphrase
×
1352
                cipherSeed := initMsg.WalletSeed
×
1353
                extendedKey := initMsg.WalletExtendedKey
×
1354
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
×
1355
                recoveryWindow := initMsg.RecoveryWindow
×
1356

×
1357
                // Before we proceed, we'll check the internal version of the
×
1358
                // seed. If it's greater than the current key derivation
×
1359
                // version, then we'll return an error as we don't understand
×
1360
                // this.
×
1361
                if cipherSeed != nil &&
×
1362
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
×
1363

×
1364
                        return nil, fmt.Errorf("invalid internal "+
×
1365
                                "seed version %v, current max version is %v",
×
1366
                                cipherSeed.InternalVersion,
×
1367
                                keychain.CurrentKeyDerivationVersion)
×
1368
                }
×
1369

1370
                loader, err := btcwallet.NewWalletLoader(
×
1371
                        cfg.ActiveNetParams.Params, recoveryWindow,
×
1372
                        loaderOpts...,
×
1373
                )
×
1374
                if err != nil {
×
1375
                        return nil, err
×
1376
                }
×
1377

1378
                // With the seed, we can now use the wallet loader to create
1379
                // the wallet, then pass it back to avoid unlocking it again.
1380
                var (
×
1381
                        birthday  time.Time
×
1382
                        newWallet *wallet.Wallet
×
1383
                )
×
1384
                switch {
×
1385
                // A normal cipher seed was given, use the birthday encoded in
1386
                // it and create the wallet from that.
1387
                case cipherSeed != nil:
×
1388
                        birthday = cipherSeed.BirthdayTime()
×
1389
                        newWallet, err = loader.CreateNewWallet(
×
1390
                                password, password, cipherSeed.Entropy[:],
×
1391
                                birthday,
×
1392
                        )
×
1393

1394
                // No seed was given, we're importing a wallet from its extended
1395
                // private key.
1396
                case extendedKey != nil:
×
1397
                        birthday = initMsg.ExtendedKeyBirthday
×
1398
                        newWallet, err = loader.CreateNewWalletExtendedKey(
×
1399
                                password, password, extendedKey, birthday,
×
1400
                        )
×
1401

1402
                // Neither seed nor extended private key was given, so maybe the
1403
                // third option was chosen, the watch-only initialization. In
1404
                // this case we need to import each of the xpubs individually.
1405
                case watchOnlyAccounts != nil:
×
1406
                        if !cfg.RemoteSigner.Enable {
×
1407
                                return nil, fmt.Errorf("cannot initialize " +
×
1408
                                        "watch only wallet with remote " +
×
1409
                                        "signer config disabled")
×
1410
                        }
×
1411

1412
                        birthday = initMsg.WatchOnlyBirthday
×
1413
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
×
1414
                                password, birthday,
×
1415
                        )
×
1416
                        if err != nil {
×
1417
                                break
×
1418
                        }
1419

1420
                        err = importWatchOnlyAccounts(newWallet, initMsg)
×
1421

1422
                default:
×
1423
                        // The unlocker service made sure either the cipher seed
×
1424
                        // or the extended key is set so, we shouldn't get here.
×
1425
                        // The default case is just here for readability and
×
1426
                        // completeness.
×
1427
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1428
                                "nor extended key was given")
×
1429
                }
1430
                if err != nil {
×
1431
                        // Don't leave the file open in case the new wallet
×
1432
                        // could not be created for whatever reason.
×
1433
                        if err := loader.UnloadWallet(); err != nil {
×
1434
                                ltndLog.Errorf("Could not unload new "+
×
1435
                                        "wallet: %v", err)
×
1436
                        }
×
1437
                        return nil, err
×
1438
                }
1439

1440
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1441
                if cfg.ResetWalletTransactions {
×
1442
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
×
1443
                                "flag for new wallet as it has no effect")
×
1444
                }
×
1445

1446
                return &walletunlocker.WalletUnlockParams{
×
1447
                        Password:        password,
×
1448
                        Birthday:        birthday,
×
1449
                        RecoveryWindow:  recoveryWindow,
×
1450
                        Wallet:          newWallet,
×
1451
                        ChansToRestore:  initMsg.ChanBackups,
×
1452
                        UnloadWallet:    loader.UnloadWallet,
×
1453
                        StatelessInit:   initMsg.StatelessInit,
×
1454
                        MacResponseChan: pwService.MacResponseChan,
×
1455
                        MacRootKey:      initMsg.MacRootKey,
×
1456
                }, nil
×
1457

1458
        // The wallet has already been created in the past, and is simply being
1459
        // unlocked. So we'll just return these passphrases.
1460
        case unlockMsg := <-pwService.UnlockMsgs:
×
1461
                // Resetting the transactions is something the user likely only
×
1462
                // wants to do once so we add a prominent warning to the log to
×
1463
                // remind the user to turn off the setting again after
×
1464
                // successful completion.
×
1465
                if cfg.ResetWalletTransactions {
×
1466
                        ltndLog.Warnf("Dropped all transaction history from " +
×
1467
                                "on-chain wallet. Remember to disable " +
×
1468
                                "reset-wallet-transactions flag for next " +
×
1469
                                "start of lnd")
×
1470
                }
×
1471

1472
                return &walletunlocker.WalletUnlockParams{
×
1473
                        Password:        unlockMsg.Passphrase,
×
1474
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
×
1475
                        Wallet:          unlockMsg.Wallet,
×
1476
                        ChansToRestore:  unlockMsg.ChanBackups,
×
1477
                        UnloadWallet:    unlockMsg.UnloadWallet,
×
1478
                        StatelessInit:   unlockMsg.StatelessInit,
×
1479
                        MacResponseChan: pwService.MacResponseChan,
×
1480
                }, nil
×
1481

1482
        // If we got a shutdown signal we just return with an error immediately
1483
        case <-shutdownChan:
×
1484
                return nil, fmt.Errorf("shutting down")
×
1485
        }
1486
}
1487

1488
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1489
// which we created as watch-only.
1490
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1491
        initMsg *walletunlocker.WalletInitMsg) error {
×
1492

×
1493
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
×
1494
        for scope := range initMsg.WatchOnlyAccounts {
×
1495
                scopes = append(scopes, scope)
×
1496
        }
×
1497

1498
        // We need to import the accounts in the correct order, otherwise the
1499
        // indices will be incorrect.
1500
        sort.Slice(scopes, func(i, j int) bool {
×
1501
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
×
1502
                        scopes[i].Index < scopes[j].Index
×
1503
        })
×
1504

1505
        for _, scope := range scopes {
×
1506
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
×
1507

×
1508
                // We want witness pubkey hash by default, except for BIP49
×
1509
                // where we want mixed and BIP86 where we want taproot address
×
1510
                // formats.
×
1511
                switch scope.Scope.Purpose {
×
1512
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1513
                        waddrmgr.KeyScopeBIP0086.Purpose:
×
1514

×
1515
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
×
1516
                }
1517

1518
                // We want a human-readable account name. But for the default
1519
                // on-chain wallet we actually need to call it "default" to make
1520
                // sure everything works correctly.
1521
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
×
1522
                if scope.Index == 0 {
×
1523
                        name = "default"
×
1524
                }
×
1525

1526
                _, err := wallet.ImportAccountWithScope(
×
1527
                        name, initMsg.WatchOnlyAccounts[scope],
×
1528
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
×
1529
                        addrSchema,
×
1530
                )
×
1531
                if err != nil {
×
1532
                        return fmt.Errorf("could not import account %v: %w",
×
1533
                                name, err)
×
1534
                }
×
1535
        }
1536

1537
        return nil
×
1538
}
1539

1540
// handleNeutrinoPostgresDBMigration handles the migration of the neutrino db
1541
// to postgres. Initially we kept the neutrino db in the bolt db when running
1542
// with kvdb postgres backend. Now now move it to postgres as well. However we
1543
// need to make a distinction whether the user migrated the neutrino db to
1544
// postgres via lndinit or not. Currently if the db is not migrated we start
1545
// with a fresh db in postgres.
1546
//
1547
// TODO(ziggie): Also migrate the db to postgres in case it is still not
1548
// migrated ?
1549
func handleNeutrinoPostgresDBMigration(dbName, dbPath string,
1550
        cfg *Config) error {
×
1551

×
1552
        if !lnrpc.FileExists(dbName) {
×
1553
                return nil
×
1554
        }
×
1555

1556
        // Open bolt db to check if it is tombstoned. If it is we assume that
1557
        // the neutrino db was successfully migrated to postgres. We open it
1558
        // in read-only mode to avoid long db open times.
1559
        boltDB, err := kvdb.Open(
×
1560
                kvdb.BoltBackendName, dbName, true,
×
1561
                cfg.DB.Bolt.DBTimeout, true,
×
1562
        )
×
1563
        if err != nil {
×
1564
                return fmt.Errorf("failed to open bolt db: %w", err)
×
1565
        }
×
1566
        defer boltDB.Close()
×
1567

×
1568
        isTombstoned := false
×
1569
        err = boltDB.View(func(tx kvdb.RTx) error {
×
1570
                _, err = channeldb.CheckMarkerPresent(
×
1571
                        tx, channeldb.TombstoneKey,
×
1572
                )
×
1573

×
1574
                return err
×
1575
        }, func() {})
×
1576
        if err == nil {
×
1577
                isTombstoned = true
×
1578
        }
×
1579

1580
        if isTombstoned {
×
1581
                ltndLog.Infof("Neutrino Bolt DB is tombstoned, assuming " +
×
1582
                        "database was successfully migrated to postgres")
×
1583

×
1584
                return nil
×
1585
        }
×
1586

1587
        // If the db is not tombstoned, we remove the files and start fresh with
1588
        // postgres. This is the case when a user was running lnd with the
1589
        // postgres backend from the beginning without migrating from bolt.
1590
        ltndLog.Infof("Neutrino Bolt DB found but NOT tombstoned, removing " +
×
1591
                "it and starting fresh with postgres")
×
1592

×
1593
        filesToRemove := []string{
×
1594
                filepath.Join(dbPath, "block_headers.bin"),
×
1595
                filepath.Join(dbPath, "reg_filter_headers.bin"),
×
1596
                dbName,
×
1597
        }
×
1598

×
1599
        for _, file := range filesToRemove {
×
1600
                if err := os.Remove(file); err != nil {
×
1601
                        ltndLog.Warnf("Could not remove %s: %v", file, err)
×
1602
                }
×
1603
        }
1604

1605
        return nil
×
1606
}
1607

1608
// initNeutrinoBackend inits a new instance of the neutrino light client
1609
// backend given a target chain directory to store the chain state.
1610
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1611
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
1612
        func(), error) {
×
1613

×
1614
        // Both channel validation flags are false by default but their meaning
×
1615
        // is the inverse of each other. Therefore both cannot be true. For
×
1616
        // every other case, the neutrino.validatechannels overwrites the
×
1617
        // routing.assumechanvalid value.
×
1618
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
×
1619
                return nil, nil, fmt.Errorf("can't set both " +
×
1620
                        "neutrino.validatechannels and routing." +
×
1621
                        "assumechanvalid to true at the same time")
×
1622
        }
×
1623
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
1624

×
1625
        // First we'll open the database file for neutrino, creating the
×
1626
        // database if needed. We append the normalized network name here to
×
1627
        // match the behavior of btcwallet.
×
1628
        dbPath := filepath.Join(
×
1629
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
1630
        )
×
1631

×
1632
        // Ensure that the neutrino db path exists.
×
1633
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1634
                return nil, nil, err
×
1635
        }
×
1636

1637
        var (
×
1638
                db  walletdb.DB
×
1639
                err error
×
1640
        )
×
1641
        switch {
×
1642
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1643
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1644
                db, err = kvdb.Open(
×
1645
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1646
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1647
                )
×
1648

1649
        case cfg.DB.Backend == kvdb.PostgresBackendName:
×
1650
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
1651

×
1652
                // This code needs to be in place because we did not start
×
1653
                // the postgres backend for neutrino at the beginning. Now we
×
1654
                // are also moving it into the postgres backend so we can phase
×
1655
                // out the bolt backend.
×
1656
                err = handleNeutrinoPostgresDBMigration(dbName, dbPath, cfg)
×
1657
                if err != nil {
×
1658
                        return nil, nil, err
×
1659
                }
×
1660

1661
                postgresConfig := lncfg.GetPostgresConfigKVDB(cfg.DB.Postgres)
×
1662
                db, err = kvdb.Open(
×
1663
                        kvdb.PostgresBackendName, ctx, postgresConfig,
×
1664
                        lncfg.NSNeutrinoDB,
×
1665
                )
×
1666

1667
        default:
×
1668
                dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
×
1669
                db, err = walletdb.Create(
×
1670
                        kvdb.BoltBackendName, dbName, !cfg.SyncFreelist,
×
1671
                        cfg.DB.Bolt.DBTimeout, false,
×
1672
                )
×
1673
        }
1674
        if err != nil {
×
1675
                return nil, nil, fmt.Errorf("unable to create "+
×
1676
                        "neutrino database: %v", err)
×
1677
        }
×
1678

1679
        headerStateAssertion, err := parseHeaderStateAssertion(
×
1680
                cfg.NeutrinoMode.AssertFilterHeader,
×
1681
        )
×
1682
        if err != nil {
×
1683
                db.Close()
×
1684
                return nil, nil, err
×
1685
        }
×
1686

1687
        // With the database open, we can now create an instance of the
1688
        // neutrino light client. We pass in relevant configuration parameters
1689
        // required.
1690
        config := neutrino.Config{
×
1691
                DataDir:      dbPath,
×
1692
                Database:     db,
×
1693
                ChainParams:  *cfg.ActiveNetParams.Params,
×
1694
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
1695
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
1696
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
1697
                        return cfg.net.Dial(
×
1698
                                addr.Network(), addr.String(),
×
1699
                                cfg.ConnectionTimeout,
×
1700
                        )
×
1701
                },
×
1702
                NameResolver: func(host string) ([]net.IP, error) {
×
1703
                        addrs, err := cfg.net.LookupHost(host)
×
1704
                        if err != nil {
×
1705
                                return nil, err
×
1706
                        }
×
1707

1708
                        ips := make([]net.IP, 0, len(addrs))
×
1709
                        for _, strIP := range addrs {
×
1710
                                ip := net.ParseIP(strIP)
×
1711
                                if ip == nil {
×
1712
                                        continue
×
1713
                                }
1714

1715
                                ips = append(ips, ip)
×
1716
                        }
1717

1718
                        return ips, nil
×
1719
                },
1720
                AssertFilterHeader: headerStateAssertion,
1721
                BlockCache:         blockCache.Cache,
1722
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1723
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1724
        }
1725

1726
        if cfg.NeutrinoMode.MaxPeers <= 0 {
×
1727
                return nil, nil, fmt.Errorf("a non-zero number must be set " +
×
1728
                        "for neutrino max peers")
×
1729
        }
×
1730
        neutrino.MaxPeers = cfg.NeutrinoMode.MaxPeers
×
1731
        neutrino.BanDuration = time.Hour * 48
×
1732
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
×
1733
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
×
1734

×
1735
        neutrinoCS, err := neutrino.NewChainService(config)
×
1736
        if err != nil {
×
1737
                db.Close()
×
1738
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1739
                        "client: %v", err)
×
1740
        }
×
1741

1742
        if err := neutrinoCS.Start(); err != nil {
×
1743
                db.Close()
×
1744
                return nil, nil, err
×
1745
        }
×
1746

1747
        cleanUp := func() {
×
1748
                if err := neutrinoCS.Stop(); err != nil {
×
1749
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1750
                                "%v", err)
×
1751
                }
×
1752
                db.Close()
×
1753
        }
1754

1755
        return neutrinoCS, cleanUp, nil
×
1756
}
1757

1758
// parseHeaderStateAssertion parses the user-specified neutrino header state
1759
// into a headerfs.FilterHeader.
1760
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
1761
        if len(state) == 0 {
×
1762
                return nil, nil
×
1763
        }
×
1764

1765
        split := strings.Split(state, ":")
×
1766
        if len(split) != 2 {
×
1767
                return nil, fmt.Errorf("header state assertion %v in "+
×
1768
                        "unexpected format, expected format height:hash", state)
×
1769
        }
×
1770

1771
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1772
        if err != nil {
×
1773
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1774
        }
×
1775

1776
        hash, err := chainhash.NewHashFromStr(split[1])
×
1777
        if err != nil {
×
1778
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1779
        }
×
1780

1781
        return &headerfs.FilterHeader{
×
1782
                Height:     uint32(height),
×
1783
                FilterHash: *hash,
×
1784
        }, nil
×
1785
}
1786

1787
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1788
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1789
// resides in the neutrino package to use all of its functionalities.
1790
func broadcastErrorMapper(err error) error {
×
1791
        var returnErr error
×
1792

×
1793
        // We only filter for specific backend errors which are relevant for the
×
1794
        // Rebroadcaster.
×
1795
        switch {
×
1796
        // This makes sure the tx is removed from the rebroadcaster once it is
1797
        // confirmed.
1798
        case errors.Is(err, chain.ErrTxAlreadyKnown),
1799
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
×
1800

×
1801
                returnErr = &pushtx.BroadcastError{
×
1802
                        Code:   pushtx.Confirmed,
×
1803
                        Reason: err.Error(),
×
1804
                }
×
1805

1806
        // Transactions which are still in mempool but might fall out because
1807
        // of low fees are rebroadcasted despite of their backend error.
1808
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1809
                returnErr = &pushtx.BroadcastError{
×
1810
                        Code:   pushtx.Mempool,
×
1811
                        Reason: err.Error(),
×
1812
                }
×
1813

1814
        // Transactions which are not accepted into mempool because of low fees
1815
        // in the first place are rebroadcasted despite of their backend error.
1816
        // Mempool conditions change over time so it makes sense to retry
1817
        // publishing the transaction. Moreover we log the detailed error so the
1818
        // user can intervene and increase the size of his mempool or increase
1819
        // his min relay fee configuration.
1820
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet),
1821
                errors.Is(err, chain.ErrMinRelayFeeNotMet):
×
1822

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

×
1825
                returnErr = &pushtx.BroadcastError{
×
1826
                        Code:   pushtx.Mempool,
×
1827
                        Reason: err.Error(),
×
1828
                }
×
1829
        }
1830

1831
        return returnErr
×
1832
}
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