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

lightningnetwork / lnd / 11963610117

21 Nov 2024 11:38PM UTC coverage: 59.117% (+0.1%) from 58.98%
11963610117

Pull #8754

github

ViktorTigerstrom
itest: wrap deriveCustomScopeAccounts at 80 chars

This commit fixes that word wrapping for the deriveCustomScopeAccounts
function docs, and ensures that it wraps at 80 characters or less.
Pull Request #8754: Add `Outbound` Remote Signer implementation

1950 of 2984 new or added lines in 44 files covered. (65.35%)

200 existing lines in 39 files now uncovered.

134504 of 227522 relevant lines covered (59.12%)

19449.04 hits per line

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

67.85
/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"
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/sweep"
54
        "github.com/lightningnetwork/lnd/walletunlocker"
55
        "github.com/lightningnetwork/lnd/watchtower"
56
        "github.com/lightningnetwork/lnd/watchtower/wtclient"
57
        "github.com/lightningnetwork/lnd/watchtower/wtdb"
58
        "google.golang.org/grpc"
59
        "gopkg.in/macaroon-bakery.v2/bakery"
60
)
61

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4✔
217
        return &DefaultWalletImpl{
4✔
218
                cfg:         cfg,
4✔
219
                logger:      logger,
4✔
220
                interceptor: interceptor,
4✔
221
                watchOnly:   watchOnly,
4✔
222
                pwService:   createWalletUnlockerService(cfg),
4✔
223
        }
4✔
224
}
4✔
225

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

4✔
235
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
4✔
236
                ctx, mux, restProxyDest, restDialOpts,
4✔
237
        )
4✔
238
}
4✔
239

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

4✔
248
        return nil
4✔
249
}
4✔
250

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

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

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

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

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

299
                                fn()
4✔
300
                        }
301
                }
302
        )
303
        defer func() {
8✔
304
                if earlyExit {
4✔
305
                        cleanUp()
×
306
                }
×
307
        }()
308

309
        // Initialize a new block cache.
310
        blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
4✔
311

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

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

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

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

353
        if !walletExists {
8✔
354
                interceptorChain.SetWalletNotCreated()
4✔
355
        } else {
8✔
356
                interceptorChain.SetWalletLocked()
4✔
357
        }
4✔
358

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

570
        // Now that the wallet password has been provided, transition the RPC
571
        // state into Unlocked.
572
        interceptorChain.SetWalletUnlocked()
4✔
573

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

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

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

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

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

4✔
650
        // Parse coin selection strategy.
4✔
651
        switch d.cfg.CoinSelectionStrategy {
4✔
652
        case "largest":
4✔
653
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
4✔
654

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

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

663
        earlyExit = false
4✔
664
        return partialChainControl, walletConfig, cleanUp, nil
4✔
665
}
666

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

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

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

3✔
692
                                sub.Notifications <- ntfn
3✔
693
                        }
3✔
694
                }()
695

696
                return &sub, nil
3✔
697
        }
698
}
699

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

705
        *pushtx.Broadcaster
706
}
707

708
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
709
func newWalletReBroadcaster(
710
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
3✔
711

3✔
712
        return &walletReBroadcaster{
3✔
713
                Broadcaster: broadcaster,
3✔
714
        }
3✔
715
}
3✔
716

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

3✔
721
        return w.Broadcaster.Start()
3✔
722
}
3✔
723

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

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

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

746
        keyRing := keychain.NewBtcWalletKeyRing(
4✔
747
                walletController.InternalWallet(), walletConfig.CoinType,
4✔
748
        )
4✔
749

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

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

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

791
                lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
3✔
792
                        pushtx.NewBroadcaster(&broadcastCfg),
3✔
793
                )
3✔
794
        }
795

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

807
        return activeChainControl, cleanUp, nil
4✔
808
}
809

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

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

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

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

4✔
846
        // Keeps track of both the remote signer and the chain control clean up
4✔
847
        // functions.
4✔
848
        var (
4✔
849
                cleanUpTasks []func()
4✔
850
                cleanUp      = func() {
8✔
851
                        for _, fn := range cleanUpTasks {
8✔
852
                                if fn == nil {
4✔
NEW
853
                                        continue
×
854
                                }
855

856
                                fn()
4✔
857
                        }
858
                }
859
        )
860

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

870
        remoteSignerBuilder := rpcwallet.NewRemoteSignerBuilder(
4✔
871
                d.DefaultWalletImpl.cfg.RemoteSigner,
4✔
872
        )
4✔
873

4✔
874
        // Create the remote signer instance. The remote signer instance type
4✔
875
        // will depend on the configuration passed to the builders contructor.
4✔
876
        remoteSigner, rsCleanUp, err := remoteSignerBuilder.Build()
4✔
877
        if err != nil {
4✔
NEW
878
                err := fmt.Errorf("unable to set up remote signer: %w", err)
×
NEW
879
                d.logger.Error(err)
×
NEW
880

×
NEW
881
                return nil, cleanUp, err
×
NEW
882
        }
×
883

884
        cleanUpTasks = append(cleanUpTasks, rsCleanUp)
4✔
885

4✔
886
        baseKeyRing := keychain.NewBtcWalletKeyRing(
4✔
887
                walletController.InternalWallet(), walletConfig.CoinType,
4✔
888
        )
4✔
889

4✔
890
        rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
4✔
891
                baseKeyRing, walletController,
4✔
892
                remoteSigner, walletConfig.NetParams,
4✔
893
        )
4✔
894
        if err != nil {
4✔
895
                err := fmt.Errorf("unable to create RPC remote signing wallet "+
×
896
                        "%v", err)
×
897
                d.logger.Error(err)
×
NEW
898

×
NEW
899
                return nil, cleanUp, err
×
UNCOV
900
        }
×
901

902
        // Create, and start the lnwallet, which handles the core payment
903
        // channel logic, and exposes control via proxy state machines.
904
        lnWalletConfig := lnwallet.Config{
4✔
905
                Database:              partialChainControl.Cfg.ChanStateDB,
4✔
906
                Notifier:              partialChainControl.ChainNotifier,
4✔
907
                WalletController:      rpcKeyRing,
4✔
908
                Signer:                rpcKeyRing,
4✔
909
                FeeEstimator:          partialChainControl.FeeEstimator,
4✔
910
                SecretKeyRing:         rpcKeyRing,
4✔
911
                ChainIO:               walletController,
4✔
912
                NetParams:             *walletConfig.NetParams,
4✔
913
                CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
4✔
914
        }
4✔
915

4✔
916
        // We've created the wallet configuration now, so we can finish
4✔
917
        // initializing the main chain control.
4✔
918
        activeChainControl, ccCleanUp, err := chainreg.NewChainControl(
4✔
919
                lnWalletConfig, rpcKeyRing, partialChainControl,
4✔
920
        )
4✔
921
        if err != nil {
4✔
922
                err := fmt.Errorf("unable to create chain control: %w", err)
×
923
                d.logger.Error(err)
×
NEW
924

×
NEW
925
                return nil, cleanUp, err
×
UNCOV
926
        }
×
927

928
        cleanUpTasks = append(cleanUpTasks, ccCleanUp)
4✔
929

4✔
930
        return activeChainControl, cleanUp, nil
4✔
931
}
932

933
// DatabaseInstances is a struct that holds all instances to the actual
934
// databases that are used in lnd.
935
type DatabaseInstances struct {
936
        // GraphDB is the database that stores the channel graph used for path
937
        // finding.
938
        //
939
        // NOTE/TODO: This currently _needs_ to be the same instance as the
940
        // ChanStateDB below until the separation of the two databases is fully
941
        // complete!
942
        GraphDB *channeldb.DB
943

944
        // ChanStateDB is the database that stores all of our node's channel
945
        // state.
946
        //
947
        // NOTE/TODO: This currently _needs_ to be the same instance as the
948
        // GraphDB above until the separation of the two databases is fully
949
        // complete!
950
        ChanStateDB *channeldb.DB
951

952
        // HeightHintDB is the database that stores height hints for spends.
953
        HeightHintDB kvdb.Backend
954

955
        // InvoiceDB is the database that stores information about invoices.
956
        InvoiceDB invoices.InvoiceDB
957

958
        // MacaroonDB is the database that stores macaroon root keys.
959
        MacaroonDB kvdb.Backend
960

961
        // DecayedLogDB is the database that stores p2p related encryption
962
        // information.
963
        DecayedLogDB kvdb.Backend
964

965
        // TowerClientDB is the database that stores the watchtower client's
966
        // configuration.
967
        TowerClientDB wtclient.DB
968

969
        // TowerServerDB is the database that stores the watchtower server's
970
        // configuration.
971
        TowerServerDB watchtower.DB
972

973
        // WalletDB is the configuration for loading the wallet database using
974
        // the btcwallet's loader.
975
        WalletDB btcwallet.LoaderOption
976

977
        // NativeSQLStore is a pointer to a native SQL store that can be used
978
        // for native SQL queries for tables that already support it. This may
979
        // be nil if the use-native-sql flag was not set.
980
        NativeSQLStore *sqldb.BaseDB
981
}
982

983
// DefaultDatabaseBuilder is a type that builds the default database backends
984
// for lnd, using the given configuration to decide what actual implementation
985
// to use.
986
type DefaultDatabaseBuilder struct {
987
        cfg    *Config
988
        logger btclog.Logger
989
}
990

991
// NewDefaultDatabaseBuilder returns a new instance of the default database
992
// builder.
993
func NewDefaultDatabaseBuilder(cfg *Config,
994
        logger btclog.Logger) *DefaultDatabaseBuilder {
4✔
995

4✔
996
        return &DefaultDatabaseBuilder{
4✔
997
                cfg:    cfg,
4✔
998
                logger: logger,
4✔
999
        }
4✔
1000
}
4✔
1001

1002
// BuildDatabase extracts the current databases that we'll use for normal
1003
// operation in the daemon. A function closure that closes all opened databases
1004
// is also returned.
1005
func (d *DefaultDatabaseBuilder) BuildDatabase(
1006
        ctx context.Context) (*DatabaseInstances, func(), error) {
4✔
1007

4✔
1008
        d.logger.Infof("Opening the main database, this might take a few " +
4✔
1009
                "minutes...")
4✔
1010

4✔
1011
        cfg := d.cfg
4✔
1012
        if cfg.DB.Backend == lncfg.BoltBackend {
8✔
1013
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
4✔
1014
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
4✔
1015
                        cfg.DB.Bolt.AutoCompact)
4✔
1016
        }
4✔
1017

1018
        startOpenTime := time.Now()
4✔
1019

4✔
1020
        databaseBackends, err := cfg.DB.GetBackends(
4✔
1021
                ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
4✔
1022
                        cfg.Watchtower.TowerDir, BitcoinChainName,
4✔
1023
                        lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
4✔
1024
                ), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
4✔
1025
        )
4✔
1026
        if err != nil {
4✔
1027
                return nil, nil, fmt.Errorf("unable to obtain database "+
×
1028
                        "backends: %v", err)
×
1029
        }
×
1030

1031
        // With the full remote mode we made sure both the graph and channel
1032
        // state DB point to the same local or remote DB and the same namespace
1033
        // within that DB.
1034
        dbs := &DatabaseInstances{
4✔
1035
                HeightHintDB:   databaseBackends.HeightHintDB,
4✔
1036
                MacaroonDB:     databaseBackends.MacaroonDB,
4✔
1037
                DecayedLogDB:   databaseBackends.DecayedLogDB,
4✔
1038
                WalletDB:       databaseBackends.WalletDB,
4✔
1039
                NativeSQLStore: databaseBackends.NativeSQLStore,
4✔
1040
        }
4✔
1041
        cleanUp := func() {
8✔
1042
                // We can just close the returned close functions directly. Even
4✔
1043
                // if we decorate the channel DB with an additional struct, its
4✔
1044
                // close function still just points to the kvdb backend.
4✔
1045
                for name, closeFunc := range databaseBackends.CloseFuncs {
8✔
1046
                        if err := closeFunc(); err != nil {
4✔
1047
                                d.logger.Errorf("Error closing %s "+
×
1048
                                        "database: %v", name, err)
×
1049
                        }
×
1050
                }
1051
        }
1052
        if databaseBackends.Remote {
4✔
1053
                d.logger.Infof("Using remote %v database! Creating "+
×
1054
                        "graph and channel state DB instances", cfg.DB.Backend)
×
1055
        } else {
4✔
1056
                d.logger.Infof("Creating local graph and channel state DB " +
4✔
1057
                        "instances")
4✔
1058
        }
4✔
1059

1060
        dbOptions := []channeldb.OptionModifier{
4✔
1061
                channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize),
4✔
1062
                channeldb.OptionSetChannelCacheSize(
4✔
1063
                        cfg.Caches.ChannelCacheSize,
4✔
1064
                ),
4✔
1065
                channeldb.OptionSetBatchCommitInterval(
4✔
1066
                        cfg.DB.BatchCommitInterval,
4✔
1067
                ),
4✔
1068
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
4✔
1069
                channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache),
4✔
1070
                channeldb.OptionKeepFailedPaymentAttempts(
4✔
1071
                        cfg.KeepFailedPaymentAttempts,
4✔
1072
                ),
4✔
1073
                channeldb.OptionStoreFinalHtlcResolutions(
4✔
1074
                        cfg.StoreFinalHtlcResolutions,
4✔
1075
                ),
4✔
1076
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
4✔
1077
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
4✔
1078
        }
4✔
1079

4✔
1080
        // We want to pre-allocate the channel graph cache according to what we
4✔
1081
        // expect for mainnet to speed up memory allocation.
4✔
1082
        if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
4✔
1083
                dbOptions = append(
×
1084
                        dbOptions, channeldb.OptionSetPreAllocCacheNumNodes(
×
1085
                                channeldb.DefaultPreAllocCacheNumNodes,
×
1086
                        ),
×
1087
                )
×
1088
        }
×
1089

1090
        // Otherwise, we'll open two instances, one for the state we only need
1091
        // locally, and the other for things we want to ensure are replicated.
1092
        dbs.GraphDB, err = channeldb.CreateWithBackend(
4✔
1093
                databaseBackends.GraphDB, dbOptions...,
4✔
1094
        )
4✔
1095
        switch {
4✔
1096
        // Give the DB a chance to dry run the migration. Since we know that
1097
        // both the channel state and graph DBs are still always behind the same
1098
        // backend, we know this would be applied to both of those DBs.
1099
        case err == channeldb.ErrDryRunMigrationOK:
×
1100
                d.logger.Infof("Graph DB dry run migration successful")
×
1101
                return nil, nil, err
×
1102

1103
        case err != nil:
×
1104
                cleanUp()
×
1105

×
1106
                err := fmt.Errorf("unable to open graph DB: %w", err)
×
1107
                d.logger.Error(err)
×
1108
                return nil, nil, err
×
1109
        }
1110

1111
        // For now, we don't _actually_ split the graph and channel state DBs on
1112
        // the code level. Since they both are based upon the *channeldb.DB
1113
        // struct it will require more refactoring to fully separate them. With
1114
        // the full remote mode we at least know for now that they both point to
1115
        // the same DB backend (and also namespace within that) so we only need
1116
        // to apply any migration once.
1117
        //
1118
        // TODO(guggero): Once the full separation of anything graph related
1119
        // from the channeldb.DB is complete, the decorated instance of the
1120
        // channel state DB should be created here individually instead of just
1121
        // using the same struct (and DB backend) instance.
1122
        dbs.ChanStateDB = dbs.GraphDB
4✔
1123

4✔
1124
        // Instantiate a native SQL invoice store if the flag is set.
4✔
1125
        if d.cfg.DB.UseNativeSQL {
4✔
1126
                // KV invoice db resides in the same database as the graph and
×
1127
                // channel state DB. Let's query the database to see if we have
×
1128
                // any invoices there. If we do, we won't allow the user to
×
1129
                // start lnd with native SQL enabled, as we don't currently
×
1130
                // migrate the invoices to the new database schema.
×
1131
                invoiceSlice, err := dbs.GraphDB.QueryInvoices(
×
1132
                        ctx, invoices.InvoiceQuery{
×
1133
                                NumMaxInvoices: 1,
×
1134
                        },
×
1135
                )
×
1136
                if err != nil {
×
1137
                        cleanUp()
×
1138
                        d.logger.Errorf("Unable to query KV invoice DB: %v",
×
1139
                                err)
×
1140

×
1141
                        return nil, nil, err
×
1142
                }
×
1143

1144
                if len(invoiceSlice.Invoices) > 0 {
×
1145
                        cleanUp()
×
1146
                        err := fmt.Errorf("found invoices in the KV invoice " +
×
1147
                                "DB, migration to native SQL is not yet " +
×
1148
                                "supported")
×
1149
                        d.logger.Error(err)
×
1150

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

1154
                executor := sqldb.NewTransactionExecutor(
×
1155
                        dbs.NativeSQLStore,
×
1156
                        func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1157
                                return dbs.NativeSQLStore.WithTx(tx)
×
1158
                        },
×
1159
                )
1160

1161
                dbs.InvoiceDB = invoices.NewSQLStore(
×
1162
                        executor, clock.NewDefaultClock(),
×
1163
                )
×
1164
        } else {
4✔
1165
                dbs.InvoiceDB = dbs.GraphDB
4✔
1166
        }
4✔
1167

1168
        // Wrap the watchtower client DB and make sure we clean up.
1169
        if cfg.WtClient.Active {
8✔
1170
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
4✔
1171
                        databaseBackends.TowerClientDB,
4✔
1172
                )
4✔
1173
                if err != nil {
4✔
1174
                        cleanUp()
×
1175

×
1176
                        err := fmt.Errorf("unable to open %s database: %w",
×
1177
                                lncfg.NSTowerClientDB, err)
×
1178
                        d.logger.Error(err)
×
1179
                        return nil, nil, err
×
1180
                }
×
1181
        }
1182

1183
        // Wrap the watchtower server DB and make sure we clean up.
1184
        if cfg.Watchtower.Active {
8✔
1185
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
4✔
1186
                        databaseBackends.TowerServerDB,
4✔
1187
                )
4✔
1188
                if err != nil {
4✔
1189
                        cleanUp()
×
1190

×
1191
                        err := fmt.Errorf("unable to open %s database: %w",
×
1192
                                lncfg.NSTowerServerDB, err)
×
1193
                        d.logger.Error(err)
×
1194
                        return nil, nil, err
×
1195
                }
×
1196
        }
1197

1198
        openTime := time.Since(startOpenTime)
4✔
1199
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
4✔
1200

4✔
1201
        return dbs, cleanUp, nil
4✔
1202
}
1203

1204
// waitForWalletPassword blocks until a password is provided by the user to
1205
// this RPC server.
1206
func waitForWalletPassword(cfg *Config,
1207
        pwService *walletunlocker.UnlockerService,
1208
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1209
        *walletunlocker.WalletUnlockParams, error) {
4✔
1210

4✔
1211
        // Wait for user to provide the password.
4✔
1212
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
4✔
1213
                "create` to create a wallet, `lncli unlock` to unlock an " +
4✔
1214
                "existing wallet, or `lncli changepassword` to change the " +
4✔
1215
                "password of an existing wallet and unlock it.")
4✔
1216

4✔
1217
        // We currently don't distinguish between getting a password to be used
4✔
1218
        // for creation or unlocking, as a new wallet db will be created if
4✔
1219
        // none exists when creating the chain control.
4✔
1220
        select {
4✔
1221
        // The wallet is being created for the first time, we'll check to see
1222
        // if the user provided any entropy for seed creation. If so, then
1223
        // we'll create the wallet early to load the seed.
1224
        case initMsg := <-pwService.InitMsgs:
4✔
1225
                password := initMsg.Passphrase
4✔
1226
                cipherSeed := initMsg.WalletSeed
4✔
1227
                extendedKey := initMsg.WalletExtendedKey
4✔
1228
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
4✔
1229
                recoveryWindow := initMsg.RecoveryWindow
4✔
1230

4✔
1231
                // Before we proceed, we'll check the internal version of the
4✔
1232
                // seed. If it's greater than the current key derivation
4✔
1233
                // version, then we'll return an error as we don't understand
4✔
1234
                // this.
4✔
1235
                if cipherSeed != nil &&
4✔
1236
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
4✔
1237

×
1238
                        return nil, fmt.Errorf("invalid internal "+
×
1239
                                "seed version %v, current max version is %v",
×
1240
                                cipherSeed.InternalVersion,
×
1241
                                keychain.CurrentKeyDerivationVersion)
×
1242
                }
×
1243

1244
                loader, err := btcwallet.NewWalletLoader(
4✔
1245
                        cfg.ActiveNetParams.Params, recoveryWindow,
4✔
1246
                        loaderOpts...,
4✔
1247
                )
4✔
1248
                if err != nil {
4✔
1249
                        return nil, err
×
1250
                }
×
1251

1252
                // With the seed, we can now use the wallet loader to create
1253
                // the wallet, then pass it back to avoid unlocking it again.
1254
                var (
4✔
1255
                        birthday  time.Time
4✔
1256
                        newWallet *wallet.Wallet
4✔
1257
                )
4✔
1258
                switch {
4✔
1259
                // A normal cipher seed was given, use the birthday encoded in
1260
                // it and create the wallet from that.
1261
                case cipherSeed != nil:
4✔
1262
                        birthday = cipherSeed.BirthdayTime()
4✔
1263
                        newWallet, err = loader.CreateNewWallet(
4✔
1264
                                password, password, cipherSeed.Entropy[:],
4✔
1265
                                birthday,
4✔
1266
                        )
4✔
1267

1268
                // No seed was given, we're importing a wallet from its extended
1269
                // private key.
1270
                case extendedKey != nil:
4✔
1271
                        birthday = initMsg.ExtendedKeyBirthday
4✔
1272
                        newWallet, err = loader.CreateNewWalletExtendedKey(
4✔
1273
                                password, password, extendedKey, birthday,
4✔
1274
                        )
4✔
1275

1276
                // Neither seed nor extended private key was given, so maybe the
1277
                // third option was chosen, the watch-only initialization. In
1278
                // this case we need to import each of the xpubs individually.
1279
                case watchOnlyAccounts != nil:
4✔
1280
                        if !cfg.RemoteSigner.Enable {
4✔
1281
                                return nil, fmt.Errorf("cannot initialize " +
×
1282
                                        "watch only wallet with remote " +
×
1283
                                        "signer config disabled")
×
1284
                        }
×
1285

1286
                        birthday = initMsg.WatchOnlyBirthday
4✔
1287
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
4✔
1288
                                password, birthday,
4✔
1289
                        )
4✔
1290
                        if err != nil {
4✔
1291
                                break
×
1292
                        }
1293

1294
                        err = importWatchOnlyAccounts(newWallet, initMsg)
4✔
1295

1296
                default:
×
1297
                        // The unlocker service made sure either the cipher seed
×
1298
                        // or the extended key is set so, we shouldn't get here.
×
1299
                        // The default case is just here for readability and
×
1300
                        // completeness.
×
1301
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1302
                                "nor extended key was given")
×
1303
                }
1304
                if err != nil {
4✔
1305
                        // Don't leave the file open in case the new wallet
×
1306
                        // could not be created for whatever reason.
×
1307
                        if err := loader.UnloadWallet(); err != nil {
×
1308
                                ltndLog.Errorf("Could not unload new "+
×
1309
                                        "wallet: %v", err)
×
1310
                        }
×
1311
                        return nil, err
×
1312
                }
1313

1314
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1315
                if cfg.ResetWalletTransactions {
8✔
1316
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
4✔
1317
                                "flag for new wallet as it has no effect")
4✔
1318
                }
4✔
1319

1320
                return &walletunlocker.WalletUnlockParams{
4✔
1321
                        Password:        password,
4✔
1322
                        Birthday:        birthday,
4✔
1323
                        RecoveryWindow:  recoveryWindow,
4✔
1324
                        Wallet:          newWallet,
4✔
1325
                        ChansToRestore:  initMsg.ChanBackups,
4✔
1326
                        UnloadWallet:    loader.UnloadWallet,
4✔
1327
                        StatelessInit:   initMsg.StatelessInit,
4✔
1328
                        MacResponseChan: pwService.MacResponseChan,
4✔
1329
                        MacRootKey:      initMsg.MacRootKey,
4✔
1330
                }, nil
4✔
1331

1332
        // The wallet has already been created in the past, and is simply being
1333
        // unlocked. So we'll just return these passphrases.
1334
        case unlockMsg := <-pwService.UnlockMsgs:
4✔
1335
                // Resetting the transactions is something the user likely only
4✔
1336
                // wants to do once so we add a prominent warning to the log to
4✔
1337
                // remind the user to turn off the setting again after
4✔
1338
                // successful completion.
4✔
1339
                if cfg.ResetWalletTransactions {
8✔
1340
                        ltndLog.Warnf("Dropped all transaction history from " +
4✔
1341
                                "on-chain wallet. Remember to disable " +
4✔
1342
                                "reset-wallet-transactions flag for next " +
4✔
1343
                                "start of lnd")
4✔
1344
                }
4✔
1345

1346
                return &walletunlocker.WalletUnlockParams{
4✔
1347
                        Password:        unlockMsg.Passphrase,
4✔
1348
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
4✔
1349
                        Wallet:          unlockMsg.Wallet,
4✔
1350
                        ChansToRestore:  unlockMsg.ChanBackups,
4✔
1351
                        UnloadWallet:    unlockMsg.UnloadWallet,
4✔
1352
                        StatelessInit:   unlockMsg.StatelessInit,
4✔
1353
                        MacResponseChan: pwService.MacResponseChan,
4✔
1354
                }, nil
4✔
1355

1356
        // If we got a shutdown signal we just return with an error immediately
1357
        case <-shutdownChan:
×
1358
                return nil, fmt.Errorf("shutting down")
×
1359
        }
1360
}
1361

1362
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1363
// which we created as watch-only.
1364
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1365
        initMsg *walletunlocker.WalletInitMsg) error {
4✔
1366

4✔
1367
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
4✔
1368
        for scope := range initMsg.WatchOnlyAccounts {
8✔
1369
                scopes = append(scopes, scope)
4✔
1370
        }
4✔
1371

1372
        // We need to import the accounts in the correct order, otherwise the
1373
        // indices will be incorrect.
1374
        sort.Slice(scopes, func(i, j int) bool {
8✔
1375
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
4✔
1376
                        scopes[i].Index < scopes[j].Index
4✔
1377
        })
4✔
1378

1379
        for _, scope := range scopes {
8✔
1380
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
4✔
1381

4✔
1382
                // We want witness pubkey hash by default, except for BIP49
4✔
1383
                // where we want mixed and BIP86 where we want taproot address
4✔
1384
                // formats.
4✔
1385
                switch scope.Scope.Purpose {
4✔
1386
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1387
                        waddrmgr.KeyScopeBIP0086.Purpose:
4✔
1388

4✔
1389
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
4✔
1390
                }
1391

1392
                // We want a human-readable account name. But for the default
1393
                // on-chain wallet we actually need to call it "default" to make
1394
                // sure everything works correctly.
1395
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
4✔
1396
                if scope.Index == 0 {
8✔
1397
                        name = "default"
4✔
1398
                }
4✔
1399

1400
                _, err := wallet.ImportAccountWithScope(
4✔
1401
                        name, initMsg.WatchOnlyAccounts[scope],
4✔
1402
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
4✔
1403
                        addrSchema,
4✔
1404
                )
4✔
1405
                if err != nil {
4✔
1406
                        return fmt.Errorf("could not import account %v: %w",
×
1407
                                name, err)
×
1408
                }
×
1409
        }
1410

1411
        return nil
4✔
1412
}
1413

1414
// initNeutrinoBackend inits a new instance of the neutrino light client
1415
// backend given a target chain directory to store the chain state.
1416
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1417
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
1418
        func(), error) {
1✔
1419

1✔
1420
        // Both channel validation flags are false by default but their meaning
1✔
1421
        // is the inverse of each other. Therefore both cannot be true. For
1✔
1422
        // every other case, the neutrino.validatechannels overwrites the
1✔
1423
        // routing.assumechanvalid value.
1✔
1424
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
1✔
1425
                return nil, nil, fmt.Errorf("can't set both " +
×
1426
                        "neutrino.validatechannels and routing." +
×
1427
                        "assumechanvalid to true at the same time")
×
1428
        }
×
1429
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
1✔
1430

1✔
1431
        // First we'll open the database file for neutrino, creating the
1✔
1432
        // database if needed. We append the normalized network name here to
1✔
1433
        // match the behavior of btcwallet.
1✔
1434
        dbPath := filepath.Join(
1✔
1435
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
1✔
1436
        )
1✔
1437

1✔
1438
        // Ensure that the neutrino db path exists.
1✔
1439
        if err := os.MkdirAll(dbPath, 0700); err != nil {
1✔
1440
                return nil, nil, err
×
1441
        }
×
1442

1443
        var (
1✔
1444
                db  walletdb.DB
1✔
1445
                err error
1✔
1446
        )
1✔
1447
        switch {
1✔
1448
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1449
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1450
                db, err = kvdb.Open(
×
1451
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1452
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1453
                )
×
1454

1455
        default:
1✔
1456
                dbName := filepath.Join(dbPath, "neutrino.db")
1✔
1457
                db, err = walletdb.Create(
1✔
1458
                        "bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
1✔
1459
                )
1✔
1460
        }
1461
        if err != nil {
1✔
1462
                return nil, nil, fmt.Errorf("unable to create "+
×
1463
                        "neutrino database: %v", err)
×
1464
        }
×
1465

1466
        headerStateAssertion, err := parseHeaderStateAssertion(
1✔
1467
                cfg.NeutrinoMode.AssertFilterHeader,
1✔
1468
        )
1✔
1469
        if err != nil {
1✔
1470
                db.Close()
×
1471
                return nil, nil, err
×
1472
        }
×
1473

1474
        // With the database open, we can now create an instance of the
1475
        // neutrino light client. We pass in relevant configuration parameters
1476
        // required.
1477
        config := neutrino.Config{
1✔
1478
                DataDir:      dbPath,
1✔
1479
                Database:     db,
1✔
1480
                ChainParams:  *cfg.ActiveNetParams.Params,
1✔
1481
                AddPeers:     cfg.NeutrinoMode.AddPeers,
1✔
1482
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
1✔
1483
                Dialer: func(addr net.Addr) (net.Conn, error) {
2✔
1484
                        return cfg.net.Dial(
1✔
1485
                                addr.Network(), addr.String(),
1✔
1486
                                cfg.ConnectionTimeout,
1✔
1487
                        )
1✔
1488
                },
1✔
1489
                NameResolver: func(host string) ([]net.IP, error) {
1✔
1490
                        addrs, err := cfg.net.LookupHost(host)
1✔
1491
                        if err != nil {
1✔
1492
                                return nil, err
×
1493
                        }
×
1494

1495
                        ips := make([]net.IP, 0, len(addrs))
1✔
1496
                        for _, strIP := range addrs {
2✔
1497
                                ip := net.ParseIP(strIP)
1✔
1498
                                if ip == nil {
1✔
1499
                                        continue
×
1500
                                }
1501

1502
                                ips = append(ips, ip)
1✔
1503
                        }
1504

1505
                        return ips, nil
1✔
1506
                },
1507
                AssertFilterHeader: headerStateAssertion,
1508
                BlockCache:         blockCache.Cache,
1509
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1510
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1511
        }
1512

1513
        neutrino.MaxPeers = 8
1✔
1514
        neutrino.BanDuration = time.Hour * 48
1✔
1515
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
1✔
1516
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
1✔
1517

1✔
1518
        neutrinoCS, err := neutrino.NewChainService(config)
1✔
1519
        if err != nil {
1✔
1520
                db.Close()
×
1521
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1522
                        "client: %v", err)
×
1523
        }
×
1524

1525
        if err := neutrinoCS.Start(); err != nil {
1✔
1526
                db.Close()
×
1527
                return nil, nil, err
×
1528
        }
×
1529

1530
        cleanUp := func() {
2✔
1531
                if err := neutrinoCS.Stop(); err != nil {
1✔
1532
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1533
                                "%v", err)
×
1534
                }
×
1535
                db.Close()
1✔
1536
        }
1537

1538
        return neutrinoCS, cleanUp, nil
1✔
1539
}
1540

1541
// parseHeaderStateAssertion parses the user-specified neutrino header state
1542
// into a headerfs.FilterHeader.
1543
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
1✔
1544
        if len(state) == 0 {
2✔
1545
                return nil, nil
1✔
1546
        }
1✔
1547

1548
        split := strings.Split(state, ":")
×
1549
        if len(split) != 2 {
×
1550
                return nil, fmt.Errorf("header state assertion %v in "+
×
1551
                        "unexpected format, expected format height:hash", state)
×
1552
        }
×
1553

1554
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1555
        if err != nil {
×
1556
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1557
        }
×
1558

1559
        hash, err := chainhash.NewHashFromStr(split[1])
×
1560
        if err != nil {
×
1561
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1562
        }
×
1563

1564
        return &headerfs.FilterHeader{
×
1565
                Height:     uint32(height),
×
1566
                FilterHash: *hash,
×
1567
        }, nil
×
1568
}
1569

1570
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1571
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1572
// resides in the neutrino package to use all of its functionalities.
1573
func broadcastErrorMapper(err error) error {
3✔
1574
        var returnErr error
3✔
1575

3✔
1576
        // We only filter for specific backend errors which are relevant for the
3✔
1577
        // Rebroadcaster.
3✔
1578
        switch {
3✔
1579
        // This makes sure the tx is removed from the rebroadcaster once it is
1580
        // confirmed.
1581
        case errors.Is(err, chain.ErrTxAlreadyKnown),
1582
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
2✔
1583

2✔
1584
                returnErr = &pushtx.BroadcastError{
2✔
1585
                        Code:   pushtx.Confirmed,
2✔
1586
                        Reason: err.Error(),
2✔
1587
                }
2✔
1588

1589
        // Transactions which are still in mempool but might fall out because
1590
        // of low fees are rebroadcasted despite of their backend error.
1591
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1592
                returnErr = &pushtx.BroadcastError{
×
1593
                        Code:   pushtx.Mempool,
×
1594
                        Reason: err.Error(),
×
1595
                }
×
1596

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

×
1605
                returnErr = &pushtx.BroadcastError{
×
1606
                        Code:   pushtx.Mempool,
×
1607
                        Reason: err.Error(),
×
1608
                }
×
1609
        }
1610

1611
        return returnErr
3✔
1612
}
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