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

lightningnetwork / lnd / 12412879685

19 Dec 2024 12:40PM UTC coverage: 58.744% (+0.09%) from 58.653%
12412879685

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

1858 of 2816 new or added lines in 47 files covered. (65.98%)

267 existing lines in 51 files now uncovered.

136038 of 231578 relevant lines covered (58.74%)

19020.65 hits per line

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

67.53
/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,
216
        interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
3✔
217

3✔
218
        return &DefaultWalletImpl{
3✔
219
                cfg:         cfg,
3✔
220
                logger:      logger,
3✔
221
                interceptor: interceptor,
3✔
222
                watchOnly:   watchOnly,
3✔
223
                pwService:   createWalletUnlockerService(cfg),
3✔
224
        }
3✔
225
}
3✔
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,
234
        restDialOpts []grpc.DialOption) error {
3✔
235

3✔
236
        return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
3✔
237
                ctx, mux, restProxyDest, restDialOpts,
3✔
238
        )
3✔
239
}
3✔
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.
246
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
3✔
247
        lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
3✔
248

3✔
249
        return nil
3✔
250
}
3✔
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.
275
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
3✔
276
        return nil
3✔
277
}
3✔
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,
287
        *btcwallet.Config, func(), error) {
3✔
288

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

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

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

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

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

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

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

354
        if !walletExists {
6✔
355
                interceptorChain.SetWalletNotCreated()
3✔
356
        } else {
6✔
357
                interceptorChain.SetWalletLocked()
3✔
358
        }
3✔
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.
365
        if d.cfg.WalletUnlockPasswordFile != "" && !walletExists &&
3✔
366
                !d.cfg.WalletUnlockAllowCreate {
3✔
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.
375
        switch {
3✔
376
        // No seed backup means we're also using the default password.
377
        case d.cfg.NoSeedBackup:
3✔
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.
422
        default:
3✔
423
                if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
3✔
424
                        return nil, nil, nil, err
×
425
                }
×
426

427
                params, err := waitForWalletPassword(
3✔
428
                        d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
3✔
429
                        d.interceptor.ShutdownChannel(),
3✔
430
                )
3✔
431
                if err != nil {
3✔
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

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

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

455
        var macaroonService *macaroons.Service
3✔
456
        if !d.cfg.NoMacaroons {
6✔
457
                // Create the macaroon authentication/authorization service.
3✔
458
                rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
3✔
459
                if err != nil {
3✔
460
                        return nil, nil, nil, err
×
461
                }
×
462
                macaroonService, err = macaroons.NewService(
3✔
463
                        rootKeyStore, "lnd", walletInitParams.StatelessInit,
3✔
464
                        macaroons.IPLockChecker,
3✔
465
                        macaroons.CustomChecker(interceptorChain),
3✔
466
                )
3✔
467
                if err != nil {
3✔
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
                }
×
473
                cleanUpTasks = append(cleanUpTasks, func() {
6✔
474
                        if err := macaroonService.Close(); err != nil {
3✔
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.
483
                err = macaroonService.CreateUnlock(&privateWalletPw)
3✔
484
                if err != nil && err != macaroons.ErrAlreadyUnlocked {
3✔
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.
492
                if len(walletInitParams.MacRootKey) > 0 {
3✔
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.
503
                adminMacBytes, err := bakeMacaroon(
3✔
504
                        ctx, macaroonService, adminPermissions(),
3✔
505
                )
3✔
506
                if err != nil {
3✔
507
                        return nil, nil, nil, err
×
508
                }
×
509
                for _, lis := range grpcListeners {
6✔
510
                        if lis.MacChan != nil {
3✔
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.
520
                if !d.cfg.NoSeedBackup {
6✔
521
                        // The channel is buffered by one element so writing
3✔
522
                        // should not block here.
3✔
523
                        walletInitParams.MacResponseChan <- adminMacBytes
3✔
524
                }
3✔
525

526
                // If the user requested a stateless initialization, no macaroon
527
                // files should be created.
528
                if !walletInitParams.StatelessInit {
6✔
529
                        // Create default macaroon files for lncli to use if
3✔
530
                        // they don't exist.
3✔
531
                        err = genDefaultMacaroons(
3✔
532
                                ctx, macaroonService, d.cfg.AdminMacPath,
3✔
533
                                d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
3✔
534
                        )
3✔
535
                        if err != nil {
3✔
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.
546
                if walletInitParams.StatelessInit {
6✔
547
                        msg := "Found %s macaroon on disk (%s) even though " +
3✔
548
                                "--stateless_init was requested. Unencrypted " +
3✔
549
                                "state is accessible by the host system. You " +
3✔
550
                                "should change the password and use " +
3✔
551
                                "--new_mac_root_key with --stateless_init to " +
3✔
552
                                "clean up and invalidate old macaroons."
3✔
553

3✔
554
                        if lnrpc.FileExists(d.cfg.AdminMacPath) {
3✔
555
                                d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
×
556
                        }
×
557
                        if lnrpc.FileExists(d.cfg.ReadMacPath) {
3✔
558
                                d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
×
559
                        }
×
560
                        if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
3✔
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.
568
                interceptorChain.AddMacaroonService(macaroonService)
3✔
569
        }
570

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

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

3✔
581
        // We'll also close all the macaroon channels since lnd is done sending
3✔
582
        // macaroon data over it.
3✔
583
        for _, lis := range grpcListeners {
6✔
584
                if lis.MacChan != nil {
3✔
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.
596
        chainControlCfg := &chainreg.Config{
3✔
597
                Bitcoin:                     d.cfg.Bitcoin,
3✔
598
                HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable,
3✔
599
                NeutrinoMode:                d.cfg.NeutrinoMode,
3✔
600
                BitcoindMode:                d.cfg.BitcoindMode,
3✔
601
                BtcdMode:                    d.cfg.BtcdMode,
3✔
602
                HeightHintDB:                dbs.HeightHintDB,
3✔
603
                ChanStateDB:                 dbs.ChanStateDB.ChannelStateDB(),
3✔
604
                NeutrinoCS:                  neutrinoCS,
3✔
605
                AuxLeafStore:                aux.AuxLeafStore,
3✔
606
                AuxSigner:                   aux.AuxSigner,
3✔
607
                ActiveNetParams:             d.cfg.ActiveNetParams,
3✔
608
                FeeURL:                      d.cfg.FeeURL,
3✔
609
                Fee: &lncfg.Fee{
3✔
610
                        URL:              d.cfg.Fee.URL,
3✔
611
                        MinUpdateTimeout: d.cfg.Fee.MinUpdateTimeout,
3✔
612
                        MaxUpdateTimeout: d.cfg.Fee.MaxUpdateTimeout,
3✔
613
                },
3✔
614
                Dialer: func(addr string) (net.Conn, error) {
3✔
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.
626
        partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
3✔
627
                chainControlCfg,
3✔
628
        )
3✔
629
        cleanUpTasks = append(cleanUpTasks, pccCleanup)
3✔
630
        if err != nil {
3✔
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

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

3✔
651
        // Parse coin selection strategy.
3✔
652
        switch d.cfg.CoinSelectionStrategy {
3✔
653
        case "largest":
3✔
654
                walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
3✔
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

664
        earlyExit = false
3✔
665
        return partialChainControl, walletConfig, cleanUp, nil
3✔
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,
672
        error) {
2✔
673

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

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

2✔
693
                                sub.Notifications <- ntfn
2✔
694
                        }
2✔
695
                }()
696

697
                return &sub, nil
2✔
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(
711
        broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
2✔
712

2✔
713
        return &walletReBroadcaster{
2✔
714
                Broadcaster: broadcaster,
2✔
715
        }
2✔
716
}
2✔
717

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

2✔
722
        return w.Broadcaster.Start()
2✔
723
}
2✔
724

725
// Started returns true if the broadcaster is already active.
726
func (w *walletReBroadcaster) Started() bool {
2✔
727
        return w.started.Load()
2✔
728
}
2✔
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,
736
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
737

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

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

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

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

2✔
777
                                return err
2✔
778
                        },
2✔
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.
786
                        MapCustomBroadcastError: func(err error) error {
2✔
787
                                rpcErr := cs.MapRPCErr(err)
2✔
788
                                return broadcastErrorMapper(rpcErr)
2✔
789
                        },
2✔
790
                }
791

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

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

808
        return activeChainControl, cleanUp, nil
3✔
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,
824
        migrateWatchOnly bool) *RPCSignerWalletImpl {
3✔
825

3✔
826
        return &RPCSignerWalletImpl{
3✔
827
                DefaultWalletImpl: &DefaultWalletImpl{
3✔
828
                        cfg:              cfg,
3✔
829
                        logger:           logger,
3✔
830
                        interceptor:      interceptor,
3✔
831
                        watchOnly:        true,
3✔
832
                        migrateWatchOnly: migrateWatchOnly,
3✔
833
                        pwService:        createWalletUnlockerService(cfg),
3✔
834
                },
3✔
835
        }
3✔
836
}
3✔
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,
845
        walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
3✔
846

3✔
847
        // Keeps track of both the remote signer and the chain control clean up
3✔
848
        // functions.
3✔
849
        var (
3✔
850
                cleanUpTasks []func()
3✔
851
                cleanUp      = func() {
6✔
852
                        for _, fn := range cleanUpTasks {
6✔
853
                                fn()
3✔
854
                        }
3✔
855
                }
856
        )
857

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

867
        remoteSignerConnBuilder := rpcwallet.NewRemoteSignerConnectionBuilder(
3✔
868
                d.DefaultWalletImpl.cfg.RemoteSigner,
3✔
869
        )
3✔
870

3✔
871
        // Create the remote signer connection instance.
3✔
872
        remoteSignerConn, err := remoteSignerConnBuilder.Build(
3✔
873
                context.Background(),
3✔
874
        )
3✔
875
        if err != nil {
3✔
NEW
876
                err := fmt.Errorf("unable to set up remote signer: %w", err)
×
NEW
877
                d.logger.Error(err)
×
NEW
878

×
NEW
879
                return nil, cleanUp, err
×
NEW
880
        }
×
881

882
        cleanUpTasks = append(cleanUpTasks, remoteSignerConn.Stop)
3✔
883

3✔
884
        baseKeyRing := keychain.NewBtcWalletKeyRing(
3✔
885
                walletController.InternalWallet(), walletConfig.CoinType,
3✔
886
        )
3✔
887

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

×
NEW
897
                return nil, cleanUp, err
×
UNCOV
898
        }
×
899

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

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

×
NEW
923
                return nil, cleanUp, err
×
UNCOV
924
        }
×
925

926
        cleanUpTasks = append(cleanUpTasks, ccCleanUp)
3✔
927

3✔
928
        return activeChainControl, cleanUp, nil
3✔
929
}
930

931
// DatabaseInstances is a struct that holds all instances to the actual
932
// databases that are used in lnd.
933
type DatabaseInstances struct {
934
        // GraphDB is the database that stores the channel graph used for path
935
        // finding.
936
        GraphDB *graphdb.ChannelGraph
937

938
        // ChanStateDB is the database that stores all of our node's channel
939
        // state.
940
        ChanStateDB *channeldb.DB
941

942
        // HeightHintDB is the database that stores height hints for spends.
943
        HeightHintDB kvdb.Backend
944

945
        // InvoiceDB is the database that stores information about invoices.
946
        InvoiceDB invoices.InvoiceDB
947

948
        // MacaroonDB is the database that stores macaroon root keys.
949
        MacaroonDB kvdb.Backend
950

951
        // DecayedLogDB is the database that stores p2p related encryption
952
        // information.
953
        DecayedLogDB kvdb.Backend
954

955
        // TowerClientDB is the database that stores the watchtower client's
956
        // configuration.
957
        TowerClientDB wtclient.DB
958

959
        // TowerServerDB is the database that stores the watchtower server's
960
        // configuration.
961
        TowerServerDB watchtower.DB
962

963
        // WalletDB is the configuration for loading the wallet database using
964
        // the btcwallet's loader.
965
        WalletDB btcwallet.LoaderOption
966

967
        // NativeSQLStore is a pointer to a native SQL store that can be used
968
        // for native SQL queries for tables that already support it. This may
969
        // be nil if the use-native-sql flag was not set.
970
        NativeSQLStore *sqldb.BaseDB
971
}
972

973
// DefaultDatabaseBuilder is a type that builds the default database backends
974
// for lnd, using the given configuration to decide what actual implementation
975
// to use.
976
type DefaultDatabaseBuilder struct {
977
        cfg    *Config
978
        logger btclog.Logger
979
}
980

981
// NewDefaultDatabaseBuilder returns a new instance of the default database
982
// builder.
983
func NewDefaultDatabaseBuilder(cfg *Config,
984
        logger btclog.Logger) *DefaultDatabaseBuilder {
3✔
985

3✔
986
        return &DefaultDatabaseBuilder{
3✔
987
                cfg:    cfg,
3✔
988
                logger: logger,
3✔
989
        }
3✔
990
}
3✔
991

992
// BuildDatabase extracts the current databases that we'll use for normal
993
// operation in the daemon. A function closure that closes all opened databases
994
// is also returned.
995
func (d *DefaultDatabaseBuilder) BuildDatabase(
996
        ctx context.Context) (*DatabaseInstances, func(), error) {
3✔
997

3✔
998
        d.logger.Infof("Opening the main database, this might take a few " +
3✔
999
                "minutes...")
3✔
1000

3✔
1001
        cfg := d.cfg
3✔
1002
        if cfg.DB.Backend == lncfg.BoltBackend {
6✔
1003
                d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
3✔
1004
                        "auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
3✔
1005
                        cfg.DB.Bolt.AutoCompact)
3✔
1006
        }
3✔
1007

1008
        startOpenTime := time.Now()
3✔
1009

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

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

1050
        graphDBOptions := []graphdb.OptionModifier{
3✔
1051
                graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
3✔
1052
                graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
3✔
1053
                graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
3✔
1054
                graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
3✔
1055
        }
3✔
1056

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

1067
        dbs.GraphDB, err = graphdb.NewChannelGraph(
3✔
1068
                databaseBackends.GraphDB, graphDBOptions...,
3✔
1069
        )
3✔
1070
        if err != nil {
3✔
1071
                cleanUp()
×
1072

×
1073
                err := fmt.Errorf("unable to open graph DB: %w", err)
×
1074
                d.logger.Error(err)
×
1075

×
1076
                return nil, nil, err
×
1077
        }
×
1078

1079
        dbOptions := []channeldb.OptionModifier{
3✔
1080
                channeldb.OptionDryRunMigration(cfg.DryRunMigration),
3✔
1081
                channeldb.OptionKeepFailedPaymentAttempts(
3✔
1082
                        cfg.KeepFailedPaymentAttempts,
3✔
1083
                ),
3✔
1084
                channeldb.OptionStoreFinalHtlcResolutions(
3✔
1085
                        cfg.StoreFinalHtlcResolutions,
3✔
1086
                ),
3✔
1087
                channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
3✔
1088
                channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
3✔
1089
        }
3✔
1090

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

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

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

1112
        // Instantiate a native SQL invoice store if the flag is set.
1113
        if d.cfg.DB.UseNativeSQL {
3✔
1114
                // KV invoice db resides in the same database as the channel
×
1115
                // state DB. Let's query the database to see if we have any
×
1116
                // invoices there. If we do, we won't allow the user to start
×
1117
                // lnd with native SQL enabled, as we don't currently migrate
×
1118
                // the invoices to the new database schema.
×
1119
                invoiceSlice, err := dbs.ChanStateDB.QueryInvoices(
×
1120
                        ctx, invoices.InvoiceQuery{
×
1121
                                NumMaxInvoices: 1,
×
1122
                        },
×
1123
                )
×
1124
                if err != nil {
×
1125
                        cleanUp()
×
1126
                        d.logger.Errorf("Unable to query KV invoice DB: %v",
×
1127
                                err)
×
1128

×
1129
                        return nil, nil, err
×
1130
                }
×
1131

1132
                if len(invoiceSlice.Invoices) > 0 {
×
1133
                        cleanUp()
×
1134
                        err := fmt.Errorf("found invoices in the KV invoice " +
×
1135
                                "DB, migration to native SQL is not yet " +
×
1136
                                "supported")
×
1137
                        d.logger.Error(err)
×
1138

×
1139
                        return nil, nil, err
×
1140
                }
×
1141

1142
                executor := sqldb.NewTransactionExecutor(
×
1143
                        dbs.NativeSQLStore,
×
1144
                        func(tx *sql.Tx) invoices.SQLInvoiceQueries {
×
1145
                                return dbs.NativeSQLStore.WithTx(tx)
×
1146
                        },
×
1147
                )
1148

1149
                dbs.InvoiceDB = invoices.NewSQLStore(
×
1150
                        executor, clock.NewDefaultClock(),
×
1151
                )
×
1152
        } else {
3✔
1153
                dbs.InvoiceDB = dbs.ChanStateDB
3✔
1154
        }
3✔
1155

1156
        // Wrap the watchtower client DB and make sure we clean up.
1157
        if cfg.WtClient.Active {
6✔
1158
                dbs.TowerClientDB, err = wtdb.OpenClientDB(
3✔
1159
                        databaseBackends.TowerClientDB,
3✔
1160
                )
3✔
1161
                if err != nil {
3✔
1162
                        cleanUp()
×
1163

×
1164
                        err := fmt.Errorf("unable to open %s database: %w",
×
1165
                                lncfg.NSTowerClientDB, err)
×
1166
                        d.logger.Error(err)
×
1167
                        return nil, nil, err
×
1168
                }
×
1169
        }
1170

1171
        // Wrap the watchtower server DB and make sure we clean up.
1172
        if cfg.Watchtower.Active {
6✔
1173
                dbs.TowerServerDB, err = wtdb.OpenTowerDB(
3✔
1174
                        databaseBackends.TowerServerDB,
3✔
1175
                )
3✔
1176
                if err != nil {
3✔
1177
                        cleanUp()
×
1178

×
1179
                        err := fmt.Errorf("unable to open %s database: %w",
×
1180
                                lncfg.NSTowerServerDB, err)
×
1181
                        d.logger.Error(err)
×
1182
                        return nil, nil, err
×
1183
                }
×
1184
        }
1185

1186
        openTime := time.Since(startOpenTime)
3✔
1187
        d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
3✔
1188

3✔
1189
        return dbs, cleanUp, nil
3✔
1190
}
1191

1192
// waitForWalletPassword blocks until a password is provided by the user to
1193
// this RPC server.
1194
func waitForWalletPassword(cfg *Config,
1195
        pwService *walletunlocker.UnlockerService,
1196
        loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
1197
        *walletunlocker.WalletUnlockParams, error) {
3✔
1198

3✔
1199
        // Wait for user to provide the password.
3✔
1200
        ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
3✔
1201
                "create` to create a wallet, `lncli unlock` to unlock an " +
3✔
1202
                "existing wallet, or `lncli changepassword` to change the " +
3✔
1203
                "password of an existing wallet and unlock it.")
3✔
1204

3✔
1205
        // We currently don't distinguish between getting a password to be used
3✔
1206
        // for creation or unlocking, as a new wallet db will be created if
3✔
1207
        // none exists when creating the chain control.
3✔
1208
        select {
3✔
1209
        // The wallet is being created for the first time, we'll check to see
1210
        // if the user provided any entropy for seed creation. If so, then
1211
        // we'll create the wallet early to load the seed.
1212
        case initMsg := <-pwService.InitMsgs:
3✔
1213
                password := initMsg.Passphrase
3✔
1214
                cipherSeed := initMsg.WalletSeed
3✔
1215
                extendedKey := initMsg.WalletExtendedKey
3✔
1216
                watchOnlyAccounts := initMsg.WatchOnlyAccounts
3✔
1217
                recoveryWindow := initMsg.RecoveryWindow
3✔
1218

3✔
1219
                // Before we proceed, we'll check the internal version of the
3✔
1220
                // seed. If it's greater than the current key derivation
3✔
1221
                // version, then we'll return an error as we don't understand
3✔
1222
                // this.
3✔
1223
                if cipherSeed != nil &&
3✔
1224
                        !keychain.IsKnownVersion(cipherSeed.InternalVersion) {
3✔
1225

×
1226
                        return nil, fmt.Errorf("invalid internal "+
×
1227
                                "seed version %v, current max version is %v",
×
1228
                                cipherSeed.InternalVersion,
×
1229
                                keychain.CurrentKeyDerivationVersion)
×
1230
                }
×
1231

1232
                loader, err := btcwallet.NewWalletLoader(
3✔
1233
                        cfg.ActiveNetParams.Params, recoveryWindow,
3✔
1234
                        loaderOpts...,
3✔
1235
                )
3✔
1236
                if err != nil {
3✔
1237
                        return nil, err
×
1238
                }
×
1239

1240
                // With the seed, we can now use the wallet loader to create
1241
                // the wallet, then pass it back to avoid unlocking it again.
1242
                var (
3✔
1243
                        birthday  time.Time
3✔
1244
                        newWallet *wallet.Wallet
3✔
1245
                )
3✔
1246
                switch {
3✔
1247
                // A normal cipher seed was given, use the birthday encoded in
1248
                // it and create the wallet from that.
1249
                case cipherSeed != nil:
3✔
1250
                        birthday = cipherSeed.BirthdayTime()
3✔
1251
                        newWallet, err = loader.CreateNewWallet(
3✔
1252
                                password, password, cipherSeed.Entropy[:],
3✔
1253
                                birthday,
3✔
1254
                        )
3✔
1255

1256
                // No seed was given, we're importing a wallet from its extended
1257
                // private key.
1258
                case extendedKey != nil:
3✔
1259
                        birthday = initMsg.ExtendedKeyBirthday
3✔
1260
                        newWallet, err = loader.CreateNewWalletExtendedKey(
3✔
1261
                                password, password, extendedKey, birthday,
3✔
1262
                        )
3✔
1263

1264
                // Neither seed nor extended private key was given, so maybe the
1265
                // third option was chosen, the watch-only initialization. In
1266
                // this case we need to import each of the xpubs individually.
1267
                case watchOnlyAccounts != nil:
3✔
1268
                        if !cfg.RemoteSigner.Enable {
3✔
1269
                                return nil, fmt.Errorf("cannot initialize " +
×
1270
                                        "watch only wallet with remote " +
×
1271
                                        "signer config disabled")
×
1272
                        }
×
1273

1274
                        birthday = initMsg.WatchOnlyBirthday
3✔
1275
                        newWallet, err = loader.CreateNewWatchingOnlyWallet(
3✔
1276
                                password, birthday,
3✔
1277
                        )
3✔
1278
                        if err != nil {
3✔
1279
                                break
×
1280
                        }
1281

1282
                        err = importWatchOnlyAccounts(newWallet, initMsg)
3✔
1283

1284
                default:
×
1285
                        // The unlocker service made sure either the cipher seed
×
1286
                        // or the extended key is set so, we shouldn't get here.
×
1287
                        // The default case is just here for readability and
×
1288
                        // completeness.
×
1289
                        err = fmt.Errorf("cannot create wallet, neither seed " +
×
1290
                                "nor extended key was given")
×
1291
                }
1292
                if err != nil {
3✔
1293
                        // Don't leave the file open in case the new wallet
×
1294
                        // could not be created for whatever reason.
×
1295
                        if err := loader.UnloadWallet(); err != nil {
×
1296
                                ltndLog.Errorf("Could not unload new "+
×
1297
                                        "wallet: %v", err)
×
1298
                        }
×
1299
                        return nil, err
×
1300
                }
1301

1302
                // For new wallets, the ResetWalletTransactions flag is a no-op.
1303
                if cfg.ResetWalletTransactions {
6✔
1304
                        ltndLog.Warnf("Ignoring reset-wallet-transactions " +
3✔
1305
                                "flag for new wallet as it has no effect")
3✔
1306
                }
3✔
1307

1308
                return &walletunlocker.WalletUnlockParams{
3✔
1309
                        Password:        password,
3✔
1310
                        Birthday:        birthday,
3✔
1311
                        RecoveryWindow:  recoveryWindow,
3✔
1312
                        Wallet:          newWallet,
3✔
1313
                        ChansToRestore:  initMsg.ChanBackups,
3✔
1314
                        UnloadWallet:    loader.UnloadWallet,
3✔
1315
                        StatelessInit:   initMsg.StatelessInit,
3✔
1316
                        MacResponseChan: pwService.MacResponseChan,
3✔
1317
                        MacRootKey:      initMsg.MacRootKey,
3✔
1318
                }, nil
3✔
1319

1320
        // The wallet has already been created in the past, and is simply being
1321
        // unlocked. So we'll just return these passphrases.
1322
        case unlockMsg := <-pwService.UnlockMsgs:
3✔
1323
                // Resetting the transactions is something the user likely only
3✔
1324
                // wants to do once so we add a prominent warning to the log to
3✔
1325
                // remind the user to turn off the setting again after
3✔
1326
                // successful completion.
3✔
1327
                if cfg.ResetWalletTransactions {
6✔
1328
                        ltndLog.Warnf("Dropped all transaction history from " +
3✔
1329
                                "on-chain wallet. Remember to disable " +
3✔
1330
                                "reset-wallet-transactions flag for next " +
3✔
1331
                                "start of lnd")
3✔
1332
                }
3✔
1333

1334
                return &walletunlocker.WalletUnlockParams{
3✔
1335
                        Password:        unlockMsg.Passphrase,
3✔
1336
                        RecoveryWindow:  unlockMsg.RecoveryWindow,
3✔
1337
                        Wallet:          unlockMsg.Wallet,
3✔
1338
                        ChansToRestore:  unlockMsg.ChanBackups,
3✔
1339
                        UnloadWallet:    unlockMsg.UnloadWallet,
3✔
1340
                        StatelessInit:   unlockMsg.StatelessInit,
3✔
1341
                        MacResponseChan: pwService.MacResponseChan,
3✔
1342
                }, nil
3✔
1343

1344
        // If we got a shutdown signal we just return with an error immediately
1345
        case <-shutdownChan:
×
1346
                return nil, fmt.Errorf("shutting down")
×
1347
        }
1348
}
1349

1350
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
1351
// which we created as watch-only.
1352
func importWatchOnlyAccounts(wallet *wallet.Wallet,
1353
        initMsg *walletunlocker.WalletInitMsg) error {
3✔
1354

3✔
1355
        scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
3✔
1356
        for scope := range initMsg.WatchOnlyAccounts {
6✔
1357
                scopes = append(scopes, scope)
3✔
1358
        }
3✔
1359

1360
        // We need to import the accounts in the correct order, otherwise the
1361
        // indices will be incorrect.
1362
        sort.Slice(scopes, func(i, j int) bool {
6✔
1363
                return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
3✔
1364
                        scopes[i].Index < scopes[j].Index
3✔
1365
        })
3✔
1366

1367
        for _, scope := range scopes {
6✔
1368
                addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
3✔
1369

3✔
1370
                // We want witness pubkey hash by default, except for BIP49
3✔
1371
                // where we want mixed and BIP86 where we want taproot address
3✔
1372
                // formats.
3✔
1373
                switch scope.Scope.Purpose {
3✔
1374
                case waddrmgr.KeyScopeBIP0049Plus.Purpose,
1375
                        waddrmgr.KeyScopeBIP0086.Purpose:
3✔
1376

3✔
1377
                        addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
3✔
1378
                }
1379

1380
                // We want a human-readable account name. But for the default
1381
                // on-chain wallet we actually need to call it "default" to make
1382
                // sure everything works correctly.
1383
                name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
3✔
1384
                if scope.Index == 0 {
6✔
1385
                        name = "default"
3✔
1386
                }
3✔
1387

1388
                _, err := wallet.ImportAccountWithScope(
3✔
1389
                        name, initMsg.WatchOnlyAccounts[scope],
3✔
1390
                        initMsg.WatchOnlyMasterFingerprint, scope.Scope,
3✔
1391
                        addrSchema,
3✔
1392
                )
3✔
1393
                if err != nil {
3✔
1394
                        return fmt.Errorf("could not import account %v: %w",
×
1395
                                name, err)
×
1396
                }
×
1397
        }
1398

1399
        return nil
3✔
1400
}
1401

1402
// initNeutrinoBackend inits a new instance of the neutrino light client
1403
// backend given a target chain directory to store the chain state.
1404
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
1405
        blockCache *blockcache.BlockCache) (*neutrino.ChainService,
1406
        func(), error) {
1✔
1407

1✔
1408
        // Both channel validation flags are false by default but their meaning
1✔
1409
        // is the inverse of each other. Therefore both cannot be true. For
1✔
1410
        // every other case, the neutrino.validatechannels overwrites the
1✔
1411
        // routing.assumechanvalid value.
1✔
1412
        if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
1✔
1413
                return nil, nil, fmt.Errorf("can't set both " +
×
1414
                        "neutrino.validatechannels and routing." +
×
1415
                        "assumechanvalid to true at the same time")
×
1416
        }
×
1417
        cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
1✔
1418

1✔
1419
        // First we'll open the database file for neutrino, creating the
1✔
1420
        // database if needed. We append the normalized network name here to
1✔
1421
        // match the behavior of btcwallet.
1✔
1422
        dbPath := filepath.Join(
1✔
1423
                chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
1✔
1424
        )
1✔
1425

1✔
1426
        // Ensure that the neutrino db path exists.
1✔
1427
        if err := os.MkdirAll(dbPath, 0700); err != nil {
1✔
1428
                return nil, nil, err
×
1429
        }
×
1430

1431
        var (
1✔
1432
                db  walletdb.DB
1✔
1433
                err error
1✔
1434
        )
1✔
1435
        switch {
1✔
1436
        case cfg.DB.Backend == kvdb.SqliteBackendName:
×
1437
                sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
×
1438
                db, err = kvdb.Open(
×
1439
                        kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
×
1440
                        lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
×
1441
                )
×
1442

1443
        default:
1✔
1444
                dbName := filepath.Join(dbPath, "neutrino.db")
1✔
1445
                db, err = walletdb.Create(
1✔
1446
                        "bdb", dbName, !cfg.SyncFreelist, cfg.DB.Bolt.DBTimeout,
1✔
1447
                )
1✔
1448
        }
1449
        if err != nil {
1✔
1450
                return nil, nil, fmt.Errorf("unable to create "+
×
1451
                        "neutrino database: %v", err)
×
1452
        }
×
1453

1454
        headerStateAssertion, err := parseHeaderStateAssertion(
1✔
1455
                cfg.NeutrinoMode.AssertFilterHeader,
1✔
1456
        )
1✔
1457
        if err != nil {
1✔
1458
                db.Close()
×
1459
                return nil, nil, err
×
1460
        }
×
1461

1462
        // With the database open, we can now create an instance of the
1463
        // neutrino light client. We pass in relevant configuration parameters
1464
        // required.
1465
        config := neutrino.Config{
1✔
1466
                DataDir:      dbPath,
1✔
1467
                Database:     db,
1✔
1468
                ChainParams:  *cfg.ActiveNetParams.Params,
1✔
1469
                AddPeers:     cfg.NeutrinoMode.AddPeers,
1✔
1470
                ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
1✔
1471
                Dialer: func(addr net.Addr) (net.Conn, error) {
2✔
1472
                        return cfg.net.Dial(
1✔
1473
                                addr.Network(), addr.String(),
1✔
1474
                                cfg.ConnectionTimeout,
1✔
1475
                        )
1✔
1476
                },
1✔
1477
                NameResolver: func(host string) ([]net.IP, error) {
1✔
1478
                        addrs, err := cfg.net.LookupHost(host)
1✔
1479
                        if err != nil {
1✔
1480
                                return nil, err
×
1481
                        }
×
1482

1483
                        ips := make([]net.IP, 0, len(addrs))
1✔
1484
                        for _, strIP := range addrs {
2✔
1485
                                ip := net.ParseIP(strIP)
1✔
1486
                                if ip == nil {
1✔
1487
                                        continue
×
1488
                                }
1489

1490
                                ips = append(ips, ip)
1✔
1491
                        }
1492

1493
                        return ips, nil
1✔
1494
                },
1495
                AssertFilterHeader: headerStateAssertion,
1496
                BlockCache:         blockCache.Cache,
1497
                BroadcastTimeout:   cfg.NeutrinoMode.BroadcastTimeout,
1498
                PersistToDisk:      cfg.NeutrinoMode.PersistFilters,
1499
        }
1500

1501
        neutrino.MaxPeers = 8
1✔
1502
        neutrino.BanDuration = time.Hour * 48
1✔
1503
        neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
1✔
1504
        neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
1✔
1505

1✔
1506
        neutrinoCS, err := neutrino.NewChainService(config)
1✔
1507
        if err != nil {
1✔
1508
                db.Close()
×
1509
                return nil, nil, fmt.Errorf("unable to create neutrino light "+
×
1510
                        "client: %v", err)
×
1511
        }
×
1512

1513
        if err := neutrinoCS.Start(); err != nil {
1✔
1514
                db.Close()
×
1515
                return nil, nil, err
×
1516
        }
×
1517

1518
        cleanUp := func() {
2✔
1519
                if err := neutrinoCS.Stop(); err != nil {
1✔
1520
                        ltndLog.Infof("Unable to stop neutrino light client: "+
×
1521
                                "%v", err)
×
1522
                }
×
1523
                db.Close()
1✔
1524
        }
1525

1526
        return neutrinoCS, cleanUp, nil
1✔
1527
}
1528

1529
// parseHeaderStateAssertion parses the user-specified neutrino header state
1530
// into a headerfs.FilterHeader.
1531
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
1✔
1532
        if len(state) == 0 {
2✔
1533
                return nil, nil
1✔
1534
        }
1✔
1535

1536
        split := strings.Split(state, ":")
×
1537
        if len(split) != 2 {
×
1538
                return nil, fmt.Errorf("header state assertion %v in "+
×
1539
                        "unexpected format, expected format height:hash", state)
×
1540
        }
×
1541

1542
        height, err := strconv.ParseUint(split[0], 10, 32)
×
1543
        if err != nil {
×
1544
                return nil, fmt.Errorf("invalid filter header height: %w", err)
×
1545
        }
×
1546

1547
        hash, err := chainhash.NewHashFromStr(split[1])
×
1548
        if err != nil {
×
1549
                return nil, fmt.Errorf("invalid filter header hash: %w", err)
×
1550
        }
×
1551

1552
        return &headerfs.FilterHeader{
×
1553
                Height:     uint32(height),
×
1554
                FilterHash: *hash,
×
1555
        }, nil
×
1556
}
1557

1558
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
1559
// the neutrino BroadcastError which allows the Rebroadcaster which currently
1560
// resides in the neutrino package to use all of its functionalities.
1561
func broadcastErrorMapper(err error) error {
2✔
1562
        var returnErr error
2✔
1563

2✔
1564
        // We only filter for specific backend errors which are relevant for the
2✔
1565
        // Rebroadcaster.
2✔
1566
        switch {
2✔
1567
        // This makes sure the tx is removed from the rebroadcaster once it is
1568
        // confirmed.
1569
        case errors.Is(err, chain.ErrTxAlreadyKnown),
1570
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
1✔
1571

1✔
1572
                returnErr = &pushtx.BroadcastError{
1✔
1573
                        Code:   pushtx.Confirmed,
1✔
1574
                        Reason: err.Error(),
1✔
1575
                }
1✔
1576

1577
        // Transactions which are still in mempool but might fall out because
1578
        // of low fees are rebroadcasted despite of their backend error.
1579
        case errors.Is(err, chain.ErrTxAlreadyInMempool):
×
1580
                returnErr = &pushtx.BroadcastError{
×
1581
                        Code:   pushtx.Mempool,
×
1582
                        Reason: err.Error(),
×
1583
                }
×
1584

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

×
1593
                returnErr = &pushtx.BroadcastError{
×
1594
                        Code:   pushtx.Mempool,
×
1595
                        Reason: err.Error(),
×
1596
                }
×
1597
        }
1598

1599
        return returnErr
2✔
1600
}
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