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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

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

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

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

84
// ExternalValidator is an interface that must be satisfied by an external
85
// macaroon validator.
86
type ExternalValidator interface {
87
        macaroons.MacaroonValidator
88

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

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

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

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

126
// ImplementationCfg is a struct that holds all configuration items for
127
// components that can be implemented outside lnd itself.
128
type ImplementationCfg struct {
129
        // GrpcRegistrar is a type that can register additional gRPC subservers
130
        // before the main gRPC server is started.
131
        GrpcRegistrar
132

133
        // RestRegistrar is a type that can register additional REST subservers
134
        // before the main REST proxy is started.
135
        RestRegistrar
136

137
        // ExternalValidator is a type that can provide external macaroon
138
        // validation.
139
        ExternalValidator
140

141
        // DatabaseBuilder is a type that can provide lnd's main database
142
        // backend instances.
143
        DatabaseBuilder
144

145
        // WalletConfigBuilder is a type that can provide a wallet configuration
146
        // with a fully loaded and unlocked wallet.
147
        WalletConfigBuilder
148

149
        // ChainControlBuilder is a type that can provide a custom wallet
150
        // implementation.
151
        ChainControlBuilder
152

153
        // AuxComponents is a set of auxiliary components that can be used by
154
        // lnd for certain custom channel types.
155
        AuxComponents
156
}
157

158
// AuxComponents is a set of auxiliary components that can be used by lnd for
159
// certain custom channel types.
160
type AuxComponents struct {
161
        // AuxLeafStore is an optional data source that can be used by custom
162
        // channels to fetch+store various data.
163
        AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
164

165
        // TrafficShaper is an optional traffic shaper that can be used to
166
        // control the outgoing channel of a payment.
167
        TrafficShaper fn.Option[routing.TlvTrafficShaper]
168

169
        // MsgRouter is an optional message router that if set will be used in
170
        // place of a new blank default message router.
171
        MsgRouter fn.Option[msgmux.Router]
172

173
        // AuxFundingController is an optional controller that can be used to
174
        // modify the way we handle certain custom channel types. It's also
175
        // able to automatically handle new custom protocol messages related to
176
        // the funding process.
177
        AuxFundingController fn.Option[funding.AuxFundingController]
178

179
        // AuxSigner is an optional signer that can be used to sign auxiliary
180
        // leaves for certain custom channel types.
181
        AuxSigner fn.Option[lnwallet.AuxSigner]
182

183
        // AuxDataParser is an optional data parser that can be used to parse
184
        // auxiliary data for certain custom channel types.
185
        AuxDataParser fn.Option[AuxDataParser]
186

187
        // AuxChanCloser is an optional channel closer that can be used to
188
        // modify the way a coop-close transaction is constructed.
189
        AuxChanCloser fn.Option[chancloser.AuxChanCloser]
190
}
191

192
// DefaultWalletImpl is the default implementation of our normal, btcwallet
193
// backed configuration.
194
type DefaultWalletImpl struct {
195
        cfg         *Config
196
        logger      btclog.Logger
197
        interceptor signal.Interceptor
198

199
        watchOnly        bool
200
        migrateWatchOnly bool
201
        pwService        *walletunlocker.UnlockerService
202
}
203

204
// NewDefaultWalletImpl creates a new default wallet implementation.
205
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
206
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
207

208
        return &DefaultWalletImpl{
209
                cfg:         cfg,
210
                logger:      logger,
211
                interceptor: interceptor,
212
                watchOnly:   watchOnly,
213
                pwService:   createWalletUnlockerService(cfg),
214
        }
UNCOV
215
}
×
UNCOV
216

×
UNCOV
217
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
×
UNCOV
218
// instance. External subservers implementing this method can then register
×
UNCOV
219
// their own REST proxy stubs to the main server instance.
×
UNCOV
220
//
×
UNCOV
221
// NOTE: This is part of the GrpcRegistrar interface.
×
UNCOV
222
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
×
UNCOV
223
        mux *proxy.ServeMux, restProxyDest string,
×
UNCOV
224
        restDialOpts []grpc.DialOption) error {
×
225

226
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
227
                ctx, mux, restProxyDest, restDialOpts,
228
        )
229
}
230

231
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
232
// grpc.Server instance. External subservers implementing this method can then
UNCOV
233
// register their own gRPC server structs to the main server instance.
×
UNCOV
234
//
×
UNCOV
235
// NOTE: This is part of the GrpcRegistrar interface.
×
UNCOV
236
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
×
UNCOV
237
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
×
UNCOV
238

×
239
        return nil
240
}
241

242
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
243
// checks its signature, makes sure all specified permissions for the called
244
// method are contained within and finally ensures all caveat conditions are
UNCOV
245
// met. A non-nil error is returned if any of the checks fail.
×
UNCOV
246
//
×
UNCOV
247
// NOTE: This is part of the ExternalValidator interface.
×
UNCOV
248
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
×
249
        requiredPermissions []bakery.Op, fullMethod string) error {
×
250

251
        // Because the default implementation does not return any permissions,
252
        // we shouldn't be registered as an external validator at all and this
253
        // should never be invoked.
254
        return fmt.Errorf("default implementation does not support external " +
255
                "macaroon validation")
256
}
257

UNCOV
258
// Permissions returns the permissions that the external validator is
×
UNCOV
259
// validating. It is a map between the full HTTP URI of each RPC and its
×
UNCOV
260
// required macaroon permissions. If multiple action/entity tuples are specified
×
UNCOV
261
// per URI, they are all required. See rpcserver.go for a list of valid action
×
UNCOV
262
// and entity values.
×
UNCOV
263
//
×
UNCOV
264
// NOTE: This is part of the ExternalValidator interface.
×
UNCOV
265
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
×
266
        return nil
267
}
268

269
// BuildWalletConfig is responsible for creating or unlocking and then
270
// fully initializing a wallet.
271
//
272
// NOTE: This is part of the WalletConfigBuilder interface.
273
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
UNCOV
274
        dbs *DatabaseInstances, aux *AuxComponents,
×
UNCOV
275
        interceptorChain *rpcperms.InterceptorChain,
×
UNCOV
276
        grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
×
277
        *btcwallet.Config, func(), error) {
278

279
        // Keep track of our various cleanup functions. We use a defer function
280
        // as well to not repeat ourselves with every return statement.
281
        var (
282
                cleanUpTasks []func()
283
                earlyExit    = true
284
                cleanUp      = func() {
285
                        for _, fn := range cleanUpTasks {
UNCOV
286
                                if fn == nil {
×
287
                                        continue
×
UNCOV
288
                                }
×
UNCOV
289

×
UNCOV
290
                                fn()
×
UNCOV
291
                        }
×
UNCOV
292
                }
×
UNCOV
293
        )
×
UNCOV
294
        defer func() {
×
UNCOV
295
                if earlyExit {
×
296
                        cleanUp()
×
297
                }
298
        }()
UNCOV
299

×
300
        // Initialize a new block cache.
301
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
302

UNCOV
303
        // Before starting the wallet, we'll create and start our Neutrino
×
UNCOV
304
        // light client instance, if enabled, in order to allow it to sync
×
UNCOV
305
        // while the rest of the daemon continues startup.
×
UNCOV
306
        mainChain := d.cfg.Bitcoin
×
307
        var neutrinoCS *neutrino.ChainService
308
        if mainChain.Node == "neutrino" {
309
                neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend(
UNCOV
310
                        ctx, d.cfg, mainChain.ChainDir, blockCache,
×
UNCOV
311
                )
×
UNCOV
312
                if err != nil {
×
313
                        err := fmt.Errorf("unable to initialize neutrino "+
×
314
                                "backend: %v", err)
×
315
                        d.logger.Error(err)
×
316
                        return nil, nil, nil, err
×
317
                }
×
UNCOV
318
                cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp)
×
UNCOV
319
                neutrinoCS = neutrinoBackend
×
UNCOV
320
        }
×
UNCOV
321

×
UNCOV
322
        var (
×
UNCOV
323
                walletInitParams = walletunlocker.WalletUnlockParams{
×
UNCOV
324
                        // In case we do auto-unlock, we need to be able to send
×
UNCOV
325
                        // into the channel without blocking so we buffer it.
×
UNCOV
326
                        MacResponseChan: make(chan []byte, 1),
×
UNCOV
327
                }
×
UNCOV
328
                privateWalletPw = lnwallet.DefaultPrivatePassphrase
×
329
                publicWalletPw  = lnwallet.DefaultPublicPassphrase
330
        )
UNCOV
331

×
UNCOV
332
        // If the user didn't request a seed, then we'll manually assume a
×
UNCOV
333
        // wallet birthday of now, as otherwise the seed would've specified
×
UNCOV
334
        // this information.
×
UNCOV
335
        walletInitParams.Birthday = time.Now()
×
UNCOV
336

×
UNCOV
337
        d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
×
UNCOV
338
        d.pwService.SetMacaroonDB(dbs.MacaroonDB)
×
UNCOV
339
        walletExists, err := d.pwService.WalletExists()
×
UNCOV
340
        if err != nil {
×
341
                return nil, nil, nil, err
×
342
        }
×
UNCOV
343

×
UNCOV
344
        if !walletExists {
×
UNCOV
345
                interceptorChain.SetWalletNotCreated()
×
UNCOV
346
        } else {
×
UNCOV
347
                interceptorChain.SetWalletLocked()
×
UNCOV
348
        }
×
UNCOV
349

×
UNCOV
350
        // If we've started in auto unlock mode, then a wallet should already
×
UNCOV
351
        // exist because we don't want to enable the RPC unlocker in that case
×
352
        // for security reasons (an attacker could inject their seed since the
UNCOV
353
        // RPC is unauthenticated). Only if the user explicitly wants to allow
×
UNCOV
354
        // wallet creation we don't error out here.
×
UNCOV
355
        if d.cfg.WalletUnlockPasswordFile != "" && !walletExists &&
×
UNCOV
356
                !d.cfg.WalletUnlockAllowCreate {
×
357

×
358
                return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
359
                        "was specified but wallet does not exist; initialize " +
360
                        "the wallet before using auto unlocking")
361
        }
362

363
        // What wallet mode are we running in? We've already made sure the no
UNCOV
364
        // seed backup and auto unlock aren't both set during config parsing.
×
UNCOV
365
        switch {
×
UNCOV
366
        // No seed backup means we're also using the default password.
×
UNCOV
367
        case d.cfg.NoSeedBackup:
×
UNCOV
368
                // We continue normally, the default password has already been
×
UNCOV
369
                // set above.
×
UNCOV
370

×
371
        // A password for unlocking is provided in a file.
372
        case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
373
                d.logger.Infof("Attempting automatic wallet unlock with " +
374
                        "password provided in file")
×
375
                pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
376
                if err != nil {
×
377
                        return nil, nil, nil, fmt.Errorf("error reading "+
378
                                "password from file %s: %v",
379
                                d.cfg.WalletUnlockPasswordFile, err)
380
                }
UNCOV
381

×
UNCOV
382
                // Remove any newlines at the end of the file. The lndinit tool
×
UNCOV
383
                // won't ever write a newline but maybe the file was provisioned
×
UNCOV
384
                // by another process or user.
×
385
                pwBytes = bytes.TrimRight(pwBytes, "\r\n")
×
386

×
387
                // We have the password now, we can ask the unlocker service to
×
388
                // do the unlock for us.
×
389
                unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
×
390
                        pwBytes, 0,
391
                )
392
                if err != nil {
393
                        return nil, nil, nil, fmt.Errorf("error unlocking "+
394
                                "wallet with password from file: %v", err)
×
395
                }
×
UNCOV
396

×
397
                cleanUpTasks = append(cleanUpTasks, func() {
×
398
                        if err := unloadWalletFn(); err != nil {
×
399
                                d.logger.Errorf("Could not unload wallet: %v",
×
400
                                        err)
×
401
                        }
×
UNCOV
402
                })
×
UNCOV
403

×
404
                privateWalletPw = pwBytes
×
405
                publicWalletPw = pwBytes
406
                walletInitParams.Wallet = unlockedWallet
×
407
                walletInitParams.UnloadWallet = unloadWalletFn
×
UNCOV
408

×
UNCOV
409
        // If none of the automatic startup options are selected, we fall back
×
UNCOV
410
        // to the default behavior of waiting for the wallet creation/unlocking
×
411
        // over RPC.
412
        default:
UNCOV
413
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
×
414
                        return nil, nil, nil, err
×
415
                }
×
UNCOV
416

×
417
                params, err := waitForWalletPassword(
418
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
419
                        d.interceptor.ShutdownChannel(),
420
                )
UNCOV
421
                if err != nil {
×
422
                        err := fmt.Errorf("unable to set up wallet password "+
×
423
                                "listeners: %v", err)
×
424
                        d.logger.Error(err)
×
425
                        return nil, nil, nil, err
426
                }
×
UNCOV
427

×
UNCOV
428
                walletInitParams = *params
×
UNCOV
429
                privateWalletPw = walletInitParams.Password
×
UNCOV
430
                publicWalletPw = walletInitParams.Password
×
UNCOV
431
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
432
                        if err := walletInitParams.UnloadWallet(); err != nil {
×
433
                                d.logger.Errorf("Could not unload wallet: %v",
×
434
                                        err)
×
435
                        }
×
436
                })
UNCOV
437

×
UNCOV
438
                if walletInitParams.RecoveryWindow > 0 {
×
UNCOV
439
                        d.logger.Infof("Wallet recovery mode enabled with "+
×
UNCOV
440
                                "address lookahead of %d addresses",
×
UNCOV
441
                                walletInitParams.RecoveryWindow)
×
UNCOV
442
                }
×
UNCOV
443
        }
×
UNCOV
444

×
445
        var macaroonService *macaroons.Service
446
        if !d.cfg.NoMacaroons {
UNCOV
447
                // Create the macaroon authentication/authorization service.
×
UNCOV
448
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
×
UNCOV
449
                if err != nil {
×
450
                        return nil, nil, nil, err
×
451
                }
×
452
                macaroonService, err = macaroons.NewService(
453
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
UNCOV
454
                        macaroons.IPLockChecker,
×
UNCOV
455
                        macaroons.CustomChecker(interceptorChain),
×
UNCOV
456
                )
×
UNCOV
457
                if err != nil {
×
458
                        err := fmt.Errorf("unable to set up macaroon "+
×
459
                                "authentication: %v", err)
×
460
                        d.logger.Error(err)
×
461
                        return nil, nil, nil, err
×
462
                }
×
UNCOV
463
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
464
                        if err := macaroonService.Close(); err != nil {
×
465
                                d.logger.Errorf("Could not close macaroon "+
×
466
                                        "service: %v", err)
×
467
                        }
×
UNCOV
468
                })
×
UNCOV
469

×
UNCOV
470
                // Try to unlock the macaroon store with the private password.
×
UNCOV
471
                // Ignore ErrAlreadyUnlocked since it could be unlocked by the
×
UNCOV
472
                // wallet unlocker.
×
UNCOV
473
                err = macaroonService.CreateUnlock(&privateWalletPw)
×
UNCOV
474
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
×
475
                        err := fmt.Errorf("unable to unlock macaroons: %w", err)
×
476
                        d.logger.Error(err)
×
477
                        return nil, nil, nil, err
478
                }
479

480
                // If we have a macaroon root key from the init wallet params,
481
                // set the root key before baking any macaroons.
UNCOV
482
                if len(walletInitParams.MacRootKey) > 0 {
×
483
                        err := macaroonService.SetRootKey(
×
484
                                walletInitParams.MacRootKey,
×
485
                        )
×
486
                        if err != nil {
×
487
                                return nil, nil, nil, err
×
488
                        }
489
                }
490

UNCOV
491
                // Send an admin macaroon to all our listeners that requested
×
UNCOV
492
                // one by setting a non-nil macaroon channel.
×
UNCOV
493
                adminMacBytes, err := bakeMacaroon(
×
UNCOV
494
                        ctx, macaroonService, adminPermissions(),
×
UNCOV
495
                )
×
UNCOV
496
                if err != nil {
×
497
                        return nil, nil, nil, err
×
498
                }
499
                for _, lis := range grpcListeners {
500
                        if lis.MacChan != nil {
501
                                lis.MacChan <- adminMacBytes
502
                        }
×
UNCOV
503
                }
×
UNCOV
504

×
UNCOV
505
                // In case we actually needed to unlock the wallet, we now need
×
UNCOV
506
                // to create an instance of the admin macaroon and send it to
×
UNCOV
507
                // the unlocker so it can forward it to the user. In no seed
×
UNCOV
508
                // backup mode, there's nobody listening on the channel and we'd
×
UNCOV
509
                // block here forever.
×
UNCOV
510
                if !d.cfg.NoSeedBackup {
×
UNCOV
511
                        // The channel is buffered by one element so writing
×
512
                        // should not block here.
513
                        walletInitParams.MacResponseChan <- adminMacBytes
514
                }
515

516
                // If the user requested a stateless initialization, no macaroon
517
                // files should be created.
518
                if !walletInitParams.StatelessInit {
UNCOV
519
                        // Create default macaroon files for lncli to use if
×
UNCOV
520
                        // they don't exist.
×
UNCOV
521
                        err = genDefaultMacaroons(
×
UNCOV
522
                                ctx, macaroonService, d.cfg.AdminMacPath,
×
UNCOV
523
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
×
524
                        )
525
                        if err != nil {
526
                                err := fmt.Errorf("unable to create macaroons "+
527
                                        "%v", err)
×
528
                                d.logger.Error(err)
×
529
                                return nil, nil, nil, err
×
530
                        }
×
UNCOV
531
                }
×
UNCOV
532

×
UNCOV
533
                // As a security service to the user, if they requested
×
UNCOV
534
                // stateless initialization and there are macaroon files on disk
×
UNCOV
535
                // we log a warning.
×
UNCOV
536
                if walletInitParams.StatelessInit {
×
UNCOV
537
                        msg := "Found %s macaroon on disk (%s) even though " +
×
UNCOV
538
                                "--stateless_init was requested. Unencrypted " +
×
UNCOV
539
                                "state is accessible by the host system. You " +
×
540
                                "should change the password and use " +
541
                                "--new_mac_root_key with --stateless_init to " +
542
                                "clean up and invalidate old macaroons."
543

544
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
545
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
546
                        }
×
UNCOV
547
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
×
548
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
549
                        }
×
UNCOV
550
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
×
551
                                d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
×
552
                        }
×
UNCOV
553
                }
×
UNCOV
554

×
UNCOV
555
                // We add the macaroon service to our RPC interceptor. This
×
UNCOV
556
                // will start checking macaroons against permissions on every
×
UNCOV
557
                // RPC invocation.
×
UNCOV
558
                interceptorChain.AddMacaroonService(macaroonService)
×
UNCOV
559
        }
×
UNCOV
560

×
UNCOV
561
        // Now that the wallet password has been provided, transition the RPC
×
562
        // state into Unlocked.
563
        interceptorChain.SetWalletUnlocked()
564

565
        // Since calls to the WalletUnlocker service wait for a response on the
566
        // macaroon channel, we close it here to make sure they return in case
UNCOV
567
        // we did not return the admin macaroon above. This will be the case if
×
568
        // --no-macaroons is used.
569
        close(walletInitParams.MacResponseChan)
570

571
        // We'll also close all the macaroon channels since lnd is done sending
UNCOV
572
        // macaroon data over it.
×
UNCOV
573
        for _, lis := range grpcListeners {
×
UNCOV
574
                if lis.MacChan != nil {
×
575
                        close(lis.MacChan)
×
576
                }
×
UNCOV
577
        }
×
UNCOV
578

×
UNCOV
579
        // With the information parsed from the configuration, create valid
×
UNCOV
580
        // instances of the pertinent interfaces required to operate the
×
UNCOV
581
        // Lightning Network Daemon.
×
UNCOV
582
        //
×
UNCOV
583
        // When we create the chain control, we need storage for the height
×
UNCOV
584
        // hints and also the wallet itself, for these two we want them to be
×
UNCOV
585
        // replicated, so we'll pass in the remote channel DB instance.
×
586
        chainControlCfg := &chainreg.Config{
587
                Bitcoin:                     d.cfg.Bitcoin,
588
                HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable,
589
                NeutrinoMode:                d.cfg.NeutrinoMode,
590
                BitcoindMode:                d.cfg.BitcoindMode,
591
                BtcdMode:                    d.cfg.BtcdMode,
592
                HeightHintDB:                dbs.HeightHintDB,
593
                ChanStateDB:                 dbs.ChanStateDB.ChannelStateDB(),
594
                NeutrinoCS:                  neutrinoCS,
UNCOV
595
                AuxLeafStore:                aux.AuxLeafStore,
×
UNCOV
596
                AuxSigner:                   aux.AuxSigner,
×
UNCOV
597
                ActiveNetParams:             d.cfg.ActiveNetParams,
×
UNCOV
598
                FeeURL:                      d.cfg.FeeURL,
×
UNCOV
599
                Fee: &lncfg.Fee{
×
UNCOV
600
                        URL:              d.cfg.Fee.URL,
×
UNCOV
601
                        MinUpdateTimeout: d.cfg.Fee.MinUpdateTimeout,
×
UNCOV
602
                        MaxUpdateTimeout: d.cfg.Fee.MaxUpdateTimeout,
×
UNCOV
603
                },
×
UNCOV
604
                Dialer: func(addr string) (net.Conn, error) {
×
605
                        return d.cfg.net.Dial(
×
606
                                "tcp", addr, d.cfg.ConnectionTimeout,
×
607
                        )
×
608
                },
×
UNCOV
609
                BlockCache:         blockCache,
×
UNCOV
610
                WalletUnlockParams: &walletInitParams,
×
UNCOV
611
        }
×
UNCOV
612

×
UNCOV
613
        // Let's go ahead and create the partial chain control now that is only
×
UNCOV
614
        // dependent on our configuration and doesn't require any wallet
×
UNCOV
615
        // specific information.
×
UNCOV
616
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
×
UNCOV
617
                chainControlCfg,
×
618
        )
619
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
620
        if err != nil {
621
                err := fmt.Errorf("unable to create partial chain control: %w",
622
                        err)
623
                d.logger.Error(err)
624
                return nil, nil, nil, err
625
        }
×
UNCOV
626

×
UNCOV
627
        walletConfig := &btcwallet.Config{
×
UNCOV
628
                PrivatePass:      privateWalletPw,
×
UNCOV
629
                PublicPass:       publicWalletPw,
×
UNCOV
630
                Birthday:         walletInitParams.Birthday,
×
UNCOV
631
                RecoveryWindow:   walletInitParams.RecoveryWindow,
×
UNCOV
632
                NetParams:        d.cfg.ActiveNetParams.Params,
×
UNCOV
633
                CoinType:         d.cfg.ActiveNetParams.CoinType,
×
UNCOV
634
                Wallet:           walletInitParams.Wallet,
×
635
                LoaderOptions:    []btcwallet.LoaderOption{dbs.WalletDB},
UNCOV
636
                ChainSource:      partialChainControl.ChainSource,
×
UNCOV
637
                WatchOnly:        d.watchOnly,
×
UNCOV
638
                MigrateWatchOnly: d.migrateWatchOnly,
×
UNCOV
639
        }
×
UNCOV
640

×
UNCOV
641
        // Parse coin selection strategy.
×
UNCOV
642
        switch d.cfg.CoinSelectionStrategy {
×
UNCOV
643
        case "largest":
×
UNCOV
644
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
×
UNCOV
645

×
646
        case "random":
×
647
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
UNCOV
648

×
649
        default:
×
650
                return nil, nil, nil, fmt.Errorf("unknown coin selection "+
×
651
                        "strategy %v", d.cfg.CoinSelectionStrategy)
×
UNCOV
652
        }
×
UNCOV
653

×
654
        earlyExit = false
UNCOV
655
        return partialChainControl, walletConfig, cleanUp, nil
×
UNCOV
656
}
×
657

UNCOV
658
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
×
UNCOV
659
// rebroadcaster client.
×
UNCOV
660
func proxyBlockEpoch(
×
661
        notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
662
        error) {
UNCOV
663

×
UNCOV
664
        return func() (*blockntfns.Subscription, error) {
×
665
                blockEpoch, err := notifier.RegisterBlockEpochNtfn(
666
                        nil,
667
                )
668
                if err != nil {
669
                        return nil, err
670
                }
UNCOV
671

×
UNCOV
672
                sub := blockntfns.Subscription{
×
UNCOV
673
                        Notifications: make(chan blockntfns.BlockNtfn, 6),
×
UNCOV
674
                        Cancel:        blockEpoch.Cancel,
×
UNCOV
675
                }
×
UNCOV
676
                go func() {
×
UNCOV
677
                        for blk := range blockEpoch.Epochs {
×
UNCOV
678
                                ntfn := blockntfns.NewBlockConnected(
×
UNCOV
679
                                        *blk.BlockHeader,
×
680
                                        uint32(blk.Height),
UNCOV
681
                                )
×
UNCOV
682

×
UNCOV
683
                                sub.Notifications <- ntfn
×
UNCOV
684
                        }
×
UNCOV
685
                }()
×
UNCOV
686

×
UNCOV
687
                return &sub, nil
×
UNCOV
688
        }
×
UNCOV
689
}
×
UNCOV
690

×
UNCOV
691
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
×
UNCOV
692
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
×
UNCOV
693
type walletReBroadcaster struct {
×
694
        started atomic.Bool
695

UNCOV
696
        *pushtx.Broadcaster
×
697
}
698

699
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
700
func newWalletReBroadcaster(
701
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
702

703
        return &walletReBroadcaster{
704
                Broadcaster: broadcaster,
705
        }
706
}
707

708
// Start launches all goroutines the rebroadcaster needs to operate.
709
func (w *walletReBroadcaster) Start() error {
UNCOV
710
        defer w.started.Store(true)
×
UNCOV
711

×
UNCOV
712
        return w.Broadcaster.Start()
×
UNCOV
713
}
×
UNCOV
714

×
UNCOV
715
// Started returns true if the broadcaster is already active.
×
716
func (w *walletReBroadcaster) Started() bool {
717
        return w.started.Load()
UNCOV
718
}
×
UNCOV
719

×
UNCOV
720
// BuildChainControl is responsible for creating a fully populated chain
×
UNCOV
721
// control instance from a wallet.
×
UNCOV
722
//
×
723
// NOTE: This is part of the ChainControlBuilder interface.
724
func (d *DefaultWalletImpl) BuildChainControl(
UNCOV
725
        partialChainControl *chainreg.PartialChainControl,
×
UNCOV
726
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
UNCOV
727

×
728
        walletController, err := btcwallet.New(
729
                *walletConfig, partialChainControl.Cfg.BlockCache,
730
        )
731
        if err != nil {
732
                err := fmt.Errorf("unable to create wallet controller: %w", err)
733
                d.logger.Error(err)
734
                return nil, nil, err
735
        }
×
UNCOV
736

×
UNCOV
737
        keyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
738
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
739
        )
×
UNCOV
740

×
UNCOV
741
        // Create, and start the lnwallet, which handles the core payment
×
UNCOV
742
        // channel logic, and exposes control via proxy state machines.
×
UNCOV
743
        lnWalletConfig := lnwallet.Config{
×
UNCOV
744
                Database:              partialChainControl.Cfg.ChanStateDB,
×
745
                Notifier:              partialChainControl.ChainNotifier,
UNCOV
746
                WalletController:      walletController,
×
UNCOV
747
                Signer:                walletController,
×
UNCOV
748
                FeeEstimator:          partialChainControl.FeeEstimator,
×
UNCOV
749
                SecretKeyRing:         keyRing,
×
UNCOV
750
                ChainIO:               walletController,
×
UNCOV
751
                NetParams:             *walletConfig.NetParams,
×
UNCOV
752
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
753
                AuxLeafStore:          partialChainControl.Cfg.AuxLeafStore,
×
UNCOV
754
                AuxSigner:             partialChainControl.Cfg.AuxSigner,
×
UNCOV
755
        }
×
UNCOV
756

×
UNCOV
757
        // The broadcast is already always active for neutrino nodes, so we
×
UNCOV
758
        // don't want to create a rebroadcast loop.
×
UNCOV
759
        if partialChainControl.Cfg.NeutrinoCS == nil {
×
UNCOV
760
                cs := partialChainControl.ChainSource
×
UNCOV
761
                broadcastCfg := pushtx.Config{
×
UNCOV
762
                        Broadcast: func(tx *wire.MsgTx) error {
×
UNCOV
763
                                _, err := cs.SendRawTransaction(
×
UNCOV
764
                                        tx, true,
×
UNCOV
765
                                )
×
UNCOV
766

×
UNCOV
767
                                return err
×
UNCOV
768
                        },
×
UNCOV
769
                        SubscribeBlocks: proxyBlockEpoch(
×
UNCOV
770
                                partialChainControl.ChainNotifier,
×
UNCOV
771
                        ),
×
UNCOV
772
                        RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
×
UNCOV
773
                        // In case the backend is different from neutrino we
×
UNCOV
774
                        // make sure that broadcast backend errors are mapped
×
UNCOV
775
                        // to the neutrino broadcastErr.
×
UNCOV
776
                        MapCustomBroadcastError: func(err error) error {
×
UNCOV
777
                                rpcErr := cs.MapRPCErr(err)
×
778
                                return broadcastErrorMapper(rpcErr)
779
                        },
780
                }
781

782
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
783
                        pushtx.NewBroadcaster(&broadcastCfg),
784
                )
UNCOV
785
        }
×
UNCOV
786

×
UNCOV
787
        // We've created the wallet configuration now, so we can finish
×
UNCOV
788
        // initializing the main chain control.
×
789
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
790
                lnWalletConfig, walletController, partialChainControl,
UNCOV
791
        )
×
UNCOV
792
        if err != nil {
×
793
                err := fmt.Errorf("unable to create chain control: %w", err)
×
794
                d.logger.Error(err)
795
                return nil, nil, err
796
        }
797

UNCOV
798
        return activeChainControl, cleanUp, nil
×
UNCOV
799
}
×
UNCOV
800

×
UNCOV
801
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
×
UNCOV
802
// an RPC interface.
×
UNCOV
803
type RPCSignerWalletImpl struct {
×
UNCOV
804
        // DefaultWalletImpl is the embedded instance of the default
×
UNCOV
805
        // implementation that the remote signer uses as its watch-only wallet
×
806
        // for keeping track of addresses and UTXOs.
UNCOV
807
        *DefaultWalletImpl
×
808
}
809

810
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
811
// implementation.
812
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
813
        interceptor signal.Interceptor,
814
        migrateWatchOnly bool) *RPCSignerWalletImpl {
815

816
        return &RPCSignerWalletImpl{
817
                DefaultWalletImpl: &DefaultWalletImpl{
818
                        cfg:              cfg,
819
                        logger:           logger,
820
                        interceptor:      interceptor,
821
                        watchOnly:        true,
822
                        migrateWatchOnly: migrateWatchOnly,
UNCOV
823
                        pwService:        createWalletUnlockerService(cfg),
×
UNCOV
824
                },
×
UNCOV
825
        }
×
UNCOV
826
}
×
UNCOV
827

×
UNCOV
828
// BuildChainControl is responsible for creating or unlocking and then fully
×
UNCOV
829
// initializing a wallet and returning it as part of a fully populated chain
×
UNCOV
830
// control instance.
×
UNCOV
831
//
×
UNCOV
832
// NOTE: This is part of the ChainControlBuilder interface.
×
UNCOV
833
func (d *RPCSignerWalletImpl) BuildChainControl(
×
UNCOV
834
        partialChainControl *chainreg.PartialChainControl,
×
UNCOV
835
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
836

837
        walletController, err := btcwallet.New(
838
                *walletConfig, partialChainControl.Cfg.BlockCache,
839
        )
840
        if err != nil {
841
                err := fmt.Errorf("unable to create wallet controller: %w", err)
842
                d.logger.Error(err)
843
                return nil, nil, err
844
        }
×
UNCOV
845

×
UNCOV
846
        baseKeyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
847
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
848
        )
×
UNCOV
849

×
UNCOV
850
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
×
UNCOV
851
                baseKeyRing, walletController,
×
UNCOV
852
                d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
×
UNCOV
853
        )
×
854
        if err != nil {
855
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
856
                        "%v", err)
×
857
                d.logger.Error(err)
×
858
                return nil, nil, err
×
859
        }
×
UNCOV
860

×
UNCOV
861
        // Create, and start the lnwallet, which handles the core payment
×
UNCOV
862
        // channel logic, and exposes control via proxy state machines.
×
UNCOV
863
        lnWalletConfig := lnwallet.Config{
×
UNCOV
864
                Database:              partialChainControl.Cfg.ChanStateDB,
×
UNCOV
865
                Notifier:              partialChainControl.ChainNotifier,
×
UNCOV
866
                WalletController:      rpcKeyRing,
×
UNCOV
867
                Signer:                rpcKeyRing,
×
UNCOV
868
                FeeEstimator:          partialChainControl.FeeEstimator,
×
869
                SecretKeyRing:         rpcKeyRing,
870
                ChainIO:               walletController,
871
                NetParams:             *walletConfig.NetParams,
UNCOV
872
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
873
        }
×
UNCOV
874

×
UNCOV
875
        // We've created the wallet configuration now, so we can finish
×
UNCOV
876
        // initializing the main chain control.
×
UNCOV
877
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
×
UNCOV
878
                lnWalletConfig, rpcKeyRing, partialChainControl,
×
UNCOV
879
        )
×
UNCOV
880
        if err != nil {
×
881
                err := fmt.Errorf("unable to create chain control: %w", err)
×
882
                d.logger.Error(err)
×
883
                return nil, nil, err
×
884
        }
×
UNCOV
885

×
UNCOV
886
        return activeChainControl, cleanUp, nil
×
UNCOV
887
}
×
UNCOV
888

×
UNCOV
889
// DatabaseInstances is a struct that holds all instances to the actual
×
UNCOV
890
// databases that are used in lnd.
×
UNCOV
891
type DatabaseInstances struct {
×
UNCOV
892
        // GraphDB is the database that stores the channel graph used for path
×
UNCOV
893
        // finding.
×
894
        //
UNCOV
895
        // NOTE/TODO: This currently _needs_ to be the same instance as the
×
896
        // ChanStateDB below until the separation of the two databases is fully
897
        // complete!
898
        GraphDB *channeldb.DB
899

900
        // ChanStateDB is the database that stores all of our node's channel
901
        // state.
902
        //
903
        // NOTE/TODO: This currently _needs_ to be the same instance as the
904
        // GraphDB above until the separation of the two databases is fully
905
        // complete!
906
        ChanStateDB *channeldb.DB
907

908
        // HeightHintDB is the database that stores height hints for spends.
909
        HeightHintDB kvdb.Backend
910

911
        // InvoiceDB is the database that stores information about invoices.
912
        InvoiceDB invoices.InvoiceDB
913

914
        // MacaroonDB is the database that stores macaroon root keys.
915
        MacaroonDB kvdb.Backend
916

917
        // DecayedLogDB is the database that stores p2p related encryption
918
        // information.
919
        DecayedLogDB kvdb.Backend
920

921
        // TowerClientDB is the database that stores the watchtower client's
922
        // configuration.
923
        TowerClientDB wtclient.DB
924

925
        // TowerServerDB is the database that stores the watchtower server's
926
        // configuration.
927
        TowerServerDB watchtower.DB
928

929
        // WalletDB is the configuration for loading the wallet database using
930
        // the btcwallet's loader.
931
        WalletDB btcwallet.LoaderOption
932

933
        // NativeSQLStore is a pointer to a native SQL store that can be used
934
        // for native SQL queries for tables that already support it. This may
935
        // be nil if the use-native-sql flag was not set.
936
        NativeSQLStore *sqldb.BaseDB
937
}
938

939
// DefaultDatabaseBuilder is a type that builds the default database backends
940
// for lnd, using the given configuration to decide what actual implementation
941
// to use.
942
type DefaultDatabaseBuilder struct {
943
        cfg    *Config
944
        logger btclog.Logger
945
}
946

947
// NewDefaultDatabaseBuilder returns a new instance of the default database
948
// builder.
949
func NewDefaultDatabaseBuilder(cfg *Config,
950
        logger btclog.Logger) *DefaultDatabaseBuilder {
951

952
        return &DefaultDatabaseBuilder{
953
                cfg:    cfg,
954
                logger: logger,
955
        }
956
}
957

958
// BuildDatabase extracts the current databases that we'll use for normal
UNCOV
959
// operation in the daemon. A function closure that closes all opened databases
×
UNCOV
960
// is also returned.
×
UNCOV
961
func (d *DefaultDatabaseBuilder) BuildDatabase(
×
UNCOV
962
        ctx context.Context) (*DatabaseInstances, func(), error) {
×
UNCOV
963

×
UNCOV
964
        d.logger.Infof("Opening the main database, this might take a few " +
×
UNCOV
965
                "minutes...")
×
966

967
        cfg := d.cfg
968
        if cfg.DB.Backend == lncfg.BoltBackend {
969
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
970
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
UNCOV
971
                        cfg.DB.Bolt.AutoCompact)
×
UNCOV
972
        }
×
UNCOV
973

×
UNCOV
974
        startOpenTime := time.Now()
×
UNCOV
975

×
UNCOV
976
        databaseBackends, err := cfg.DB.GetBackends(
×
UNCOV
977
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
×
UNCOV
978
                        cfg.Watchtower.TowerDir, BitcoinChainName,
×
UNCOV
979
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
980
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
×
UNCOV
981
        )
×
982
        if err != nil {
983
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
984
                        "backends: %v", err)
×
985
        }
×
UNCOV
986

×
UNCOV
987
        // With the full remote mode we made sure both the graph and channel
×
UNCOV
988
        // state DB point to the same local or remote DB and the same namespace
×
UNCOV
989
        // within that DB.
×
UNCOV
990
        dbs := &DatabaseInstances{
×
UNCOV
991
                HeightHintDB:   databaseBackends.HeightHintDB,
×
UNCOV
992
                MacaroonDB:     databaseBackends.MacaroonDB,
×
UNCOV
993
                DecayedLogDB:   databaseBackends.DecayedLogDB,
×
UNCOV
994
                WalletDB:       databaseBackends.WalletDB,
×
995
                NativeSQLStore: databaseBackends.NativeSQLStore,
996
        }
997
        cleanUp := func() {
998
                // We can just close the returned close functions directly. Even
UNCOV
999
                // if we decorate the channel DB with an additional struct, its
×
UNCOV
1000
                // close function still just points to the kvdb backend.
×
UNCOV
1001
                for name, closeFunc := range databaseBackends.CloseFuncs {
×
UNCOV
1002
                        if err := closeFunc(); err != nil {
×
1003
                                d.logger.Errorf("Error closing %s "+
×
1004
                                        "database: %v", name, err)
×
1005
                        }
×
UNCOV
1006
                }
×
UNCOV
1007
        }
×
UNCOV
1008
        if databaseBackends.Remote {
×
1009
                d.logger.Infof("Using remote %v database! Creating "+
×
1010
                        "graph and channel state DB instances", cfg.DB.Backend)
×
UNCOV
1011
        } else {
×
UNCOV
1012
                d.logger.Infof("Creating local graph and channel state DB " +
×
UNCOV
1013
                        "instances")
×
UNCOV
1014
        }
×
1015

1016
        dbOptions := []channeldb.OptionModifier{
UNCOV
1017
                channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize),
×
UNCOV
1018
                channeldb.OptionSetChannelCacheSize(
×
UNCOV
1019
                        cfg.Caches.ChannelCacheSize,
×
UNCOV
1020
                ),
×
UNCOV
1021
                channeldb.OptionSetBatchCommitInterval(
×
UNCOV
1022
                        cfg.DB.BatchCommitInterval,
×
UNCOV
1023
                ),
×
1024
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
UNCOV
1025
                channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache),
×
UNCOV
1026
                channeldb.OptionKeepFailedPaymentAttempts(
×
UNCOV
1027
                        cfg.KeepFailedPaymentAttempts,
×
UNCOV
1028
                ),
×
UNCOV
1029
                channeldb.OptionStoreFinalHtlcResolutions(
×
UNCOV
1030
                        cfg.StoreFinalHtlcResolutions,
×
UNCOV
1031
                ),
×
UNCOV
1032
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
×
UNCOV
1033
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
×
UNCOV
1034
        }
×
UNCOV
1035

×
UNCOV
1036
        // We want to pre-allocate the channel graph cache according to what we
×
UNCOV
1037
        // expect for mainnet to speed up memory allocation.
×
UNCOV
1038
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
×
1039
                dbOptions = append(
×
1040
                        dbOptions, channeldb.OptionSetPreAllocCacheNumNodes(
×
1041
                                channeldb.DefaultPreAllocCacheNumNodes,
×
1042
                        ),
×
1043
                )
×
1044
        }
×
UNCOV
1045

×
UNCOV
1046
        // Otherwise, we'll open two instances, one for the state we only need
×
UNCOV
1047
        // locally, and the other for things we want to ensure are replicated.
×
UNCOV
1048
        dbs.GraphDB, err = channeldb.CreateWithBackend(
×
UNCOV
1049
                databaseBackends.GraphDB, dbOptions...,
×
UNCOV
1050
        )
×
UNCOV
1051
        switch {
×
UNCOV
1052
        // Give the DB a chance to dry run the migration. Since we know that
×
UNCOV
1053
        // both the channel state and graph DBs are still always behind the same
×
1054
        // backend, we know this would be applied to both of those DBs.
1055
        case err == channeldb.ErrDryRunMigrationOK:
1056
                d.logger.Infof("Graph DB dry run migration successful")
1057
                return nil, nil, err
×
UNCOV
1058

×
1059
        case err != nil:
×
1060
                cleanUp()
×
1061

1062
                err := fmt.Errorf("unable to open graph DB: %w", err)
1063
                d.logger.Error(err)
1064
                return nil, nil, err
×
UNCOV
1065
        }
×
UNCOV
1066

×
1067
        // For now, we don't _actually_ split the graph and channel state DBs on
UNCOV
1068
        // the code level. Since they both are based upon the *channeldb.DB
×
UNCOV
1069
        // struct it will require more refactoring to fully separate them. With
×
UNCOV
1070
        // the full remote mode we at least know for now that they both point to
×
UNCOV
1071
        // the same DB backend (and also namespace within that) so we only need
×
UNCOV
1072
        // to apply any migration once.
×
UNCOV
1073
        //
×
1074
        // TODO(guggero): Once the full separation of anything graph related
1075
        // from the channeldb.DB is complete, the decorated instance of the
1076
        // channel state DB should be created here individually instead of just
1077
        // using the same struct (and DB backend) instance.
1078
        dbs.ChanStateDB = dbs.GraphDB
1079

1080
        // Instantiate a native SQL invoice store if the flag is set.
1081
        if d.cfg.DB.UseNativeSQL {
1082
                // KV invoice db resides in the same database as the graph and
1083
                // channel state DB. Let's query the database to see if we have
1084
                // any invoices there. If we do, we won't allow the user to
1085
                // start lnd with native SQL enabled, as we don't currently
1086
                // migrate the invoices to the new database schema.
1087
                invoiceSlice, err := dbs.GraphDB.QueryInvoices(
×
1088
                        ctx, invoices.InvoiceQuery{
×
1089
                                NumMaxInvoices: 1,
×
1090
                        },
×
1091
                )
×
1092
                if err != nil {
×
1093
                        cleanUp()
×
1094
                        d.logger.Errorf("Unable to query KV invoice DB: %v",
×
1095
                                err)
×
1096

×
1097
                        return nil, nil, err
×
1098
                }
×
UNCOV
1099

×
1100
                if len(invoiceSlice.Invoices) > 0 {
×
1101
                        cleanUp()
×
1102
                        err := fmt.Errorf("found invoices in the KV invoice " +
×
1103
                                "DB, migration to native SQL is not yet " +
×
1104
                                "supported")
×
1105
                        d.logger.Error(err)
×
1106

×
1107
                        return nil, nil, err
×
1108
                }
UNCOV
1109

×
1110
                executor := sqldb.NewTransactionExecutor(
×
1111
                        dbs.NativeSQLStore,
×
1112
                        func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1113
                                return dbs.NativeSQLStore.WithTx(tx)
×
1114
                        },
×
UNCOV
1115
                )
×
UNCOV
1116

×
1117
                dbs.InvoiceDB = invoices.NewSQLStore(
×
1118
                        executor, clock.NewDefaultClock(),
1119
                )
×
UNCOV
1120
        } else {
×
UNCOV
1121
                dbs.InvoiceDB = dbs.GraphDB
×
UNCOV
1122
        }
×
UNCOV
1123

×
1124
        // Wrap the watchtower client DB and make sure we clean up.
1125
        if cfg.WtClient.Active {
UNCOV
1126
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
×
UNCOV
1127
                        databaseBackends.TowerClientDB,
×
UNCOV
1128
                )
×
UNCOV
1129
                if err != nil {
×
1130
                        cleanUp()
×
1131

×
1132
                        err := fmt.Errorf("unable to open %s database: %w",
1133
                                lncfg.NSTowerClientDB, err)
1134
                        d.logger.Error(err)
×
1135
                        return nil, nil, err
×
1136
                }
×
UNCOV
1137
        }
×
UNCOV
1138

×
UNCOV
1139
        // Wrap the watchtower server DB and make sure we clean up.
×
UNCOV
1140
        if cfg.Watchtower.Active {
×
UNCOV
1141
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
×
UNCOV
1142
                        databaseBackends.TowerServerDB,
×
UNCOV
1143
                )
×
UNCOV
1144
                if err != nil {
×
1145
                        cleanUp()
×
1146

1147
                        err := fmt.Errorf("unable to open %s database: %w",
1148
                                lncfg.NSTowerServerDB, err)
1149
                        d.logger.Error(err)
×
1150
                        return nil, nil, err
×
1151
                }
×
UNCOV
1152
        }
×
UNCOV
1153

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

×
UNCOV
1157
        return dbs, cleanUp, nil
×
UNCOV
1158
}
×
UNCOV
1159

×
UNCOV
1160
// waitForWalletPassword blocks until a password is provided by the user to
×
1161
// this RPC server.
1162
func waitForWalletPassword(cfg *Config,
UNCOV
1163
        pwService *walletunlocker.UnlockerService,
×
UNCOV
1164
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
×
UNCOV
1165
        *walletunlocker.WalletUnlockParams, error) {
×
UNCOV
1166

×
1167
        // Wait for user to provide the password.
1168
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
1169
                "create` to create a wallet, `lncli unlock` to unlock an " +
1170
                "existing wallet, or `lncli changepassword` to change the " +
1171
                "password of an existing wallet and unlock it.")
1172

1173
        // We currently don't distinguish between getting a password to be used
UNCOV
1174
        // for creation or unlocking, as a new wallet db will be created if
×
UNCOV
1175
        // none exists when creating the chain control.
×
UNCOV
1176
        select {
×
UNCOV
1177
        // The wallet is being created for the first time, we'll check to see
×
UNCOV
1178
        // if the user provided any entropy for seed creation. If so, then
×
UNCOV
1179
        // we'll create the wallet early to load the seed.
×
UNCOV
1180
        case initMsg := <-pwService.InitMsgs:
×
UNCOV
1181
                password := initMsg.Passphrase
×
UNCOV
1182
                cipherSeed := initMsg.WalletSeed
×
UNCOV
1183
                extendedKey := initMsg.WalletExtendedKey
×
UNCOV
1184
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
×
UNCOV
1185
                recoveryWindow := initMsg.RecoveryWindow
×
1186

1187
                // Before we proceed, we'll check the internal version of the
1188
                // seed. If it's greater than the current key derivation
UNCOV
1189
                // version, then we'll return an error as we don't understand
×
UNCOV
1190
                // this.
×
UNCOV
1191
                if cipherSeed != nil &&
×
UNCOV
1192
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
×
1193

×
1194
                        return nil, fmt.Errorf("invalid internal "+
×
1195
                                "seed version %v, current max version is %v",
×
1196
                                cipherSeed.InternalVersion,
×
1197
                                keychain.CurrentKeyDerivationVersion)
×
1198
                }
×
UNCOV
1199

×
UNCOV
1200
                loader, err := btcwallet.NewWalletLoader(
×
UNCOV
1201
                        cfg.ActiveNetParams.Params, recoveryWindow,
×
UNCOV
1202
                        loaderOpts...,
×
UNCOV
1203
                )
×
UNCOV
1204
                if err != nil {
×
1205
                        return nil, err
×
1206
                }
×
UNCOV
1207

×
1208
                // With the seed, we can now use the wallet loader to create
UNCOV
1209
                // the wallet, then pass it back to avoid unlocking it again.
×
UNCOV
1210
                var (
×
UNCOV
1211
                        birthday  time.Time
×
UNCOV
1212
                        newWallet *wallet.Wallet
×
UNCOV
1213
                )
×
UNCOV
1214
                switch {
×
UNCOV
1215
                // A normal cipher seed was given, use the birthday encoded in
×
1216
                // it and create the wallet from that.
1217
                case cipherSeed != nil:
1218
                        birthday = cipherSeed.BirthdayTime()
UNCOV
1219
                        newWallet, err = loader.CreateNewWallet(
×
UNCOV
1220
                                password, password, cipherSeed.Entropy[:],
×
UNCOV
1221
                                birthday,
×
UNCOV
1222
                        )
×
UNCOV
1223

×
1224
                // No seed was given, we're importing a wallet from its extended
1225
                // private key.
UNCOV
1226
                case extendedKey != nil:
×
UNCOV
1227
                        birthday = initMsg.ExtendedKeyBirthday
×
UNCOV
1228
                        newWallet, err = loader.CreateNewWalletExtendedKey(
×
UNCOV
1229
                                password, password, extendedKey, birthday,
×
UNCOV
1230
                        )
×
UNCOV
1231

×
1232
                // Neither seed nor extended private key was given, so maybe the
1233
                // third option was chosen, the watch-only initialization. In
1234
                // this case we need to import each of the xpubs individually.
UNCOV
1235
                case watchOnlyAccounts != nil:
×
UNCOV
1236
                        if !cfg.RemoteSigner.Enable {
×
1237
                                return nil, fmt.Errorf("cannot initialize " +
×
1238
                                        "watch only wallet with remote " +
×
1239
                                        "signer config disabled")
×
1240
                        }
1241

1242
                        birthday = initMsg.WatchOnlyBirthday
1243
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
UNCOV
1244
                                password, birthday,
×
UNCOV
1245
                        )
×
UNCOV
1246
                        if err != nil {
×
1247
                                break
×
UNCOV
1248
                        }
×
UNCOV
1249

×
1250
                        err = importWatchOnlyAccounts(newWallet, initMsg)
UNCOV
1251

×
1252
                default:
×
1253
                        // The unlocker service made sure either the cipher seed
×
1254
                        // or the extended key is set so, we shouldn't get here.
×
1255
                        // The default case is just here for readability and
×
1256
                        // completeness.
×
1257
                        err = fmt.Errorf("cannot create wallet, neither seed " +
1258
                                "nor extended key was given")
UNCOV
1259
                }
×
1260
                if err != nil {
1261
                        // Don't leave the file open in case the new wallet
×
1262
                        // could not be created for whatever reason.
×
1263
                        if err := loader.UnloadWallet(); err != nil {
×
1264
                                ltndLog.Errorf("Could not unload new "+
×
1265
                                        "wallet: %v", err)
×
1266
                        }
×
1267
                        return nil, err
×
1268
                }
UNCOV
1269

×
UNCOV
1270
                // For new wallets, the ResetWalletTransactions flag is a no-op.
×
UNCOV
1271
                if cfg.ResetWalletTransactions {
×
UNCOV
1272
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
×
UNCOV
1273
                                "flag for new wallet as it has no effect")
×
UNCOV
1274
                }
×
UNCOV
1275

×
UNCOV
1276
                return &walletunlocker.WalletUnlockParams{
×
1277
                        Password:        password,
1278
                        Birthday:        birthday,
1279
                        RecoveryWindow:  recoveryWindow,
UNCOV
1280
                        Wallet:          newWallet,
×
UNCOV
1281
                        ChansToRestore:  initMsg.ChanBackups,
×
UNCOV
1282
                        UnloadWallet:    loader.UnloadWallet,
×
UNCOV
1283
                        StatelessInit:   initMsg.StatelessInit,
×
1284
                        MacResponseChan: pwService.MacResponseChan,
UNCOV
1285
                        MacRootKey:      initMsg.MacRootKey,
×
UNCOV
1286
                }, nil
×
UNCOV
1287

×
UNCOV
1288
        // The wallet has already been created in the past, and is simply being
×
UNCOV
1289
        // unlocked. So we'll just return these passphrases.
×
UNCOV
1290
        case unlockMsg := <-pwService.UnlockMsgs:
×
UNCOV
1291
                // Resetting the transactions is something the user likely only
×
UNCOV
1292
                // wants to do once so we add a prominent warning to the log to
×
UNCOV
1293
                // remind the user to turn off the setting again after
×
UNCOV
1294
                // successful completion.
×
UNCOV
1295
                if cfg.ResetWalletTransactions {
×
1296
                        ltndLog.Warnf("Dropped all transaction history from " +
1297
                                "on-chain wallet. Remember to disable " +
1298
                                "reset-wallet-transactions flag for next " +
UNCOV
1299
                                "start of lnd")
×
UNCOV
1300
                }
×
UNCOV
1301

×
UNCOV
1302
                return &walletunlocker.WalletUnlockParams{
×
UNCOV
1303
                        Password:        unlockMsg.Passphrase,
×
UNCOV
1304
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
×
UNCOV
1305
                        Wallet:          unlockMsg.Wallet,
×
UNCOV
1306
                        ChansToRestore:  unlockMsg.ChanBackups,
×
UNCOV
1307
                        UnloadWallet:    unlockMsg.UnloadWallet,
×
UNCOV
1308
                        StatelessInit:   unlockMsg.StatelessInit,
×
UNCOV
1309
                        MacResponseChan: pwService.MacResponseChan,
×
1310
                }, nil
UNCOV
1311

×
UNCOV
1312
        // If we got a shutdown signal we just return with an error immediately
×
1313
        case <-shutdownChan:
×
1314
                return nil, fmt.Errorf("shutting down")
×
UNCOV
1315
        }
×
UNCOV
1316
}
×
UNCOV
1317

×
UNCOV
1318
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
×
UNCOV
1319
// which we created as watch-only.
×
1320
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1321
        initMsg *walletunlocker.WalletInitMsg) error {
UNCOV
1322

×
UNCOV
1323
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
×
1324
        for scope := range initMsg.WatchOnlyAccounts {
1325
                scopes = append(scopes, scope)
1326
        }
1327

1328
        // We need to import the accounts in the correct order, otherwise the
1329
        // indices will be incorrect.
UNCOV
1330
        sort.Slice(scopes, func(i, j int) bool {
×
UNCOV
1331
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
×
UNCOV
1332
                        scopes[i].Index < scopes[j].Index
×
UNCOV
1333
        })
×
UNCOV
1334

×
UNCOV
1335
        for _, scope := range scopes {
×
1336
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
1337

1338
                // We want witness pubkey hash by default, except for BIP49
UNCOV
1339
                // where we want mixed and BIP86 where we want taproot address
×
UNCOV
1340
                // formats.
×
UNCOV
1341
                switch scope.Scope.Purpose {
×
UNCOV
1342
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
×
1343
                        waddrmgr.KeyScopeBIP0086.Purpose:
UNCOV
1344

×
UNCOV
1345
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
×
UNCOV
1346
                }
×
UNCOV
1347

×
UNCOV
1348
                // We want a human-readable account name. But for the default
×
UNCOV
1349
                // on-chain wallet we actually need to call it "default" to make
×
UNCOV
1350
                // sure everything works correctly.
×
1351
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
UNCOV
1352
                if scope.Index == 0 {
×
UNCOV
1353
                        name = "default"
×
UNCOV
1354
                }
×
1355

1356
                _, err := wallet.ImportAccountWithScope(
1357
                        name, initMsg.WatchOnlyAccounts[scope],
1358
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
1359
                        addrSchema,
UNCOV
1360
                )
×
UNCOV
1361
                if err != nil {
×
1362
                        return fmt.Errorf("could not import account %v: %w",
×
1363
                                name, err)
×
1364
                }
UNCOV
1365
        }
×
UNCOV
1366

×
UNCOV
1367
        return nil
×
UNCOV
1368
}
×
UNCOV
1369

×
UNCOV
1370
// initNeutrinoBackend inits a new instance of the neutrino light client
×
UNCOV
1371
// backend given a target chain directory to store the chain state.
×
UNCOV
1372
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
×
UNCOV
1373
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
×
1374
        func(), error) {
1375

UNCOV
1376
        // Both channel validation flags are false by default but their meaning
×
1377
        // is the inverse of each other. Therefore both cannot be true. For
1378
        // every other case, the neutrino.validatechannels overwrites the
1379
        // routing.assumechanvalid value.
1380
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
1381
                return nil, nil, fmt.Errorf("can't set both " +
1382
                        "neutrino.validatechannels and routing." +
1383
                        "assumechanvalid to true at the same time")
×
1384
        }
×
UNCOV
1385
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
×
UNCOV
1386

×
UNCOV
1387
        // First we'll open the database file for neutrino, creating the
×
UNCOV
1388
        // database if needed. We append the normalized network name here to
×
UNCOV
1389
        // match the behavior of btcwallet.
×
UNCOV
1390
        dbPath := filepath.Join(
×
UNCOV
1391
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
×
UNCOV
1392
        )
×
UNCOV
1393

×
UNCOV
1394
        // Ensure that the neutrino db path exists.
×
UNCOV
1395
        if err := os.MkdirAll(dbPath, 0700); err != nil {
×
1396
                return nil, nil, err
×
1397
        }
×
UNCOV
1398

×
UNCOV
1399
        var (
×
UNCOV
1400
                db  walletdb.DB
×
UNCOV
1401
                err error
×
UNCOV
1402
        )
×
UNCOV
1403
        switch {
×
1404
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1405
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1406
                db, err = kvdb.Open(
×
1407
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
1408
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1409
                )
×
UNCOV
1410

×
UNCOV
1411
        default:
×
UNCOV
1412
                dbName := filepath.Join(dbPath, "neutrino.db")
×
UNCOV
1413
                db, err = walletdb.Create(
×
UNCOV
1414
                        "bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
×
UNCOV
1415
                )
×
UNCOV
1416
        }
×
UNCOV
1417
        if err != nil {
×
1418
                return nil, nil, fmt.Errorf("unable to create "+
×
1419
                        "neutrino database: %v", err)
1420
        }
×
UNCOV
1421

×
UNCOV
1422
        headerStateAssertion, err := parseHeaderStateAssertion(
×
UNCOV
1423
                cfg.NeutrinoMode.AssertFilterHeader,
×
UNCOV
1424
        )
×
1425
        if err != nil {
1426
                db.Close()
×
1427
                return nil, nil, err
×
1428
        }
×
UNCOV
1429

×
1430
        // With the database open, we can now create an instance of the
UNCOV
1431
        // neutrino light client. We pass in relevant configuration parameters
×
UNCOV
1432
        // required.
×
UNCOV
1433
        config := neutrino.Config{
×
UNCOV
1434
                DataDir:      dbPath,
×
UNCOV
1435
                Database:     db,
×
UNCOV
1436
                ChainParams:  *cfg.ActiveNetParams.Params,
×
UNCOV
1437
                AddPeers:     cfg.NeutrinoMode.AddPeers,
×
1438
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
1439
                Dialer: func(addr net.Addr) (net.Conn, error) {
1440
                        return cfg.net.Dial(
1441
                                addr.Network(), addr.String(),
UNCOV
1442
                                cfg.ConnectionTimeout,
×
UNCOV
1443
                        )
×
UNCOV
1444
                },
×
UNCOV
1445
                NameResolver: func(host string) ([]net.IP, error) {
×
UNCOV
1446
                        addrs, err := cfg.net.LookupHost(host)
×
UNCOV
1447
                        if err != nil {
×
1448
                                return nil, err
×
1449
                        }
×
UNCOV
1450

×
UNCOV
1451
                        ips := make([]net.IP, 0, len(addrs))
×
UNCOV
1452
                        for _, strIP := range addrs {
×
UNCOV
1453
                                ip := net.ParseIP(strIP)
×
UNCOV
1454
                                if ip == nil {
×
1455
                                        continue
×
UNCOV
1456
                                }
×
UNCOV
1457

×
UNCOV
1458
                                ips = append(ips, ip)
×
1459
                        }
UNCOV
1460

×
UNCOV
1461
                        return ips, nil
×
UNCOV
1462
                },
×
UNCOV
1463
                AssertFilterHeader: headerStateAssertion,
×
UNCOV
1464
                BlockCache:         blockCache.Cache,
×
1465
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1466
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
UNCOV
1467
        }
×
1468

1469
        neutrino.MaxPeers = 8
UNCOV
1470
        neutrino.BanDuration = time.Hour * 48
×
1471
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
1472
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
1473

1474
        neutrinoCS, err := neutrino.NewChainService(config)
1475
        if err != nil {
1476
                db.Close()
1477
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
1478
                        "client: %v", err)
×
1479
        }
×
UNCOV
1480

×
UNCOV
1481
        if err := neutrinoCS.Start(); err != nil {
×
1482
                db.Close()
×
1483
                return nil, nil, err
×
1484
        }
×
UNCOV
1485

×
UNCOV
1486
        cleanUp := func() {
×
UNCOV
1487
                if err := neutrinoCS.Stop(); err != nil {
×
1488
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1489
                                "%v", err)
1490
                }
×
UNCOV
1491
                db.Close()
×
UNCOV
1492
        }
×
UNCOV
1493

×
1494
        return neutrinoCS, cleanUp, nil
UNCOV
1495
}
×
UNCOV
1496

×
UNCOV
1497
// parseHeaderStateAssertion parses the user-specified neutrino header state
×
UNCOV
1498
// into a headerfs.FilterHeader.
×
UNCOV
1499
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
×
UNCOV
1500
        if len(state) == 0 {
×
1501
                return nil, nil
1502
        }
UNCOV
1503

×
1504
        split := strings.Split(state, ":")
1505
        if len(split) != 2 {
1506
                return nil, fmt.Errorf("header state assertion %v in "+
1507
                        "unexpected format, expected format height:hash", state)
1508
        }
×
UNCOV
1509

×
1510
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1511
        if err != nil {
×
1512
                return nil, fmt.Errorf("invalid filter header height: %w", err)
1513
        }
×
UNCOV
1514

×
1515
        hash, err := chainhash.NewHashFromStr(split[1])
×
1516
        if err != nil {
×
1517
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1518
        }
UNCOV
1519

×
1520
        return &headerfs.FilterHeader{
×
1521
                Height:     uint32(height),
×
1522
                FilterHash: *hash,
×
1523
        }, nil
UNCOV
1524
}
×
UNCOV
1525

×
UNCOV
1526
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
×
UNCOV
1527
// the neutrino BroadcastError which allows the Rebroadcaster which currently
×
1528
// resides in the neutrino package to use all of its functionalities.
UNCOV
1529
func broadcastErrorMapper(err error) error {
×
UNCOV
1530
        var returnErr error
×
UNCOV
1531

×
UNCOV
1532
        // We only filter for specific backend errors which are relevant for the
×
1533
        // Rebroadcaster.
1534
        switch {
1535
        // This makes sure the tx is removed from the rebroadcaster once it is
1536
        // confirmed.
1537
        case errors.Is(err, chain.ErrTxAlreadyKnown),
UNCOV
1538
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
×
UNCOV
1539

×
UNCOV
1540
                returnErr = &pushtx.BroadcastError{
×
UNCOV
1541
                        Code:   pushtx.Confirmed,
×
UNCOV
1542
                        Reason: err.Error(),
×
UNCOV
1543
                }
×
1544

1545
        // Transactions which are still in mempool but might fall out because
1546
        // of low fees are rebroadcasted despite of their backend error.
1547
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1548
                returnErr = &pushtx.BroadcastError{
×
1549
                        Code:   pushtx.Mempool,
×
1550
                        Reason: err.Error(),
×
1551
                }
×
UNCOV
1552

×
1553
        // Transactions which are not accepted into mempool because of low fees
1554
        // in the first place are rebroadcasted despite of their backend error.
1555
        // Mempool conditions change over time so it makes sense to retry
UNCOV
1556
        // publishing the transaction. Moreover we log the detailed error so the
×
UNCOV
1557
        // user can intervene and increase the size of his mempool.
×
1558
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
×
1559
                ltndLog.Warnf("Error while broadcasting transaction: %v", err)
×
1560

×
1561
                returnErr = &pushtx.BroadcastError{
1562
                        Code:   pushtx.Mempool,
1563
                        Reason: err.Error(),
1564
                }
1565
        }
1566

UNCOV
1567
        return returnErr
×
UNCOV
1568
}
×
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