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

lightningnetwork / lnd / 12343072627

15 Dec 2024 11:09PM UTC coverage: 57.504% (-1.1%) from 58.636%
12343072627

Pull #9315

github

yyforyongyu
contractcourt: offer outgoing htlc one block earlier before its expiry

We need to offer the outgoing htlc one block earlier to make sure when
the expiry height hits, the sweeper will not miss sweeping it in the
same block. This also means the outgoing contest resolver now only does
one thing - watch for preimage spend till height expiry-1, which can
easily be moved into the timeout resolver instead in the future.
Pull Request #9315: Implement `blockbeat`

1445 of 2007 new or added lines in 26 files covered. (72.0%)

19246 existing lines in 249 files now uncovered.

102342 of 177975 relevant lines covered (57.5%)

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

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

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

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

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

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

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

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

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

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

139
        // ExternalValidator is a type that can provide external macaroon
140
        // validation.
141
        ExternalValidator
142

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

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

151
        // ChainControlBuilder is a type that can provide a custom wallet
152
        // implementation.
153
        ChainControlBuilder
154

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

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

167
        // TrafficShaper is an optional traffic shaper that can be used to
168
        // control the outgoing channel of a payment.
169
        TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
170

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

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

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

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

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

193
        // AuxSweeper is an optional interface that can be used to modify the
194
        // way sweep transaction are generated.
195
        AuxSweeper fn.Option[sweep.AuxSweeper]
196

197
        // AuxContractResolver is an optional interface that can be used to
198
        // modify the way contracts are resolved.
199
        AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
200
}
201

202
// DefaultWalletImpl is the default implementation of our normal, btcwallet
203
// backed configuration.
204
type DefaultWalletImpl struct {
205
        cfg         *Config
206
        logger      btclog.Logger
207
        interceptor signal.Interceptor
208

209
        watchOnly        bool
210
        migrateWatchOnly bool
211
        pwService        *walletunlocker.UnlockerService
212
}
213

214
// NewDefaultWalletImpl creates a new default wallet implementation.
215
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
UNCOV
216
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
×
UNCOV
217

×
UNCOV
218
        return &DefaultWalletImpl{
×
UNCOV
219
                cfg:         cfg,
×
UNCOV
220
                logger:      logger,
×
UNCOV
221
                interceptor: interceptor,
×
UNCOV
222
                watchOnly:   watchOnly,
×
UNCOV
223
                pwService:   createWalletUnlockerService(cfg),
×
UNCOV
224
        }
×
UNCOV
225
}
×
226

227
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
228
// instance. External subservers implementing this method can then register
229
// their own REST proxy stubs to the main server instance.
230
//
231
// NOTE: This is part of the GrpcRegistrar interface.
232
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
233
        mux *proxy.ServeMux, restProxyDest string,
UNCOV
234
        restDialOpts []grpc.DialOption) error {
×
UNCOV
235

×
UNCOV
236
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
×
UNCOV
237
                ctx, mux, restProxyDest, restDialOpts,
×
UNCOV
238
        )
×
UNCOV
239
}
×
240

241
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
242
// grpc.Server instance. External subservers implementing this method can then
243
// register their own gRPC server structs to the main server instance.
244
//
245
// NOTE: This is part of the GrpcRegistrar interface.
UNCOV
246
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
×
UNCOV
247
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
×
UNCOV
248

×
UNCOV
249
        return nil
×
UNCOV
250
}
×
251

252
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
253
// checks its signature, makes sure all specified permissions for the called
254
// method are contained within and finally ensures all caveat conditions are
255
// met. A non-nil error is returned if any of the checks fail.
256
//
257
// NOTE: This is part of the ExternalValidator interface.
258
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
259
        requiredPermissions []bakery.Op, fullMethod string) error {
×
260

×
261
        // Because the default implementation does not return any permissions,
×
262
        // we shouldn't be registered as an external validator at all and this
×
263
        // should never be invoked.
×
264
        return fmt.Errorf("default implementation does not support external " +
×
265
                "macaroon validation")
×
266
}
×
267

268
// Permissions returns the permissions that the external validator is
269
// validating. It is a map between the full HTTP URI of each RPC and its
270
// required macaroon permissions. If multiple action/entity tuples are specified
271
// per URI, they are all required. See rpcserver.go for a list of valid action
272
// and entity values.
273
//
274
// NOTE: This is part of the ExternalValidator interface.
UNCOV
275
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
×
UNCOV
276
        return nil
×
UNCOV
277
}
×
278

279
// BuildWalletConfig is responsible for creating or unlocking and then
280
// fully initializing a wallet.
281
//
282
// NOTE: This is part of the WalletConfigBuilder interface.
283
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
284
        dbs *DatabaseInstances, aux *AuxComponents,
285
        interceptorChain *rpcperms.InterceptorChain,
286
        grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
UNCOV
287
        *btcwallet.Config, func(), error) {
×
UNCOV
288

×
UNCOV
289
        // Keep track of our various cleanup functions. We use a defer function
×
UNCOV
290
        // as well to not repeat ourselves with every return statement.
×
UNCOV
291
        var (
×
UNCOV
292
                cleanUpTasks []func()
×
UNCOV
293
                earlyExit    = true
×
UNCOV
294
                cleanUp      = func() {
×
UNCOV
295
                        for _, fn := range cleanUpTasks {
×
UNCOV
296
                                if fn == nil {
×
297
                                        continue
×
298
                                }
299

UNCOV
300
                                fn()
×
301
                        }
302
                }
303
        )
UNCOV
304
        defer func() {
×
UNCOV
305
                if earlyExit {
×
306
                        cleanUp()
×
307
                }
×
308
        }()
309

310
        // Initialize a new block cache.
UNCOV
311
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
×
UNCOV
312

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

UNCOV
332
        var (
×
UNCOV
333
                walletInitParams = walletunlocker.WalletUnlockParams{
×
UNCOV
334
                        // In case we do auto-unlock, we need to be able to send
×
UNCOV
335
                        // into the channel without blocking so we buffer it.
×
UNCOV
336
                        MacResponseChan: make(chan []byte, 1),
×
UNCOV
337
                }
×
UNCOV
338
                privateWalletPw = lnwallet.DefaultPrivatePassphrase
×
UNCOV
339
                publicWalletPw  = lnwallet.DefaultPublicPassphrase
×
UNCOV
340
        )
×
UNCOV
341

×
UNCOV
342
        // If the user didn't request a seed, then we'll manually assume a
×
UNCOV
343
        // wallet birthday of now, as otherwise the seed would've specified
×
UNCOV
344
        // this information.
×
UNCOV
345
        walletInitParams.Birthday = time.Now()
×
UNCOV
346

×
UNCOV
347
        d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
×
UNCOV
348
        d.pwService.SetMacaroonDB(dbs.MacaroonDB)
×
UNCOV
349
        walletExists, err := d.pwService.WalletExists()
×
UNCOV
350
        if err != nil {
×
351
                return nil, nil, nil, err
×
352
        }
×
353

UNCOV
354
        if !walletExists {
×
UNCOV
355
                interceptorChain.SetWalletNotCreated()
×
UNCOV
356
        } else {
×
UNCOV
357
                interceptorChain.SetWalletLocked()
×
UNCOV
358
        }
×
359

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

×
368
                return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
×
369
                        "was specified but wallet does not exist; initialize " +
×
370
                        "the wallet before using auto unlocking")
×
371
        }
×
372

373
        // What wallet mode are we running in? We've already made sure the no
374
        // seed backup and auto unlock aren't both set during config parsing.
UNCOV
375
        switch {
×
376
        // No seed backup means we're also using the default password.
UNCOV
377
        case d.cfg.NoSeedBackup:
×
378
                // We continue normally, the default password has already been
379
                // set above.
380

381
        // A password for unlocking is provided in a file.
382
        case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
×
383
                d.logger.Infof("Attempting automatic wallet unlock with " +
×
384
                        "password provided in file")
×
385
                pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
×
386
                if err != nil {
×
387
                        return nil, nil, nil, fmt.Errorf("error reading "+
×
388
                                "password from file %s: %v",
×
389
                                d.cfg.WalletUnlockPasswordFile, err)
×
390
                }
×
391

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

×
397
                // We have the password now, we can ask the unlocker service to
×
398
                // do the unlock for us.
×
399
                unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
×
400
                        pwBytes, 0,
×
401
                )
×
402
                if err != nil {
×
403
                        return nil, nil, nil, fmt.Errorf("error unlocking "+
×
404
                                "wallet with password from file: %v", err)
×
405
                }
×
406

407
                cleanUpTasks = append(cleanUpTasks, func() {
×
408
                        if err := unloadWalletFn(); err != nil {
×
409
                                d.logger.Errorf("Could not unload wallet: %v",
×
410
                                        err)
×
411
                        }
×
412
                })
413

414
                privateWalletPw = pwBytes
×
415
                publicWalletPw = pwBytes
×
416
                walletInitParams.Wallet = unlockedWallet
×
417
                walletInitParams.UnloadWallet = unloadWalletFn
×
418

419
        // If none of the automatic startup options are selected, we fall back
420
        // to the default behavior of waiting for the wallet creation/unlocking
421
        // over RPC.
UNCOV
422
        default:
×
UNCOV
423
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
×
424
                        return nil, nil, nil, err
×
425
                }
×
426

UNCOV
427
                params, err := waitForWalletPassword(
×
UNCOV
428
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
×
UNCOV
429
                        d.interceptor.ShutdownChannel(),
×
UNCOV
430
                )
×
UNCOV
431
                if err != nil {
×
432
                        err := fmt.Errorf("unable to set up wallet password "+
×
433
                                "listeners: %v", err)
×
434
                        d.logger.Error(err)
×
435
                        return nil, nil, nil, err
×
436
                }
×
437

UNCOV
438
                walletInitParams = *params
×
UNCOV
439
                privateWalletPw = walletInitParams.Password
×
UNCOV
440
                publicWalletPw = walletInitParams.Password
×
UNCOV
441
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
442
                        if err := walletInitParams.UnloadWallet(); err != nil {
×
443
                                d.logger.Errorf("Could not unload wallet: %v",
×
444
                                        err)
×
445
                        }
×
446
                })
447

UNCOV
448
                if walletInitParams.RecoveryWindow > 0 {
×
UNCOV
449
                        d.logger.Infof("Wallet recovery mode enabled with "+
×
UNCOV
450
                                "address lookahead of %d addresses",
×
UNCOV
451
                                walletInitParams.RecoveryWindow)
×
UNCOV
452
                }
×
453
        }
454

UNCOV
455
        var macaroonService *macaroons.Service
×
UNCOV
456
        if !d.cfg.NoMacaroons {
×
UNCOV
457
                // Create the macaroon authentication/authorization service.
×
UNCOV
458
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
×
UNCOV
459
                if err != nil {
×
460
                        return nil, nil, nil, err
×
461
                }
×
UNCOV
462
                macaroonService, err = macaroons.NewService(
×
UNCOV
463
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
×
UNCOV
464
                        macaroons.IPLockChecker,
×
UNCOV
465
                        macaroons.CustomChecker(interceptorChain),
×
UNCOV
466
                )
×
UNCOV
467
                if err != nil {
×
468
                        err := fmt.Errorf("unable to set up macaroon "+
×
469
                                "authentication: %v", err)
×
470
                        d.logger.Error(err)
×
471
                        return nil, nil, nil, err
×
472
                }
×
UNCOV
473
                cleanUpTasks = append(cleanUpTasks, func() {
×
UNCOV
474
                        if err := macaroonService.Close(); err != nil {
×
475
                                d.logger.Errorf("Could not close macaroon "+
×
476
                                        "service: %v", err)
×
477
                        }
×
478
                })
479

480
                // Try to unlock the macaroon store with the private password.
481
                // Ignore ErrAlreadyUnlocked since it could be unlocked by the
482
                // wallet unlocker.
UNCOV
483
                err = macaroonService.CreateUnlock(&privateWalletPw)
×
UNCOV
484
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
×
485
                        err := fmt.Errorf("unable to unlock macaroons: %w", err)
×
486
                        d.logger.Error(err)
×
487
                        return nil, nil, nil, err
×
488
                }
×
489

490
                // If we have a macaroon root key from the init wallet params,
491
                // set the root key before baking any macaroons.
UNCOV
492
                if len(walletInitParams.MacRootKey) > 0 {
×
493
                        err := macaroonService.SetRootKey(
×
494
                                walletInitParams.MacRootKey,
×
495
                        )
×
496
                        if err != nil {
×
497
                                return nil, nil, nil, err
×
498
                        }
×
499
                }
500

501
                // Send an admin macaroon to all our listeners that requested
502
                // one by setting a non-nil macaroon channel.
UNCOV
503
                adminMacBytes, err := bakeMacaroon(
×
UNCOV
504
                        ctx, macaroonService, adminPermissions(),
×
UNCOV
505
                )
×
UNCOV
506
                if err != nil {
×
507
                        return nil, nil, nil, err
×
508
                }
×
UNCOV
509
                for _, lis := range grpcListeners {
×
UNCOV
510
                        if lis.MacChan != nil {
×
511
                                lis.MacChan <- adminMacBytes
×
512
                        }
×
513
                }
514

515
                // In case we actually needed to unlock the wallet, we now need
516
                // to create an instance of the admin macaroon and send it to
517
                // the unlocker so it can forward it to the user. In no seed
518
                // backup mode, there's nobody listening on the channel and we'd
519
                // block here forever.
UNCOV
520
                if !d.cfg.NoSeedBackup {
×
UNCOV
521
                        // The channel is buffered by one element so writing
×
UNCOV
522
                        // should not block here.
×
UNCOV
523
                        walletInitParams.MacResponseChan <- adminMacBytes
×
UNCOV
524
                }
×
525

526
                // If the user requested a stateless initialization, no macaroon
527
                // files should be created.
UNCOV
528
                if !walletInitParams.StatelessInit {
×
UNCOV
529
                        // Create default macaroon files for lncli to use if
×
UNCOV
530
                        // they don't exist.
×
UNCOV
531
                        err = genDefaultMacaroons(
×
UNCOV
532
                                ctx, macaroonService, d.cfg.AdminMacPath,
×
UNCOV
533
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
×
UNCOV
534
                        )
×
UNCOV
535
                        if err != nil {
×
536
                                err := fmt.Errorf("unable to create macaroons "+
×
537
                                        "%v", err)
×
538
                                d.logger.Error(err)
×
539
                                return nil, nil, nil, err
×
540
                        }
×
541
                }
542

543
                // As a security service to the user, if they requested
544
                // stateless initialization and there are macaroon files on disk
545
                // we log a warning.
UNCOV
546
                if walletInitParams.StatelessInit {
×
UNCOV
547
                        msg := "Found %s macaroon on disk (%s) even though " +
×
UNCOV
548
                                "--stateless_init was requested. Unencrypted " +
×
UNCOV
549
                                "state is accessible by the host system. You " +
×
UNCOV
550
                                "should change the password and use " +
×
UNCOV
551
                                "--new_mac_root_key with --stateless_init to " +
×
UNCOV
552
                                "clean up and invalidate old macaroons."
×
UNCOV
553

×
UNCOV
554
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
×
555
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
556
                        }
×
UNCOV
557
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
×
558
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
559
                        }
×
UNCOV
560
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
×
561
                                d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
×
562
                        }
×
563
                }
564

565
                // We add the macaroon service to our RPC interceptor. This
566
                // will start checking macaroons against permissions on every
567
                // RPC invocation.
UNCOV
568
                interceptorChain.AddMacaroonService(macaroonService)
×
569
        }
570

571
        // Now that the wallet password has been provided, transition the RPC
572
        // state into Unlocked.
UNCOV
573
        interceptorChain.SetWalletUnlocked()
×
UNCOV
574

×
UNCOV
575
        // Since calls to the WalletUnlocker service wait for a response on the
×
UNCOV
576
        // macaroon channel, we close it here to make sure they return in case
×
UNCOV
577
        // we did not return the admin macaroon above. This will be the case if
×
UNCOV
578
        // --no-macaroons is used.
×
UNCOV
579
        close(walletInitParams.MacResponseChan)
×
UNCOV
580

×
UNCOV
581
        // We'll also close all the macaroon channels since lnd is done sending
×
UNCOV
582
        // macaroon data over it.
×
UNCOV
583
        for _, lis := range grpcListeners {
×
UNCOV
584
                if lis.MacChan != nil {
×
585
                        close(lis.MacChan)
×
586
                }
×
587
        }
588

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

623
        // Let's go ahead and create the partial chain control now that is only
624
        // dependent on our configuration and doesn't require any wallet
625
        // specific information.
UNCOV
626
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
×
UNCOV
627
                chainControlCfg,
×
UNCOV
628
        )
×
UNCOV
629
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
×
UNCOV
630
        if err != nil {
×
631
                err := fmt.Errorf("unable to create partial chain control: %w",
×
632
                        err)
×
633
                d.logger.Error(err)
×
634
                return nil, nil, nil, err
×
635
        }
×
636

UNCOV
637
        walletConfig := &btcwallet.Config{
×
UNCOV
638
                PrivatePass:      privateWalletPw,
×
UNCOV
639
                PublicPass:       publicWalletPw,
×
UNCOV
640
                Birthday:         walletInitParams.Birthday,
×
UNCOV
641
                RecoveryWindow:   walletInitParams.RecoveryWindow,
×
UNCOV
642
                NetParams:        d.cfg.ActiveNetParams.Params,
×
UNCOV
643
                CoinType:         d.cfg.ActiveNetParams.CoinType,
×
UNCOV
644
                Wallet:           walletInitParams.Wallet,
×
UNCOV
645
                LoaderOptions:    []btcwallet.LoaderOption{dbs.WalletDB},
×
UNCOV
646
                ChainSource:      partialChainControl.ChainSource,
×
UNCOV
647
                WatchOnly:        d.watchOnly,
×
UNCOV
648
                MigrateWatchOnly: d.migrateWatchOnly,
×
UNCOV
649
        }
×
UNCOV
650

×
UNCOV
651
        // Parse coin selection strategy.
×
UNCOV
652
        switch d.cfg.CoinSelectionStrategy {
×
UNCOV
653
        case "largest":
×
UNCOV
654
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
×
655

656
        case "random":
×
657
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
×
658

659
        default:
×
660
                return nil, nil, nil, fmt.Errorf("unknown coin selection "+
×
661
                        "strategy %v", d.cfg.CoinSelectionStrategy)
×
662
        }
663

UNCOV
664
        earlyExit = false
×
UNCOV
665
        return partialChainControl, walletConfig, cleanUp, nil
×
666
}
667

668
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
669
// rebroadcaster client.
670
func proxyBlockEpoch(
671
        notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
UNCOV
672
        error) {
×
UNCOV
673

×
UNCOV
674
        return func() (*blockntfns.Subscription, error) {
×
UNCOV
675
                blockEpoch, err := notifier.RegisterBlockEpochNtfn(
×
UNCOV
676
                        nil,
×
UNCOV
677
                )
×
UNCOV
678
                if err != nil {
×
679
                        return nil, err
×
680
                }
×
681

UNCOV
682
                sub := blockntfns.Subscription{
×
UNCOV
683
                        Notifications: make(chan blockntfns.BlockNtfn, 6),
×
UNCOV
684
                        Cancel:        blockEpoch.Cancel,
×
UNCOV
685
                }
×
UNCOV
686
                go func() {
×
UNCOV
687
                        for blk := range blockEpoch.Epochs {
×
UNCOV
688
                                ntfn := blockntfns.NewBlockConnected(
×
UNCOV
689
                                        *blk.BlockHeader,
×
UNCOV
690
                                        uint32(blk.Height),
×
UNCOV
691
                                )
×
UNCOV
692

×
UNCOV
693
                                sub.Notifications <- ntfn
×
UNCOV
694
                        }
×
695
                }()
696

UNCOV
697
                return &sub, nil
×
698
        }
699
}
700

701
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
702
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
703
type walletReBroadcaster struct {
704
        started atomic.Bool
705

706
        *pushtx.Broadcaster
707
}
708

709
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
710
func newWalletReBroadcaster(
UNCOV
711
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
×
UNCOV
712

×
UNCOV
713
        return &walletReBroadcaster{
×
UNCOV
714
                Broadcaster: broadcaster,
×
UNCOV
715
        }
×
UNCOV
716
}
×
717

718
// Start launches all goroutines the rebroadcaster needs to operate.
UNCOV
719
func (w *walletReBroadcaster) Start() error {
×
UNCOV
720
        defer w.started.Store(true)
×
UNCOV
721

×
UNCOV
722
        return w.Broadcaster.Start()
×
UNCOV
723
}
×
724

725
// Started returns true if the broadcaster is already active.
UNCOV
726
func (w *walletReBroadcaster) Started() bool {
×
UNCOV
727
        return w.started.Load()
×
UNCOV
728
}
×
729

730
// BuildChainControl is responsible for creating a fully populated chain
731
// control instance from a wallet.
732
//
733
// NOTE: This is part of the ChainControlBuilder interface.
734
func (d *DefaultWalletImpl) BuildChainControl(
735
        partialChainControl *chainreg.PartialChainControl,
UNCOV
736
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
UNCOV
737

×
UNCOV
738
        walletController, err := btcwallet.New(
×
UNCOV
739
                *walletConfig, partialChainControl.Cfg.BlockCache,
×
UNCOV
740
        )
×
UNCOV
741
        if err != nil {
×
742
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
743
                d.logger.Error(err)
×
744
                return nil, nil, err
×
745
        }
×
746

UNCOV
747
        keyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
748
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
749
        )
×
UNCOV
750

×
UNCOV
751
        // Create, and start the lnwallet, which handles the core payment
×
UNCOV
752
        // channel logic, and exposes control via proxy state machines.
×
UNCOV
753
        lnWalletConfig := lnwallet.Config{
×
UNCOV
754
                Database:              partialChainControl.Cfg.ChanStateDB,
×
UNCOV
755
                Notifier:              partialChainControl.ChainNotifier,
×
UNCOV
756
                WalletController:      walletController,
×
UNCOV
757
                Signer:                walletController,
×
UNCOV
758
                FeeEstimator:          partialChainControl.FeeEstimator,
×
UNCOV
759
                SecretKeyRing:         keyRing,
×
UNCOV
760
                ChainIO:               walletController,
×
UNCOV
761
                NetParams:             *walletConfig.NetParams,
×
UNCOV
762
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
763
                AuxLeafStore:          partialChainControl.Cfg.AuxLeafStore,
×
UNCOV
764
                AuxSigner:             partialChainControl.Cfg.AuxSigner,
×
UNCOV
765
        }
×
UNCOV
766

×
UNCOV
767
        // The broadcast is already always active for neutrino nodes, so we
×
UNCOV
768
        // don't want to create a rebroadcast loop.
×
UNCOV
769
        if partialChainControl.Cfg.NeutrinoCS == nil {
×
UNCOV
770
                cs := partialChainControl.ChainSource
×
UNCOV
771
                broadcastCfg := pushtx.Config{
×
UNCOV
772
                        Broadcast: func(tx *wire.MsgTx) error {
×
UNCOV
773
                                _, err := cs.SendRawTransaction(
×
UNCOV
774
                                        tx, true,
×
UNCOV
775
                                )
×
UNCOV
776

×
UNCOV
777
                                return err
×
UNCOV
778
                        },
×
779
                        SubscribeBlocks: proxyBlockEpoch(
780
                                partialChainControl.ChainNotifier,
781
                        ),
782
                        RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
783
                        // In case the backend is different from neutrino we
784
                        // make sure that broadcast backend errors are mapped
785
                        // to the neutrino broadcastErr.
UNCOV
786
                        MapCustomBroadcastError: func(err error) error {
×
UNCOV
787
                                rpcErr := cs.MapRPCErr(err)
×
UNCOV
788
                                return broadcastErrorMapper(rpcErr)
×
UNCOV
789
                        },
×
790
                }
791

UNCOV
792
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
×
UNCOV
793
                        pushtx.NewBroadcaster(&broadcastCfg),
×
UNCOV
794
                )
×
795
        }
796

797
        // We've created the wallet configuration now, so we can finish
798
        // initializing the main chain control.
UNCOV
799
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
×
UNCOV
800
                lnWalletConfig, walletController, partialChainControl,
×
UNCOV
801
        )
×
UNCOV
802
        if err != nil {
×
803
                err := fmt.Errorf("unable to create chain control: %w", err)
×
804
                d.logger.Error(err)
×
805
                return nil, nil, err
×
806
        }
×
807

UNCOV
808
        return activeChainControl, cleanUp, nil
×
809
}
810

811
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
812
// an RPC interface.
813
type RPCSignerWalletImpl struct {
814
        // DefaultWalletImpl is the embedded instance of the default
815
        // implementation that the remote signer uses as its watch-only wallet
816
        // for keeping track of addresses and UTXOs.
817
        *DefaultWalletImpl
818
}
819

820
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
821
// implementation.
822
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
823
        interceptor signal.Interceptor,
UNCOV
824
        migrateWatchOnly bool) *RPCSignerWalletImpl {
×
UNCOV
825

×
UNCOV
826
        return &RPCSignerWalletImpl{
×
UNCOV
827
                DefaultWalletImpl: &DefaultWalletImpl{
×
UNCOV
828
                        cfg:              cfg,
×
UNCOV
829
                        logger:           logger,
×
UNCOV
830
                        interceptor:      interceptor,
×
UNCOV
831
                        watchOnly:        true,
×
UNCOV
832
                        migrateWatchOnly: migrateWatchOnly,
×
UNCOV
833
                        pwService:        createWalletUnlockerService(cfg),
×
UNCOV
834
                },
×
UNCOV
835
        }
×
UNCOV
836
}
×
837

838
// BuildChainControl is responsible for creating or unlocking and then fully
839
// initializing a wallet and returning it as part of a fully populated chain
840
// control instance.
841
//
842
// NOTE: This is part of the ChainControlBuilder interface.
843
func (d *RPCSignerWalletImpl) BuildChainControl(
844
        partialChainControl *chainreg.PartialChainControl,
UNCOV
845
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
×
UNCOV
846

×
UNCOV
847
        walletController, err := btcwallet.New(
×
UNCOV
848
                *walletConfig, partialChainControl.Cfg.BlockCache,
×
UNCOV
849
        )
×
UNCOV
850
        if err != nil {
×
851
                err := fmt.Errorf("unable to create wallet controller: %w", err)
×
852
                d.logger.Error(err)
×
853
                return nil, nil, err
×
854
        }
×
855

UNCOV
856
        baseKeyRing := keychain.NewBtcWalletKeyRing(
×
UNCOV
857
                walletController.InternalWallet(), walletConfig.CoinType,
×
UNCOV
858
        )
×
UNCOV
859

×
UNCOV
860
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
×
UNCOV
861
                baseKeyRing, walletController,
×
UNCOV
862
                d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
×
UNCOV
863
        )
×
UNCOV
864
        if err != nil {
×
865
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
866
                        "%v", err)
×
867
                d.logger.Error(err)
×
868
                return nil, nil, err
×
869
        }
×
870

871
        // Create, and start the lnwallet, which handles the core payment
872
        // channel logic, and exposes control via proxy state machines.
UNCOV
873
        lnWalletConfig := lnwallet.Config{
×
UNCOV
874
                Database:              partialChainControl.Cfg.ChanStateDB,
×
UNCOV
875
                Notifier:              partialChainControl.ChainNotifier,
×
UNCOV
876
                WalletController:      rpcKeyRing,
×
UNCOV
877
                Signer:                rpcKeyRing,
×
UNCOV
878
                FeeEstimator:          partialChainControl.FeeEstimator,
×
UNCOV
879
                SecretKeyRing:         rpcKeyRing,
×
UNCOV
880
                ChainIO:               walletController,
×
UNCOV
881
                NetParams:             *walletConfig.NetParams,
×
UNCOV
882
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
×
UNCOV
883
        }
×
UNCOV
884

×
UNCOV
885
        // We've created the wallet configuration now, so we can finish
×
UNCOV
886
        // initializing the main chain control.
×
UNCOV
887
        activeChainControl, cleanUp, err := chainreg.NewChainControl(
×
UNCOV
888
                lnWalletConfig, rpcKeyRing, partialChainControl,
×
UNCOV
889
        )
×
UNCOV
890
        if err != nil {
×
891
                err := fmt.Errorf("unable to create chain control: %w", err)
×
892
                d.logger.Error(err)
×
893
                return nil, nil, err
×
894
        }
×
895

UNCOV
896
        return activeChainControl, cleanUp, nil
×
897
}
898

899
// DatabaseInstances is a struct that holds all instances to the actual
900
// databases that are used in lnd.
901
type DatabaseInstances struct {
902
        // GraphDB is the database that stores the channel graph used for path
903
        // finding.
904
        GraphDB *graphdb.ChannelGraph
905

906
        // ChanStateDB is the database that stores all of our node's channel
907
        // state.
908
        ChanStateDB *channeldb.DB
909

910
        // HeightHintDB is the database that stores height hints for spends.
911
        HeightHintDB kvdb.Backend
912

913
        // InvoiceDB is the database that stores information about invoices.
914
        InvoiceDB invoices.InvoiceDB
915

916
        // MacaroonDB is the database that stores macaroon root keys.
917
        MacaroonDB kvdb.Backend
918

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

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

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

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

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

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

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

×
UNCOV
954
        return &DefaultDatabaseBuilder{
×
UNCOV
955
                cfg:    cfg,
×
UNCOV
956
                logger: logger,
×
UNCOV
957
        }
×
UNCOV
958
}
×
959

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

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

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

UNCOV
976
        startOpenTime := time.Now()
×
UNCOV
977

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

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

UNCOV
1018
        graphDBOptions := []graphdb.OptionModifier{
×
UNCOV
1019
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
×
UNCOV
1020
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
×
UNCOV
1021
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
×
UNCOV
1022
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
×
UNCOV
1023
        }
×
UNCOV
1024

×
UNCOV
1025
        // We want to pre-allocate the channel graph cache according to what we
×
UNCOV
1026
        // expect for mainnet to speed up memory allocation.
×
UNCOV
1027
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
×
1028
                graphDBOptions = append(
×
1029
                        graphDBOptions, graphdb.WithPreAllocCacheNumNodes(
×
1030
                                graphdb.DefaultPreAllocCacheNumNodes,
×
1031
                        ),
×
1032
                )
×
1033
        }
×
1034

UNCOV
1035
        dbs.GraphDB, err = graphdb.NewChannelGraph(
×
UNCOV
1036
                databaseBackends.GraphDB, graphDBOptions...,
×
UNCOV
1037
        )
×
UNCOV
1038
        if err != nil {
×
1039
                cleanUp()
×
1040

×
1041
                err := fmt.Errorf("unable to open graph DB: %w", err)
×
1042
                d.logger.Error(err)
×
1043

×
1044
                return nil, nil, err
×
1045
        }
×
1046

UNCOV
1047
        dbOptions := []channeldb.OptionModifier{
×
UNCOV
1048
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
×
UNCOV
1049
                channeldb.OptionKeepFailedPaymentAttempts(
×
UNCOV
1050
                        cfg.KeepFailedPaymentAttempts,
×
UNCOV
1051
                ),
×
UNCOV
1052
                channeldb.OptionStoreFinalHtlcResolutions(
×
UNCOV
1053
                        cfg.StoreFinalHtlcResolutions,
×
UNCOV
1054
                ),
×
UNCOV
1055
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
×
UNCOV
1056
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
×
UNCOV
1057
        }
×
UNCOV
1058

×
UNCOV
1059
        // Otherwise, we'll open two instances, one for the state we only need
×
UNCOV
1060
        // locally, and the other for things we want to ensure are replicated.
×
UNCOV
1061
        dbs.ChanStateDB, err = channeldb.CreateWithBackend(
×
UNCOV
1062
                databaseBackends.ChanStateDB, dbOptions...,
×
UNCOV
1063
        )
×
UNCOV
1064
        switch {
×
1065
        // Give the DB a chance to dry run the migration. Since we know that
1066
        // both the channel state and graph DBs are still always behind the same
1067
        // backend, we know this would be applied to both of those DBs.
1068
        case err == channeldb.ErrDryRunMigrationOK:
×
1069
                d.logger.Infof("Channel DB dry run migration successful")
×
1070
                return nil, nil, err
×
1071

1072
        case err != nil:
×
1073
                cleanUp()
×
1074

×
1075
                err := fmt.Errorf("unable to open graph DB: %w", err)
×
1076
                d.logger.Error(err)
×
1077
                return nil, nil, err
×
1078
        }
1079

1080
        // Instantiate a native SQL invoice store if the flag is set.
UNCOV
1081
        if d.cfg.DB.UseNativeSQL {
×
1082
                // KV invoice db resides in the same database as the channel
×
1083
                // state DB. Let's query the database to see if we have any
×
1084
                // invoices there. If we do, we won't allow the user to start
×
1085
                // lnd with native SQL enabled, as we don't currently migrate
×
1086
                // the invoices to the new database schema.
×
1087
                invoiceSlice, err := dbs.ChanStateDB.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
                }
×
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
                }
×
1109

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

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

1124
        // Wrap the watchtower client DB and make sure we clean up.
UNCOV
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
                }
×
1137
        }
1138

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
                }
×
1152
        }
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
×
1158
}
1159

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

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

×
UNCOV
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 {
×
1177
        // The wallet is being created for the first time, we'll check to see
1178
        // if the user provided any entropy for seed creation. If so, then
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
×
UNCOV
1186

×
UNCOV
1187
                // Before we proceed, we'll check the internal version of the
×
UNCOV
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
                }
×
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
                }
×
1207

1208
                // With the seed, we can now use the wallet loader to create
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 {
×
1215
                // A normal cipher seed was given, use the birthday encoded in
1216
                // it and create the wallet from that.
UNCOV
1217
                case cipherSeed != nil:
×
UNCOV
1218
                        birthday = cipherSeed.BirthdayTime()
×
UNCOV
1219
                        newWallet, err = loader.CreateNewWallet(
×
UNCOV
1220
                                password, password, cipherSeed.Entropy[:],
×
UNCOV
1221
                                birthday,
×
UNCOV
1222
                        )
×
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
                        )
×
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

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

UNCOV
1250
                        err = importWatchOnlyAccounts(newWallet, initMsg)
×
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")
×
1259
                }
UNCOV
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
                }
1269

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
                }
×
1275

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

1288
        // The wallet has already been created in the past, and is simply being
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 {
×
UNCOV
1296
                        ltndLog.Warnf("Dropped all transaction history from " +
×
UNCOV
1297
                                "on-chain wallet. Remember to disable " +
×
UNCOV
1298
                                "reset-wallet-transactions flag for next " +
×
UNCOV
1299
                                "start of lnd")
×
UNCOV
1300
                }
×
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,
×
UNCOV
1310
                }, nil
×
1311

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

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

×
UNCOV
1323
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
×
UNCOV
1324
        for scope := range initMsg.WatchOnlyAccounts {
×
UNCOV
1325
                scopes = append(scopes, scope)
×
UNCOV
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
        })
×
1334

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

×
UNCOV
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 {
×
1342
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
UNCOV
1343
                        waddrmgr.KeyScopeBIP0086.Purpose:
×
UNCOV
1344

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

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

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

UNCOV
1367
        return nil
×
1368
}
1369

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

×
UNCOV
1376
        // Both channel validation flags are false by default but their meaning
×
UNCOV
1377
        // is the inverse of each other. Therefore both cannot be true. For
×
UNCOV
1378
        // every other case, the neutrino.validatechannels overwrites the
×
UNCOV
1379
        // routing.assumechanvalid value.
×
UNCOV
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
        }
×
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
                )
×
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
                )
×
1416
        }
UNCOV
1417
        if err != nil {
×
1418
                return nil, nil, fmt.Errorf("unable to create "+
×
1419
                        "neutrino database: %v", err)
×
1420
        }
×
1421

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

1430
        // With the database open, we can now create an instance of the
1431
        // neutrino light client. We pass in relevant configuration parameters
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,
×
UNCOV
1438
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
×
UNCOV
1439
                Dialer: func(addr net.Addr) (net.Conn, error) {
×
UNCOV
1440
                        return cfg.net.Dial(
×
UNCOV
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
                        }
×
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
×
1456
                                }
1457

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

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

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

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

UNCOV
1481
        if err := neutrinoCS.Start(); err != nil {
×
1482
                db.Close()
×
1483
                return nil, nil, err
×
1484
        }
×
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()
×
1492
        }
1493

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

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

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

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

1526
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
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
×
UNCOV
1533
        // Rebroadcaster.
×
UNCOV
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
                }
×
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
1556
        // publishing the transaction. Moreover we log the detailed error so the
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
×
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