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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

60.4
/lnwallet/btcwallet/btcwallet.go
1
package btcwallet
2

3
import (
4
        "bytes"
5
        "encoding/hex"
6
        "errors"
7
        "fmt"
8
        "math"
9
        "sync"
10
        "time"
11

12
        "github.com/btcsuite/btcd/btcec/v2"
13
        "github.com/btcsuite/btcd/btcutil"
14
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
15
        "github.com/btcsuite/btcd/chaincfg"
16
        "github.com/btcsuite/btcd/chaincfg/chainhash"
17
        "github.com/btcsuite/btcd/rpcclient"
18
        "github.com/btcsuite/btcd/txscript"
19
        "github.com/btcsuite/btcd/wire"
20
        "github.com/btcsuite/btcwallet/chain"
21
        "github.com/btcsuite/btcwallet/waddrmgr"
22
        "github.com/btcsuite/btcwallet/wallet"
23
        base "github.com/btcsuite/btcwallet/wallet"
24
        "github.com/btcsuite/btcwallet/wallet/txauthor"
25
        "github.com/btcsuite/btcwallet/wallet/txrules"
26
        "github.com/btcsuite/btcwallet/walletdb"
27
        "github.com/btcsuite/btcwallet/wtxmgr"
28
        "github.com/davecgh/go-spew/spew"
29
        "github.com/lightningnetwork/lnd/blockcache"
30
        "github.com/lightningnetwork/lnd/fn/v2"
31
        "github.com/lightningnetwork/lnd/input"
32
        "github.com/lightningnetwork/lnd/keychain"
33
        "github.com/lightningnetwork/lnd/kvdb"
34
        "github.com/lightningnetwork/lnd/lnwallet"
35
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
36
)
37

38
const (
39
        defaultAccount  = uint32(waddrmgr.DefaultAccountNum)
40
        importedAccount = uint32(waddrmgr.ImportedAddrAccount)
41

42
        // dryRunImportAccountNumAddrs represents the number of addresses we'll
43
        // derive for an imported account's external and internal branch when a
44
        // dry run is attempted.
45
        dryRunImportAccountNumAddrs = 5
46

47
        // UnconfirmedHeight is the special case end height that is used to
48
        // obtain unconfirmed transactions from ListTransactionDetails.
49
        UnconfirmedHeight int32 = -1
50

51
        // walletMetaBucket is used to store wallet metadata.
52
        walletMetaBucket = "lnwallet"
53

54
        // walletReadyKey is used to indicate that the wallet has been
55
        // initialized.
56
        walletReadyKey = "ready"
57
)
58

59
var (
60
        // waddrmgrNamespaceKey is the namespace key that the waddrmgr state is
61
        // stored within the top-level walletdb buckets of btcwallet.
62
        waddrmgrNamespaceKey = []byte("waddrmgr")
63

64
        // wtxmgrNamespaceKey is the namespace key that the wtxmgr state is
65
        // stored within the top-level waleltdb buckets of btcwallet.
66
        wtxmgrNamespaceKey = []byte("wtxmgr")
67

68
        // lightningAddrSchema is the scope addr schema for all keys that we
69
        // derive. We'll treat them all as p2wkh addresses, as atm we must
70
        // specify a particular type.
71
        lightningAddrSchema = waddrmgr.ScopeAddrSchema{
72
                ExternalAddrType: waddrmgr.WitnessPubKey,
73
                InternalAddrType: waddrmgr.WitnessPubKey,
74
        }
75

76
        // LndDefaultKeyScopes is the list of default key scopes that lnd adds
77
        // to its wallet.
78
        LndDefaultKeyScopes = []waddrmgr.KeyScope{
79
                waddrmgr.KeyScopeBIP0049Plus,
80
                waddrmgr.KeyScopeBIP0084,
81
                waddrmgr.KeyScopeBIP0086,
82
        }
83

84
        // errNoImportedAddrGen is an error returned when a new address is
85
        // requested for the default imported account within the wallet.
86
        errNoImportedAddrGen = errors.New("addresses cannot be generated for " +
87
                "the default imported account")
88
)
89

90
// BtcWallet is an implementation of the lnwallet.WalletController interface
91
// backed by an active instance of btcwallet. At the time of the writing of
92
// this documentation, this implementation requires a full btcd node to
93
// operate.
94
type BtcWallet struct {
95
        // wallet is an active instance of btcwallet.
96
        wallet *base.Wallet
97

98
        chain chain.Interface
99

100
        db walletdb.DB
101

102
        cfg *Config
103

104
        netParams *chaincfg.Params
105

106
        chainKeyScope waddrmgr.KeyScope
107

108
        blockCache *blockcache.BlockCache
109

110
        *input.MusigSessionManager
111
}
112

113
// A compile time check to ensure that BtcWallet implements the
114
// WalletController and BlockChainIO interfaces.
115
var _ lnwallet.WalletController = (*BtcWallet)(nil)
116
var _ lnwallet.BlockChainIO = (*BtcWallet)(nil)
117

118
// New returns a new fully initialized instance of BtcWallet given a valid
119
// configuration struct.
120
func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
12✔
121
        // Create the key scope for the coin type being managed by this wallet.
12✔
122
        chainKeyScope := waddrmgr.KeyScope{
12✔
123
                Purpose: keychain.BIP0043Purpose,
12✔
124
                Coin:    cfg.CoinType,
12✔
125
        }
12✔
126

12✔
127
        // Maybe the wallet has already been opened and unlocked by the
12✔
128
        // WalletUnlocker. So if we get a non-nil value from the config,
12✔
129
        // we assume everything is in order.
12✔
130
        var wallet = cfg.Wallet
12✔
131
        if wallet == nil {
24✔
132
                // No ready wallet was passed, so try to open an existing one.
12✔
133
                var pubPass []byte
12✔
134
                if cfg.PublicPass == nil {
24✔
135
                        pubPass = defaultPubPassphrase
12✔
136
                } else {
12✔
UNCOV
137
                        pubPass = cfg.PublicPass
×
UNCOV
138
                }
×
139

140
                loader, err := NewWalletLoader(
12✔
141
                        cfg.NetParams, cfg.RecoveryWindow, cfg.LoaderOptions...,
12✔
142
                )
12✔
143
                if err != nil {
12✔
144
                        return nil, err
×
145
                }
×
146
                walletExists, err := loader.WalletExists()
12✔
147
                if err != nil {
12✔
148
                        return nil, err
×
149
                }
×
150

151
                if !walletExists {
24✔
152
                        // Wallet has never been created, perform initial
12✔
153
                        // set up.
12✔
154
                        wallet, err = loader.CreateNewWallet(
12✔
155
                                pubPass, cfg.PrivatePass, cfg.HdSeed,
12✔
156
                                cfg.Birthday,
12✔
157
                        )
12✔
158
                        if err != nil {
12✔
159
                                return nil, err
×
160
                        }
×
UNCOV
161
                } else {
×
UNCOV
162
                        // Wallet has been created and been initialized at
×
UNCOV
163
                        // this point, open it along with all the required DB
×
UNCOV
164
                        // namespaces, and the DB itself.
×
UNCOV
165
                        wallet, err = loader.OpenExistingWallet(pubPass, false)
×
UNCOV
166
                        if err != nil {
×
167
                                return nil, err
×
168
                        }
×
169
                }
170
        }
171

172
        finalWallet := &BtcWallet{
12✔
173
                cfg:           &cfg,
12✔
174
                wallet:        wallet,
12✔
175
                db:            wallet.Database(),
12✔
176
                chain:         cfg.ChainSource,
12✔
177
                netParams:     cfg.NetParams,
12✔
178
                chainKeyScope: chainKeyScope,
12✔
179
                blockCache:    blockCache,
12✔
180
        }
12✔
181

12✔
182
        finalWallet.MusigSessionManager = input.NewMusigSessionManager(
12✔
183
                finalWallet.fetchPrivKey,
12✔
184
        )
12✔
185

12✔
186
        return finalWallet, nil
12✔
187
}
188

189
// loaderCfg holds optional wallet loader configuration.
190
type loaderCfg struct {
191
        dbDirPath      string
192
        noFreelistSync bool
193
        dbTimeout      time.Duration
194
        useLocalDB     bool
195
        externalDB     kvdb.Backend
196
}
197

198
// LoaderOption is a functional option to update the optional loader config.
199
type LoaderOption func(*loaderCfg)
200

201
// LoaderWithLocalWalletDB configures the wallet loader to use the local db.
202
func LoaderWithLocalWalletDB(dbDirPath string, noFreelistSync bool,
203
        dbTimeout time.Duration) LoaderOption {
20✔
204

20✔
205
        return func(cfg *loaderCfg) {
49✔
206
                cfg.dbDirPath = dbDirPath
29✔
207
                cfg.noFreelistSync = noFreelistSync
29✔
208
                cfg.dbTimeout = dbTimeout
29✔
209
                cfg.useLocalDB = true
29✔
210
        }
29✔
211
}
212

213
// LoaderWithExternalWalletDB configures the wallet loadr to use an external db.
214
func LoaderWithExternalWalletDB(db kvdb.Backend) LoaderOption {
×
215
        return func(cfg *loaderCfg) {
×
216
                cfg.externalDB = db
×
217
        }
×
218
}
219

220
// NewWalletLoader constructs a wallet loader.
221
func NewWalletLoader(chainParams *chaincfg.Params, recoveryWindow uint32,
222
        opts ...LoaderOption) (*base.Loader, error) {
29✔
223

29✔
224
        cfg := &loaderCfg{}
29✔
225

29✔
226
        // Apply all functional options.
29✔
227
        for _, o := range opts {
58✔
228
                o(cfg)
29✔
229
        }
29✔
230

231
        if cfg.externalDB != nil && cfg.useLocalDB {
29✔
232
                return nil, fmt.Errorf("wallet can either be in the local or " +
×
233
                        "an external db")
×
234
        }
×
235

236
        if cfg.externalDB != nil {
29✔
237
                loader, err := base.NewLoaderWithDB(
×
238
                        chainParams, recoveryWindow, cfg.externalDB,
×
239
                        func() (bool, error) {
×
240
                                return externalWalletExists(cfg.externalDB)
×
241
                        },
×
242
                )
243
                if err != nil {
×
244
                        return nil, err
×
245
                }
×
246

247
                // Decorate wallet db with out own key such that we
248
                // can always check whether the wallet exists or not.
249
                loader.OnWalletCreated(onWalletCreated)
×
250
                return loader, nil
×
251
        }
252

253
        return base.NewLoader(
29✔
254
                chainParams, cfg.dbDirPath, cfg.noFreelistSync,
29✔
255
                cfg.dbTimeout, recoveryWindow,
29✔
256
        ), nil
29✔
257
}
258

259
// externalWalletExists is a helper function that we use to template btcwallet's
260
// Loader in order to be able check if the wallet database has been initialized
261
// in an external DB.
262
func externalWalletExists(db kvdb.Backend) (bool, error) {
×
263
        exists := false
×
264
        err := kvdb.View(db, func(tx kvdb.RTx) error {
×
265
                metaBucket := tx.ReadBucket([]byte(walletMetaBucket))
×
266
                if metaBucket != nil {
×
267
                        walletReady := metaBucket.Get([]byte(walletReadyKey))
×
268
                        exists = string(walletReady) == walletReadyKey
×
269
                }
×
270

271
                return nil
×
272
        }, func() {})
×
273

274
        return exists, err
×
275
}
276

277
// onWalletCreated is executed when btcwallet creates the wallet the first time.
278
func onWalletCreated(tx kvdb.RwTx) error {
×
279
        metaBucket, err := tx.CreateTopLevelBucket([]byte(walletMetaBucket))
×
280
        if err != nil {
×
281
                return err
×
282
        }
×
283

284
        return metaBucket.Put([]byte(walletReadyKey), []byte(walletReadyKey))
×
285
}
286

287
// BackEnd returns the underlying ChainService's name as a string.
288
//
289
// This is a part of the WalletController interface.
290
func (b *BtcWallet) BackEnd() string {
32✔
291
        if b.chain != nil {
64✔
292
                return b.chain.BackEnd()
32✔
293
        }
32✔
294

295
        return ""
×
296
}
297

298
// InternalWallet returns a pointer to the internal base wallet which is the
299
// core of btcwallet.
300
func (b *BtcWallet) InternalWallet() *base.Wallet {
9✔
301
        return b.wallet
9✔
302
}
9✔
303

304
// Start initializes the underlying rpc connection, the wallet itself, and
305
// begins syncing to the current available blockchain state.
306
//
307
// This is a part of the WalletController interface.
308
func (b *BtcWallet) Start() error {
12✔
309
        // Is the wallet (according to its database) currently watch-only
12✔
310
        // already? If it is, we won't need to convert it later.
12✔
311
        walletIsWatchOnly := b.wallet.Manager.WatchOnly()
12✔
312

12✔
313
        // If the wallet is watch-only, but we don't expect it to be, then we
12✔
314
        // are in an unexpected state and cannot continue.
12✔
315
        if walletIsWatchOnly && !b.cfg.WatchOnly {
12✔
316
                return fmt.Errorf("wallet is watch-only but we expect it " +
×
317
                        "not to be; check if remote signing was disabled by " +
×
318
                        "accident")
×
319
        }
×
320

321
        // We'll start by unlocking the wallet and ensuring that the KeyScope:
322
        // (1017, 1) exists within the internal waddrmgr. We'll need this in
323
        // order to properly generate the keys required for signing various
324
        // contracts. If this is a watch-only wallet, we don't have any private
325
        // keys and therefore unlocking is not necessary.
326
        if !walletIsWatchOnly {
24✔
327
                if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
12✔
328
                        return err
×
329
                }
×
330

331
                // If the wallet isn't about to be converted, we need to inform
332
                // the user that this wallet still contains all private key
333
                // material and that they need to migrate the existing wallet.
334
                if b.cfg.WatchOnly && !b.cfg.MigrateWatchOnly {
12✔
335
                        log.Warnf("Wallet is expected to be in watch-only " +
×
336
                                "mode but hasn't been migrated to watch-only " +
×
337
                                "yet, it still contains private keys; " +
×
338
                                "consider turning on the watch-only wallet " +
×
339
                                "migration in remote signing mode")
×
340
                }
×
341
        }
342

343
        // Because we might add new "default" key scopes over time, they are
344
        // created correctly for new wallets. Existing wallets don't
345
        // automatically add them, we need to do that manually now.
346
        for _, scope := range LndDefaultKeyScopes {
48✔
347
                _, err := b.wallet.Manager.FetchScopedKeyManager(scope)
36✔
348
                if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) {
36✔
349
                        // The default scope wasn't found, that probably means
×
350
                        // it was added recently and older wallets don't know it
×
351
                        // yet. Let's add it now.
×
352
                        addrSchema := waddrmgr.ScopeAddrMap[scope]
×
353
                        err := walletdb.Update(
×
354
                                b.db, func(tx walletdb.ReadWriteTx) error {
×
355
                                        addrmgrNs := tx.ReadWriteBucket(
×
356
                                                waddrmgrNamespaceKey,
×
357
                                        )
×
358

×
359
                                        _, err := b.wallet.Manager.NewScopedKeyManager(
×
360
                                                addrmgrNs, scope, addrSchema,
×
361
                                        )
×
362
                                        return err
×
363
                                },
×
364
                        )
365
                        if err != nil {
×
366
                                return err
×
367
                        }
×
368
                }
369
        }
370

371
        scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
12✔
372
        if err != nil {
24✔
373
                // If the scope hasn't yet been created (it wouldn't been
12✔
374
                // loaded by default if it was), then we'll manually create the
12✔
375
                // scope for the first time ourselves.
12✔
376
                err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
24✔
377
                        addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
12✔
378

12✔
379
                        scope, err = b.wallet.Manager.NewScopedKeyManager(
12✔
380
                                addrmgrNs, b.chainKeyScope, lightningAddrSchema,
12✔
381
                        )
12✔
382
                        return err
12✔
383
                })
12✔
384
                if err != nil {
12✔
385
                        return err
×
386
                }
×
387
        }
388

389
        // Now that the wallet is unlocked, we'll go ahead and make sure we
390
        // create accounts for all the key families we're going to use. This
391
        // will make it possible to list all the account/family xpubs in the
392
        // wallet list RPC.
393
        err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
24✔
394
                addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
12✔
395

12✔
396
                // Generate all accounts that we could ever need. This includes
12✔
397
                // all lnd key families as well as some key families used in
12✔
398
                // external liquidity tools.
12✔
399
                for keyFam := uint32(1); keyFam <= 255; keyFam++ {
3,072✔
400
                        // Otherwise, we'll check if the account already exists,
3,060✔
401
                        // if so, we can once again bail early.
3,060✔
402
                        _, err := scope.AccountName(addrmgrNs, keyFam)
3,060✔
403
                        if err == nil {
3,060✔
UNCOV
404
                                continue
×
405
                        }
406

407
                        // If we reach this point, then the account hasn't yet
408
                        // been created, so we'll need to create it before we
409
                        // can proceed.
410
                        err = scope.NewRawAccount(addrmgrNs, keyFam)
3,060✔
411
                        if err != nil {
3,060✔
412
                                return err
×
413
                        }
×
414
                }
415

416
                // If this is the first startup with remote signing and wallet
417
                // migration turned on and the wallet wasn't previously
418
                // migrated, we can do that now that we made sure all accounts
419
                // that we need were derived correctly.
420
                if !walletIsWatchOnly && b.cfg.WatchOnly &&
12✔
421
                        b.cfg.MigrateWatchOnly {
12✔
422

×
423
                        log.Infof("Migrating wallet to watch-only mode, " +
×
424
                                "purging all private key material")
×
425

×
426
                        ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
×
427
                        err = b.wallet.Manager.ConvertToWatchingOnly(ns)
×
428
                        if err != nil {
×
429
                                return err
×
430
                        }
×
431
                }
432

433
                return nil
12✔
434
        })
435
        if err != nil {
12✔
436
                return err
×
437
        }
×
438

439
        // Establish an RPC connection in addition to starting the goroutines
440
        // in the underlying wallet.
441
        if err := b.chain.Start(); err != nil {
12✔
442
                return err
×
443
        }
×
444

445
        // Start the underlying btcwallet core.
446
        b.wallet.Start()
12✔
447

12✔
448
        // Pass the rpc client into the wallet so it can sync up to the
12✔
449
        // current main chain.
12✔
450
        b.wallet.SynchronizeRPC(b.chain)
12✔
451

12✔
452
        return nil
12✔
453
}
454

455
// Stop signals the wallet for shutdown. Shutdown may entail closing
456
// any active sockets, database handles, stopping goroutines, etc.
457
//
458
// This is a part of the WalletController interface.
459
func (b *BtcWallet) Stop() error {
8✔
460
        b.wallet.Stop()
8✔
461

8✔
462
        b.wallet.WaitForShutdown()
8✔
463

8✔
464
        b.chain.Stop()
8✔
465

8✔
466
        return nil
8✔
467
}
8✔
468

469
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
470
// have at least confs confirmations. If confs is set to zero, then all unspent
471
// outputs, including those currently in the mempool will be included in the
472
// final sum. The account parameter serves as a filter to retrieve the balance
473
// for a specific account. When empty, the confirmed balance of all wallet
474
// accounts is returned.
475
//
476
// This is a part of the WalletController interface.
477
func (b *BtcWallet) ConfirmedBalance(confs int32,
478
        accountFilter string) (btcutil.Amount, error) {
106✔
479

106✔
480
        var balance btcutil.Amount
106✔
481

106✔
482
        witnessOutputs, err := b.ListUnspentWitness(
106✔
483
                confs, math.MaxInt32, accountFilter,
106✔
484
        )
106✔
485
        if err != nil {
106✔
486
                return 0, err
×
487
        }
×
488

489
        for _, witnessOutput := range witnessOutputs {
1,951✔
490
                balance += witnessOutput.Value
1,845✔
491
        }
1,845✔
492

493
        return balance, nil
106✔
494
}
495

496
// keyScopeForAccountAddr determines the appropriate key scope of an account
497
// based on its name/address type.
498
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
499
        addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
530✔
500

530✔
501
        // Map the requested address type to its key scope.
530✔
502
        var addrKeyScope waddrmgr.KeyScope
530✔
503
        switch addrType {
530✔
504
        case lnwallet.WitnessPubKey:
492✔
505
                addrKeyScope = waddrmgr.KeyScopeBIP0084
492✔
506
        case lnwallet.NestedWitnessPubKey:
12✔
507
                addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
12✔
508
        case lnwallet.TaprootPubkey:
26✔
509
                addrKeyScope = waddrmgr.KeyScopeBIP0086
26✔
510
        default:
×
511
                return waddrmgr.KeyScope{}, 0,
×
512
                        fmt.Errorf("unknown address type")
×
513
        }
514

515
        // The default account spans across multiple key scopes, so the
516
        // requested address type should already be valid for this account.
517
        if accountName == lnwallet.DefaultAccountName {
1,060✔
518
                return addrKeyScope, defaultAccount, nil
530✔
519
        }
530✔
520

521
        // Otherwise, look up the custom account and if it supports the given
522
        // key scope.
UNCOV
523
        accountNumber, err := b.wallet.AccountNumber(addrKeyScope, accountName)
×
UNCOV
524
        if err != nil {
×
525
                return waddrmgr.KeyScope{}, 0, err
×
526
        }
×
527

UNCOV
528
        return addrKeyScope, accountNumber, nil
×
529
}
530

531
// NewAddress returns the next external or internal address for the wallet
532
// dictated by the value of the `change` parameter. If change is true, then an
533
// internal address will be returned, otherwise an external address should be
534
// returned. The account parameter must be non-empty as it determines which
535
// account the address should be generated from.
536
//
537
// This is a part of the WalletController interface.
538
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool,
539
        accountName string) (btcutil.Address, error) {
506✔
540

506✔
541
        // Addresses cannot be derived from the catch-all imported accounts.
506✔
542
        if accountName == waddrmgr.ImportedAddrAccountName {
506✔
543
                return nil, errNoImportedAddrGen
×
544
        }
×
545

546
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
506✔
547
        if err != nil {
506✔
548
                return nil, err
×
549
        }
×
550

551
        if change {
534✔
552
                return b.wallet.NewChangeAddress(account, keyScope)
28✔
553
        }
28✔
554
        return b.wallet.NewAddress(account, keyScope)
478✔
555
}
556

557
// LastUnusedAddress returns the last *unused* address known by the wallet. An
558
// address is unused if it hasn't received any payments. This can be useful in
559
// UIs in order to continually show the "freshest" address without having to
560
// worry about "address inflation" caused by continual refreshing. Similar to
561
// NewAddress it can derive a specified address type, and also optionally a
562
// change address. The account parameter must be non-empty as it determines
563
// which account the address should be generated from.
564
func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType,
565
        accountName string) (btcutil.Address, error) {
24✔
566

24✔
567
        // Addresses cannot be derived from the catch-all imported accounts.
24✔
568
        if accountName == waddrmgr.ImportedAddrAccountName {
24✔
569
                return nil, errNoImportedAddrGen
×
570
        }
×
571

572
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
24✔
573
        if err != nil {
24✔
574
                return nil, err
×
575
        }
×
576

577
        return b.wallet.CurrentAddress(account, keyScope)
24✔
578
}
579

580
// IsOurAddress checks if the passed address belongs to this wallet
581
//
582
// This is a part of the WalletController interface.
583
func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool {
32✔
584
        result, err := b.wallet.HaveAddress(a)
32✔
585
        return result && (err == nil)
32✔
586
}
32✔
587

588
// AddressInfo returns the information about an address, if it's known to this
589
// wallet.
590
//
591
// NOTE: This is a part of the WalletController interface.
592
func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress,
593
        error) {
2✔
594

2✔
595
        return b.wallet.AddressInfo(a)
2✔
596
}
2✔
597

598
// ListAccounts retrieves all accounts belonging to the wallet by default. A
599
// name and key scope filter can be provided to filter through all of the wallet
600
// accounts and return only those matching.
601
//
602
// This is a part of the WalletController interface.
603
func (b *BtcWallet) ListAccounts(name string,
UNCOV
604
        keyScope *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
×
UNCOV
605

×
UNCOV
606
        var res []*waddrmgr.AccountProperties
×
UNCOV
607
        switch {
×
608
        // If both the name and key scope filters were provided, we'll return
609
        // the existing account matching those.
UNCOV
610
        case name != "" && keyScope != nil:
×
UNCOV
611
                account, err := b.wallet.AccountPropertiesByName(*keyScope, name)
×
UNCOV
612
                if err != nil {
×
613
                        return nil, err
×
614
                }
×
UNCOV
615
                res = append(res, account)
×
616

617
        // Only the name filter was provided.
UNCOV
618
        case name != "" && keyScope == nil:
×
UNCOV
619
                // If the name corresponds to the default or imported accounts,
×
UNCOV
620
                // we'll return them for all our supported key scopes.
×
UNCOV
621
                if name == lnwallet.DefaultAccountName ||
×
UNCOV
622
                        name == waddrmgr.ImportedAddrAccountName {
×
623

×
624
                        for _, defaultScope := range LndDefaultKeyScopes {
×
625
                                a, err := b.wallet.AccountPropertiesByName(
×
626
                                        defaultScope, name,
×
627
                                )
×
628
                                if err != nil {
×
629
                                        return nil, err
×
630
                                }
×
631
                                res = append(res, a)
×
632
                        }
633

634
                        break
×
635
                }
636

637
                // In theory, there should be only one custom account for the
638
                // given name. However, due to a lack of check, users could
639
                // create custom accounts with various key scopes. This
640
                // behaviour has been fixed but, we return all potential custom
641
                // accounts with the given name.
UNCOV
642
                for _, scope := range waddrmgr.DefaultKeyScopes {
×
UNCOV
643
                        a, err := b.wallet.AccountPropertiesByName(
×
UNCOV
644
                                scope, name,
×
UNCOV
645
                        )
×
UNCOV
646
                        switch {
×
UNCOV
647
                        case waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound):
×
UNCOV
648
                                continue
×
649

650
                        // In the specific case of a wallet initialized only by
651
                        // importing account xpubs (watch only wallets), it is
652
                        // possible that some keyscopes will be 'unknown' by the
653
                        // wallet (depending on the xpubs given to initialize
654
                        // it). If the keyscope is not found, just skip it.
UNCOV
655
                        case waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound):
×
UNCOV
656
                                continue
×
657

658
                        case err != nil:
×
659
                                return nil, err
×
660
                        }
661

UNCOV
662
                        res = append(res, a)
×
663
                }
UNCOV
664
                if len(res) == 0 {
×
UNCOV
665
                        return nil, newAccountNotFoundError(name)
×
UNCOV
666
                }
×
667

668
        // Only the key scope filter was provided, so we'll return all accounts
669
        // matching it.
670
        case name == "" && keyScope != nil:
×
671
                accounts, err := b.wallet.Accounts(*keyScope)
×
672
                if err != nil {
×
673
                        return nil, err
×
674
                }
×
675
                for _, account := range accounts.Accounts {
×
676
                        account := account
×
677
                        res = append(res, &account.AccountProperties)
×
678
                }
×
679

680
        // Neither of the filters were provided, so return all accounts for our
681
        // supported key scopes.
UNCOV
682
        case name == "" && keyScope == nil:
×
UNCOV
683
                for _, defaultScope := range LndDefaultKeyScopes {
×
UNCOV
684
                        accounts, err := b.wallet.Accounts(defaultScope)
×
UNCOV
685
                        if err != nil {
×
686
                                return nil, err
×
687
                        }
×
UNCOV
688
                        for _, account := range accounts.Accounts {
×
UNCOV
689
                                account := account
×
UNCOV
690
                                res = append(res, &account.AccountProperties)
×
UNCOV
691
                        }
×
692
                }
693

UNCOV
694
                accounts, err := b.wallet.Accounts(waddrmgr.KeyScope{
×
UNCOV
695
                        Purpose: keychain.BIP0043Purpose,
×
UNCOV
696
                        Coin:    b.cfg.CoinType,
×
UNCOV
697
                })
×
UNCOV
698
                if err != nil {
×
699
                        return nil, err
×
700
                }
×
UNCOV
701
                for _, account := range accounts.Accounts {
×
UNCOV
702
                        account := account
×
UNCOV
703
                        res = append(res, &account.AccountProperties)
×
UNCOV
704
                }
×
705
        }
706

UNCOV
707
        return res, nil
×
708
}
709

710
// newAccountNotFoundError returns an error indicating that the manager didn't
711
// find the specific account. This error is used to be compatible with the old
712
// 'LookupAccount' behaviour previously used.
UNCOV
713
func newAccountNotFoundError(name string) error {
×
UNCOV
714
        str := fmt.Sprintf("account name '%s' not found", name)
×
UNCOV
715

×
UNCOV
716
        return waddrmgr.ManagerError{
×
UNCOV
717
                ErrorCode:   waddrmgr.ErrAccountNotFound,
×
UNCOV
718
                Description: str,
×
UNCOV
719
        }
×
UNCOV
720
}
×
721

722
// RequiredReserve returns the minimum amount of satoshis that should be
723
// kept in the wallet in order to fee bump anchor channels if necessary.
724
// The value scales with the number of public anchor channels but is
725
// capped at a maximum.
726
func (b *BtcWallet) RequiredReserve(
727
        numAnchorChans uint32) btcutil.Amount {
80✔
728

80✔
729
        anchorChanReservedValue := lnwallet.AnchorChanReservedValue
80✔
730
        reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
80✔
731
        if reserved > lnwallet.MaxAnchorChanReservedValue {
80✔
732
                reserved = lnwallet.MaxAnchorChanReservedValue
×
733
        }
×
734

735
        return reserved
80✔
736
}
737

738
// ListAddresses retrieves all the addresses along with their balance. An
739
// account name filter can be provided to filter through all of the
740
// wallet accounts and return the addresses of only those matching.
741
//
742
// This is a part of the WalletController interface.
743
func (b *BtcWallet) ListAddresses(name string,
UNCOV
744
        showCustomAccounts bool) (lnwallet.AccountAddressMap, error) {
×
UNCOV
745

×
UNCOV
746
        accounts, err := b.ListAccounts(name, nil)
×
UNCOV
747
        if err != nil {
×
748
                return nil, err
×
749
        }
×
750

UNCOV
751
        addresses := make(lnwallet.AccountAddressMap)
×
UNCOV
752
        addressBalance := make(map[string]btcutil.Amount)
×
UNCOV
753

×
UNCOV
754
        // Retrieve all the unspent ouputs.
×
UNCOV
755
        outputs, err := b.wallet.ListUnspent(0, math.MaxInt32, "")
×
UNCOV
756
        if err != nil {
×
757
                return nil, err
×
758
        }
×
759

760
        // Calculate the total balance of each address.
UNCOV
761
        for _, output := range outputs {
×
UNCOV
762
                amount, err := btcutil.NewAmount(output.Amount)
×
UNCOV
763
                if err != nil {
×
764
                        return nil, err
×
765
                }
×
766

UNCOV
767
                addressBalance[output.Address] += amount
×
768
        }
769

UNCOV
770
        for _, accntDetails := range accounts {
×
UNCOV
771
                accntScope := accntDetails.KeyScope
×
UNCOV
772
                scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
×
UNCOV
773
                        accntScope,
×
UNCOV
774
                )
×
UNCOV
775
                if err != nil {
×
776
                        return nil, err
×
777
                }
×
778

UNCOV
779
                var managedAddrs []waddrmgr.ManagedAddress
×
UNCOV
780
                err = walletdb.View(
×
UNCOV
781
                        b.wallet.Database(), func(tx walletdb.ReadTx) error {
×
UNCOV
782
                                managedAddrs = nil
×
UNCOV
783
                                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
×
UNCOV
784
                                return scopedMgr.ForEachAccountAddress(
×
UNCOV
785
                                        addrmgrNs, accntDetails.AccountNumber,
×
UNCOV
786
                                        func(a waddrmgr.ManagedAddress) error {
×
UNCOV
787
                                                managedAddrs = append(
×
UNCOV
788
                                                        managedAddrs, a,
×
UNCOV
789
                                                )
×
UNCOV
790

×
UNCOV
791
                                                return nil
×
UNCOV
792
                                        },
×
793
                                )
794
                        },
795
                )
UNCOV
796
                if err != nil {
×
797
                        return nil, err
×
798
                }
×
799

800
                // Only consider those accounts which have addresses.
UNCOV
801
                if len(managedAddrs) == 0 {
×
UNCOV
802
                        continue
×
803
                }
804

805
                // All the lnd internal/custom keys for channels and other
806
                // functionality are derived from the same scope. Since they
807
                // aren't really used as addresses and will never have an
808
                // on-chain balance, we'll want to show the public key instead.
UNCOV
809
                isLndCustom := accntScope.Purpose == keychain.BIP0043Purpose
×
UNCOV
810
                addressProperties := make(
×
UNCOV
811
                        []lnwallet.AddressProperty, len(managedAddrs),
×
UNCOV
812
                )
×
UNCOV
813

×
UNCOV
814
                for idx, managedAddr := range managedAddrs {
×
UNCOV
815
                        addr := managedAddr.Address()
×
UNCOV
816
                        addressString := addr.String()
×
UNCOV
817

×
UNCOV
818
                        // Hex-encode the compressed public key for custom lnd
×
UNCOV
819
                        // keys, addresses don't make a lot of sense.
×
UNCOV
820
                        var (
×
UNCOV
821
                                pubKey         *btcec.PublicKey
×
UNCOV
822
                                derivationPath string
×
UNCOV
823
                        )
×
UNCOV
824
                        pka, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
×
UNCOV
825
                        if ok {
×
UNCOV
826
                                pubKey = pka.PubKey()
×
UNCOV
827

×
UNCOV
828
                                // There can be an error in two cases: Either
×
UNCOV
829
                                // the address isn't a managed pubkey address,
×
UNCOV
830
                                // which we already checked above, or the
×
UNCOV
831
                                // address is imported in which case we don't
×
UNCOV
832
                                // know the derivation path, and it will just be
×
UNCOV
833
                                // empty anyway.
×
UNCOV
834
                                _, _, derivationPath, _ =
×
UNCOV
835
                                        Bip32DerivationFromAddress(pka)
×
UNCOV
836
                        }
×
UNCOV
837
                        if pubKey != nil && isLndCustom {
×
UNCOV
838
                                addressString = hex.EncodeToString(
×
UNCOV
839
                                        pubKey.SerializeCompressed(),
×
UNCOV
840
                                )
×
UNCOV
841
                        }
×
842

UNCOV
843
                        addressProperties[idx] = lnwallet.AddressProperty{
×
UNCOV
844
                                Address:        addressString,
×
UNCOV
845
                                Internal:       managedAddr.Internal(),
×
UNCOV
846
                                Balance:        addressBalance[addressString],
×
UNCOV
847
                                PublicKey:      pubKey,
×
UNCOV
848
                                DerivationPath: derivationPath,
×
UNCOV
849
                        }
×
850
                }
851

UNCOV
852
                if accntScope.Purpose != keychain.BIP0043Purpose ||
×
UNCOV
853
                        showCustomAccounts {
×
UNCOV
854

×
UNCOV
855
                        addresses[accntDetails] = addressProperties
×
UNCOV
856
                }
×
857
        }
858

UNCOV
859
        return addresses, nil
×
860
}
861

862
// ImportAccount imports an account backed by an account extended public key.
863
// The master key fingerprint denotes the fingerprint of the root key
864
// corresponding to the account public key (also known as the key with
865
// derivation path m/). This may be required by some hardware wallets for proper
866
// identification and signing.
867
//
868
// The address type can usually be inferred from the key's version, but may be
869
// required for certain keys to map them into the proper scope.
870
//
871
// For custom accounts, we will first check if there is no account with the same
872
// name (even with a different key scope). No custom account should have various
873
// key scopes as it will result in non-deterministic behaviour.
874
//
875
// For BIP-0044 keys, an address type must be specified as we intend to not
876
// support importing BIP-0044 keys into the wallet using the legacy
877
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
878
// the standard BIP-0049 derivation scheme, while a witness address type will
879
// force the standard BIP-0084 derivation scheme.
880
//
881
// For BIP-0049 keys, an address type must also be specified to make a
882
// distinction between the standard BIP-0049 address schema (nested witness
883
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
884
// externally, witness pubkeys internally).
885
//
886
// This is a part of the WalletController interface.
887
func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
888
        masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
889
        dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
UNCOV
890
        []btcutil.Address, error) {
×
UNCOV
891

×
UNCOV
892
        // For custom accounts, we first check if there is no existing account
×
UNCOV
893
        // with the same name.
×
UNCOV
894
        if name != lnwallet.DefaultAccountName &&
×
UNCOV
895
                name != waddrmgr.ImportedAddrAccountName {
×
UNCOV
896

×
UNCOV
897
                _, err := b.ListAccounts(name, nil)
×
UNCOV
898
                if err == nil {
×
UNCOV
899
                        return nil, nil, nil,
×
UNCOV
900
                                fmt.Errorf("account '%s' already exists",
×
UNCOV
901
                                        name)
×
UNCOV
902
                }
×
UNCOV
903
                if !waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
×
904
                        return nil, nil, nil, err
×
905
                }
×
906
        }
907

UNCOV
908
        if !dryRun {
×
UNCOV
909
                accountProps, err := b.wallet.ImportAccount(
×
UNCOV
910
                        name, accountPubKey, masterKeyFingerprint, addrType,
×
UNCOV
911
                )
×
UNCOV
912
                if err != nil {
×
913
                        return nil, nil, nil, err
×
914
                }
×
UNCOV
915
                return accountProps, nil, nil, nil
×
916
        }
917

918
        // Derive addresses from both the external and internal branches of the
919
        // account. There's no risk of address inflation as this is only done
920
        // for dry runs.
921
        accountProps, extAddrs, intAddrs, err := b.wallet.ImportAccountDryRun(
×
922
                name, accountPubKey, masterKeyFingerprint, addrType,
×
923
                dryRunImportAccountNumAddrs,
×
924
        )
×
925
        if err != nil {
×
926
                return nil, nil, nil, err
×
927
        }
×
928

929
        externalAddrs := make([]btcutil.Address, len(extAddrs))
×
930
        for i := 0; i < len(extAddrs); i++ {
×
931
                externalAddrs[i] = extAddrs[i].Address()
×
932
        }
×
933

934
        internalAddrs := make([]btcutil.Address, len(intAddrs))
×
935
        for i := 0; i < len(intAddrs); i++ {
×
936
                internalAddrs[i] = intAddrs[i].Address()
×
937
        }
×
938

939
        return accountProps, externalAddrs, internalAddrs, nil
×
940
}
941

942
// ImportPublicKey imports a single derived public key into the wallet. The
943
// address type can usually be inferred from the key's version, but in the case
944
// of legacy versions (xpub, tpub), an address type must be specified as we
945
// intend to not support importing BIP-44 keys into the wallet using the legacy
946
// pay-to-pubkey-hash (P2PKH) scheme.
947
//
948
// This is a part of the WalletController interface.
949
func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey,
UNCOV
950
        addrType waddrmgr.AddressType) error {
×
UNCOV
951

×
UNCOV
952
        return b.wallet.ImportPublicKey(pubKey, addrType)
×
UNCOV
953
}
×
954

955
// ImportTaprootScript imports a user-provided taproot script into the address
956
// manager. The imported script will act as a pay-to-taproot address.
957
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
958
        tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
2✔
959

2✔
960
        // We want to be able to import script addresses into a watch-only
2✔
961
        // wallet, which is only possible if we don't encrypt the script with
2✔
962
        // the private key encryption key. By specifying the script as being
2✔
963
        // "not secret", we can also decrypt the script in a watch-only wallet.
2✔
964
        const isSecretScript = false
2✔
965

2✔
966
        // Currently, only v1 (Taproot) scripts are supported. We don't even
2✔
967
        // know what a v2 witness version would look like at this point.
2✔
968
        const witnessVersionTaproot byte = 1
2✔
969

2✔
970
        return b.wallet.ImportTaprootScript(
2✔
971
                scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
2✔
972
        )
2✔
973
}
2✔
974

975
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
976
// the specified outputs. In the case the wallet has insufficient funds, or the
977
// outputs are non-standard, a non-nil error will be returned.
978
//
979
// NOTE: This method requires the global coin selection lock to be held.
980
//
981
// This is a part of the WalletController interface.
982
func (b *BtcWallet) SendOutputs(inputs fn.Set[wire.OutPoint],
983
        outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
984
        minConfs int32, label string,
985
        strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) {
134✔
986

134✔
987
        // Convert our fee rate from sat/kw to sat/kb since it's required by
134✔
988
        // SendOutputs.
134✔
989
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
134✔
990

134✔
991
        // Sanity check outputs.
134✔
992
        if len(outputs) < 1 {
142✔
993
                return nil, lnwallet.ErrNoOutputs
8✔
994
        }
8✔
995

996
        // Sanity check minConfs.
997
        if minConfs < 0 {
126✔
998
                return nil, lnwallet.ErrInvalidMinconf
×
999
        }
×
1000

1001
        // Use selected UTXOs if specified, otherwise default selection.
1002
        if len(inputs) != 0 {
126✔
UNCOV
1003
                return b.wallet.SendOutputsWithInput(
×
UNCOV
1004
                        outputs, nil, defaultAccount, minConfs, feeSatPerKB,
×
UNCOV
1005
                        strategy, label, inputs.ToSlice(),
×
UNCOV
1006
                )
×
UNCOV
1007
        }
×
1008

1009
        return b.wallet.SendOutputs(
126✔
1010
                outputs, nil, defaultAccount, minConfs, feeSatPerKB,
126✔
1011
                strategy, label,
126✔
1012
        )
126✔
1013
}
1014

1015
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
1016
// outputs. The transaction is not broadcasted to the network, but a new change
1017
// address might be created in the wallet database. In the case the wallet has
1018
// insufficient funds, or the outputs are non-standard, an error should be
1019
// returned. This method also takes the target fee expressed in sat/kw that
1020
// should be used when crafting the transaction.
1021
//
1022
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
1023
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
1024
//
1025
// NOTE: This method requires the global coin selection lock to be held.
1026
//
1027
// This is a part of the WalletController interface.
1028
func (b *BtcWallet) CreateSimpleTx(inputs fn.Set[wire.OutPoint],
1029
        outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, minConfs int32,
1030
        strategy base.CoinSelectionStrategy, dryRun bool) (
1031
        *txauthor.AuthoredTx, error) {
56✔
1032

56✔
1033
        // The fee rate is passed in using units of sat/kw, so we'll convert
56✔
1034
        // this to sat/KB as the CreateSimpleTx method requires this unit.
56✔
1035
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
56✔
1036

56✔
1037
        // Sanity check outputs.
56✔
1038
        if len(outputs) < 1 {
64✔
1039
                return nil, lnwallet.ErrNoOutputs
8✔
1040
        }
8✔
1041

1042
        // Sanity check minConfs.
1043
        if minConfs < 0 {
48✔
1044
                return nil, lnwallet.ErrInvalidMinconf
×
1045
        }
×
1046

1047
        for _, output := range outputs {
264✔
1048
                // When checking an output for things like dusty-ness, we'll
216✔
1049
                // use the default mempool relay fee rather than the target
216✔
1050
                // effective fee rate to ensure accuracy. Otherwise, we may
216✔
1051
                // mistakenly mark small-ish, but not quite dust output as
216✔
1052
                // dust.
216✔
1053
                err := txrules.CheckOutput(
216✔
1054
                        output, txrules.DefaultRelayFeePerKb,
216✔
1055
                )
216✔
1056
                if err != nil {
224✔
1057
                        return nil, err
8✔
1058
                }
8✔
1059
        }
1060

1061
        // Add the optional inputs to the transaction.
1062
        optFunc := wallet.WithCustomSelectUtxos(inputs.ToSlice())
40✔
1063

40✔
1064
        return b.wallet.CreateSimpleTx(
40✔
1065
                nil, defaultAccount, outputs, minConfs, feeSatPerKB,
40✔
1066
                strategy, dryRun, []wallet.TxCreateOption{optFunc}...,
40✔
1067
        )
40✔
1068
}
1069

1070
// LeaseOutput locks an output to the given ID, preventing it from being
1071
// available for any future coin selection attempts. The absolute time of the
1072
// lock's expiration is returned. The expiration of the lock can be extended by
1073
// successive invocations of this call. Outputs can be unlocked before their
1074
// expiration through `ReleaseOutput`.
1075
//
1076
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
1077
// output has already been locked to a different ID, then
1078
// wtxmgr.ErrOutputAlreadyLocked is returned.
1079
//
1080
// NOTE: This method requires the global coin selection lock to be held.
1081
func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
1082
        duration time.Duration) (time.Time, error) {
140✔
1083

140✔
1084
        // Make sure we don't attempt to double lock an output that's been
140✔
1085
        // locked by the in-memory implementation.
140✔
1086
        if b.wallet.LockedOutpoint(op) {
140✔
1087
                return time.Time{}, wtxmgr.ErrOutputAlreadyLocked
×
1088
        }
×
1089

1090
        lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
140✔
1091
        if err != nil {
140✔
1092
                return time.Time{}, err
×
1093
        }
×
1094

1095
        return lockedUntil, nil
140✔
1096
}
1097

1098
// ListLeasedOutputs returns a list of all currently locked outputs.
1099
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
UNCOV
1100
        error) {
×
UNCOV
1101

×
UNCOV
1102
        return b.wallet.ListLeasedOutputs()
×
UNCOV
1103
}
×
1104

1105
// ReleaseOutput unlocks an output, allowing it to be available for coin
1106
// selection if it remains unspent. The ID should match the one used to
1107
// originally lock the output.
1108
//
1109
// NOTE: This method requires the global coin selection lock to be held.
1110
func (b *BtcWallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error {
104✔
1111
        return b.wallet.ReleaseOutput(id, op)
104✔
1112
}
104✔
1113

1114
// ListUnspentWitness returns all unspent outputs which are version 0 witness
1115
// programs. The 'minConfs' and 'maxConfs' parameters indicate the minimum
1116
// and maximum number of confirmations an output needs in order to be returned
1117
// by this method. Passing -1 as 'minConfs' indicates that even unconfirmed
1118
// outputs should be returned. Using MaxInt32 as 'maxConfs' implies returning
1119
// all outputs with at least 'minConfs'. The account parameter serves as a
1120
// filter to retrieve the unspent outputs for a specific account.  When empty,
1121
// the unspent outputs of all wallet accounts are returned.
1122
//
1123
// NOTE: This method requires the global coin selection lock to be held.
1124
//
1125
// This is a part of the WalletController interface.
1126
func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32,
1127
        accountFilter string) ([]*lnwallet.Utxo, error) {
194✔
1128

194✔
1129
        // First, grab all the unfiltered currently unspent outputs.
194✔
1130
        unspentOutputs, err := b.wallet.ListUnspent(
194✔
1131
                minConfs, maxConfs, accountFilter,
194✔
1132
        )
194✔
1133
        if err != nil {
194✔
1134
                return nil, err
×
1135
        }
×
1136

1137
        // Next, we'll run through all the regular outputs, only saving those
1138
        // which are p2wkh outputs or a p2wsh output nested within a p2sh output.
1139
        witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
194✔
1140
        for _, output := range unspentOutputs {
3,177✔
1141
                pkScript, err := hex.DecodeString(output.ScriptPubKey)
2,983✔
1142
                if err != nil {
2,983✔
1143
                        return nil, err
×
1144
                }
×
1145

1146
                addressType := lnwallet.UnknownAddressType
2,983✔
1147
                if txscript.IsPayToWitnessPubKeyHash(pkScript) {
5,363✔
1148
                        addressType = lnwallet.WitnessPubKey
2,380✔
1149
                } else if txscript.IsPayToScriptHash(pkScript) {
3,025✔
1150
                        // TODO(roasbeef): This assumes all p2sh outputs returned by the
42✔
1151
                        // wallet are nested p2pkh. We can't check the redeem script because
42✔
1152
                        // the btcwallet service does not include it.
42✔
1153
                        addressType = lnwallet.NestedWitnessPubKey
42✔
1154
                } else if txscript.IsPayToTaproot(pkScript) {
1,164✔
1155
                        addressType = lnwallet.TaprootPubkey
561✔
1156
                }
561✔
1157

1158
                if addressType == lnwallet.WitnessPubKey ||
2,983✔
1159
                        addressType == lnwallet.NestedWitnessPubKey ||
2,983✔
1160
                        addressType == lnwallet.TaprootPubkey {
5,966✔
1161

2,983✔
1162
                        txid, err := chainhash.NewHashFromStr(output.TxID)
2,983✔
1163
                        if err != nil {
2,983✔
1164
                                return nil, err
×
1165
                        }
×
1166

1167
                        // We'll ensure we properly convert the amount given in
1168
                        // BTC to satoshis.
1169
                        amt, err := btcutil.NewAmount(output.Amount)
2,983✔
1170
                        if err != nil {
2,983✔
1171
                                return nil, err
×
1172
                        }
×
1173

1174
                        utxo := &lnwallet.Utxo{
2,983✔
1175
                                AddressType: addressType,
2,983✔
1176
                                Value:       amt,
2,983✔
1177
                                PkScript:    pkScript,
2,983✔
1178
                                OutPoint: wire.OutPoint{
2,983✔
1179
                                        Hash:  *txid,
2,983✔
1180
                                        Index: output.Vout,
2,983✔
1181
                                },
2,983✔
1182
                                Confirmations: output.Confirmations,
2,983✔
1183
                        }
2,983✔
1184
                        witnessOutputs = append(witnessOutputs, utxo)
2,983✔
1185
                }
1186

1187
        }
1188

1189
        return witnessOutputs, nil
194✔
1190
}
1191

1192
// mapRpcclientError maps an error from the `btcwallet/chain` package to
1193
// defined error in this package.
1194
//
1195
// NOTE: we are mapping the errors returned from `sendrawtransaction` RPC or
1196
// the reject reason from `testmempoolaccept` RPC.
1197
func mapRpcclientError(err error) error {
55✔
1198
        // If we failed to publish the transaction, check whether we got an
55✔
1199
        // error of known type.
55✔
1200
        switch {
55✔
1201
        // If the wallet reports a double spend, convert it to our internal
1202
        // ErrDoubleSpend and return.
1203
        case errors.Is(err, chain.ErrMempoolConflict),
1204
                errors.Is(err, chain.ErrMissingInputs),
1205
                errors.Is(err, chain.ErrTxAlreadyKnown),
1206
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
3✔
1207

3✔
1208
                return lnwallet.ErrDoubleSpend
3✔
1209

1210
        // If the wallet reports that fee requirements for accepting the tx
1211
        // into mempool are not met, convert it to our internal ErrMempoolFee
1212
        // and return.
1213
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
×
1214
                return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee, err.Error())
×
1215
        }
1216

1217
        return err
52✔
1218
}
1219

1220
// PublishTransaction performs cursory validation (dust checks, etc), then
1221
// finally broadcasts the passed transaction to the Bitcoin network. If
1222
// publishing the transaction fails, an error describing the reason is returned
1223
// and mapped to the wallet's internal error types. If the transaction is
1224
// already published to the network (either in the mempool or chain) no error
1225
// will be returned.
1226
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
55✔
1227
        // For neutrino backend there's no mempool, so we return early by
55✔
1228
        // publishing the transaction.
55✔
1229
        if b.chain.BackEnd() == "neutrino" {
68✔
1230
                err := b.wallet.PublishTransaction(tx, label)
13✔
1231

13✔
1232
                return mapRpcclientError(err)
13✔
1233
        }
13✔
1234

1235
        // For non-neutrino nodes, we will first check whether the transaction
1236
        // can be accepted by the mempool.
1237
        // Use a max feerate of 0 means the default value will be used when
1238
        // testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
1239
        // or 10,000 sat/vb.
1240
        results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
42✔
1241
        if err != nil {
42✔
1242
                // If the chain backend doesn't support the mempool acceptance
×
1243
                // test RPC, we'll just attempt to publish the transaction.
×
1244
                if errors.Is(err, rpcclient.ErrBackendVersion) {
×
1245
                        log.Warnf("TestMempoolAccept not supported by "+
×
1246
                                "backend, consider upgrading %s to a newer "+
×
1247
                                "version", b.chain.BackEnd())
×
1248

×
1249
                        err := b.wallet.PublishTransaction(tx, label)
×
1250

×
1251
                        return mapRpcclientError(err)
×
1252
                }
×
1253

1254
                return err
×
1255
        }
1256

1257
        // Sanity check that the expected single result is returned.
1258
        if len(results) != 1 {
42✔
1259
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
1260
                        "instead got %v", len(results))
×
1261
        }
×
1262

1263
        result := results[0]
42✔
1264
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
42✔
1265

42✔
1266
        // Once mempool check passed, we can publish the transaction.
42✔
1267
        if result.Allowed {
69✔
1268
                err = b.wallet.PublishTransaction(tx, label)
27✔
1269

27✔
1270
                return mapRpcclientError(err)
27✔
1271
        }
27✔
1272

1273
        // If the check failed, there's no need to publish it. We'll handle the
1274
        // error and return.
1275
        log.Warnf("Transaction %v not accepted by mempool: %v",
15✔
1276
                tx.TxHash(), result.RejectReason)
15✔
1277

15✔
1278
        // We need to use the string to create an error type and map it to a
15✔
1279
        // btcwallet error.
15✔
1280
        err = b.chain.MapRPCErr(errors.New(result.RejectReason))
15✔
1281

15✔
1282
        //nolint:ll
15✔
1283
        // These two errors are ignored inside `PublishTransaction`:
15✔
1284
        // https://github.com/btcsuite/btcwallet/blob/master/wallet/wallet.go#L3763
15✔
1285
        // To keep our current behavior, we need to ignore the same errors
15✔
1286
        // returned from TestMempoolAccept.
15✔
1287
        //
15✔
1288
        // TODO(yy): since `LightningWallet.PublishTransaction` always publish
15✔
1289
        // the same tx twice, we'd always get ErrTxAlreadyInMempool. We should
15✔
1290
        // instead create a new rebroadcaster that monitors the mempool, and
15✔
1291
        // only rebroadcast when the tx is evicted. This way we don't need to
15✔
1292
        // broadcast twice, and can instead return these errors here.
15✔
1293
        switch {
15✔
1294
        // NOTE: In addition to ignoring these errors, we need to call
1295
        // `PublishTransaction` again because we need to mark the label in the
1296
        // wallet. We can remove this exception once we have the above TODO
1297
        // fixed.
1298
        case errors.Is(err, chain.ErrTxAlreadyInMempool),
1299
                errors.Is(err, chain.ErrTxAlreadyKnown),
1300
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
6✔
1301

6✔
1302
                err := b.wallet.PublishTransaction(tx, label)
6✔
1303
                return mapRpcclientError(err)
6✔
1304
        }
1305

1306
        return mapRpcclientError(err)
9✔
1307
}
1308

1309
// LabelTransaction adds a label to a transaction. If the tx already
1310
// has a label, this call will fail unless the overwrite parameter
1311
// is set. Labels must not be empty, and they are limited to 500 chars.
1312
//
1313
// Note: it is part of the WalletController interface.
1314
func (b *BtcWallet) LabelTransaction(hash chainhash.Hash, label string,
UNCOV
1315
        overwrite bool) error {
×
UNCOV
1316

×
UNCOV
1317
        return b.wallet.LabelTransaction(hash, label, overwrite)
×
UNCOV
1318
}
×
1319

1320
// extractBalanceDelta extracts the net balance delta from the PoV of the
1321
// wallet given a TransactionSummary.
1322
func extractBalanceDelta(
1323
        txSummary base.TransactionSummary,
1324
        tx *wire.MsgTx,
1325
) (btcutil.Amount, error) {
2,189✔
1326
        // For each input we debit the wallet's outflow for this transaction,
2,189✔
1327
        // and for each output we credit the wallet's inflow for this
2,189✔
1328
        // transaction.
2,189✔
1329
        var balanceDelta btcutil.Amount
2,189✔
1330
        for _, input := range txSummary.MyInputs {
3,339✔
1331
                balanceDelta -= input.PreviousAmount
1,150✔
1332
        }
1,150✔
1333
        for _, output := range txSummary.MyOutputs {
4,358✔
1334
                balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
2,169✔
1335
        }
2,169✔
1336

1337
        return balanceDelta, nil
2,189✔
1338
}
1339

1340
// getPreviousOutpoints is a helper function which gets the previous
1341
// outpoints of a transaction.
1342
func getPreviousOutpoints(wireTx *wire.MsgTx,
1343
        myInputs []base.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
2,194✔
1344

2,194✔
1345
        // isOurOutput is a map containing the output indices
2,194✔
1346
        // controlled by the wallet.
2,194✔
1347
        // Note: We make use of the information in `myInputs` provided
2,194✔
1348
        // by the `wallet.TransactionSummary` structure that holds
2,194✔
1349
        // information only if the input/previous_output is controlled by the wallet.
2,194✔
1350
        isOurOutput := make(map[uint32]bool, len(myInputs))
2,194✔
1351
        for _, myInput := range myInputs {
3,352✔
1352
                isOurOutput[myInput.Index] = true
1,158✔
1353
        }
1,158✔
1354

1355
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
2,194✔
1356
        for idx, txIn := range wireTx.TxIn {
6,374✔
1357
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
4,180✔
1358
                        OutPoint:    txIn.PreviousOutPoint.String(),
4,180✔
1359
                        IsOurOutput: isOurOutput[uint32(idx)],
4,180✔
1360
                }
4,180✔
1361
        }
4,180✔
1362

1363
        return previousOutpoints
2,194✔
1364
}
1365

1366
// GetTransactionDetails returns details of a transaction given its
1367
// transaction hash.
1368
func (b *BtcWallet) GetTransactionDetails(
1369
        txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
4✔
1370

4✔
1371
        // Grab the best block the wallet knows of, we'll use this to calculate
4✔
1372
        // # of confirmations shortly below.
4✔
1373
        bestBlock := b.wallet.Manager.SyncedTo()
4✔
1374
        currentHeight := bestBlock.Height
4✔
1375
        tx, err := b.wallet.GetTransaction(*txHash)
4✔
1376
        if err != nil {
4✔
UNCOV
1377
                return nil, err
×
UNCOV
1378
        }
×
1379

1380
        // For both confirmed and unconfirmed transactions, create a
1381
        // TransactionDetail which re-packages the data returned by the base
1382
        // wallet.
1383
        if tx.Confirmations > 0 {
8✔
1384
                txDetails, err := minedTransactionsToDetails(
4✔
1385
                        currentHeight,
4✔
1386
                        base.Block{
4✔
1387
                                Transactions: []base.TransactionSummary{
4✔
1388
                                        tx.Summary,
4✔
1389
                                },
4✔
1390
                                Hash:      tx.BlockHash,
4✔
1391
                                Height:    tx.Height,
4✔
1392
                                Timestamp: tx.Summary.Timestamp},
4✔
1393
                        b.netParams,
4✔
1394
                )
4✔
1395
                if err != nil {
4✔
1396
                        return nil, err
×
1397
                }
×
1398

1399
                return txDetails[0], nil
4✔
1400
        }
1401

UNCOV
1402
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
×
1403
}
1404

1405
// minedTransactionsToDetails is a helper function which converts a summary
1406
// information about mined transactions to a TransactionDetail.
1407
func minedTransactionsToDetails(
1408
        currentHeight int32,
1409
        block base.Block,
1410
        chainParams *chaincfg.Params,
1411
) ([]*lnwallet.TransactionDetail, error) {
430✔
1412

430✔
1413
        details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
430✔
1414
        for _, tx := range block.Transactions {
2,584✔
1415
                wireTx := &wire.MsgTx{}
2,154✔
1416
                txReader := bytes.NewReader(tx.Transaction)
2,154✔
1417

2,154✔
1418
                if err := wireTx.Deserialize(txReader); err != nil {
2,154✔
1419
                        return nil, err
×
1420
                }
×
1421

1422
                // isOurAddress is a map containing the output indices
1423
                // controlled by the wallet.
1424
                // Note: We make use of the information in `MyOutputs` provided
1425
                // by the `wallet.TransactionSummary` structure that holds
1426
                // information only if the output is controlled by the wallet.
1427
                isOurAddress := make(map[int]bool, len(tx.MyOutputs))
2,154✔
1428
                for _, o := range tx.MyOutputs {
4,284✔
1429
                        isOurAddress[int(o.Index)] = true
2,130✔
1430
                }
2,130✔
1431

1432
                var outputDetails []lnwallet.OutputDetail
2,154✔
1433
                for i, txOut := range wireTx.TxOut {
6,398✔
1434
                        var addresses []btcutil.Address
4,244✔
1435
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
4,244✔
1436
                                txOut.PkScript, chainParams,
4,244✔
1437
                        )
4,244✔
1438
                        if err == nil {
8,488✔
1439
                                // Add supported addresses.
4,244✔
1440
                                addresses = outAddresses
4,244✔
1441
                        }
4,244✔
1442

1443
                        outputDetails = append(outputDetails, lnwallet.OutputDetail{
4,244✔
1444
                                OutputType:   sc,
4,244✔
1445
                                Addresses:    addresses,
4,244✔
1446
                                PkScript:     txOut.PkScript,
4,244✔
1447
                                OutputIndex:  i,
4,244✔
1448
                                Value:        btcutil.Amount(txOut.Value),
4,244✔
1449
                                IsOurAddress: isOurAddress[i],
4,244✔
1450
                        })
4,244✔
1451
                }
1452

1453
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
2,154✔
1454

2,154✔
1455
                txDetail := &lnwallet.TransactionDetail{
2,154✔
1456
                        Hash:              *tx.Hash,
2,154✔
1457
                        NumConfirmations:  currentHeight - block.Height + 1,
2,154✔
1458
                        BlockHash:         block.Hash,
2,154✔
1459
                        BlockHeight:       block.Height,
2,154✔
1460
                        Timestamp:         block.Timestamp,
2,154✔
1461
                        TotalFees:         int64(tx.Fee),
2,154✔
1462
                        OutputDetails:     outputDetails,
2,154✔
1463
                        RawTx:             tx.Transaction,
2,154✔
1464
                        Label:             tx.Label,
2,154✔
1465
                        PreviousOutpoints: previousOutpoints,
2,154✔
1466
                }
2,154✔
1467

2,154✔
1468
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
2,154✔
1469
                if err != nil {
2,154✔
1470
                        return nil, err
×
1471
                }
×
1472
                txDetail.Value = balanceDelta
2,154✔
1473

2,154✔
1474
                details = append(details, txDetail)
2,154✔
1475
        }
1476

1477
        return details, nil
430✔
1478
}
1479

1480
// unminedTransactionsToDetail is a helper function which converts a summary
1481
// for an unconfirmed transaction to a transaction detail.
1482
func unminedTransactionsToDetail(
1483
        summary base.TransactionSummary,
1484
        chainParams *chaincfg.Params,
1485
) (*lnwallet.TransactionDetail, error) {
35✔
1486

35✔
1487
        wireTx := &wire.MsgTx{}
35✔
1488
        txReader := bytes.NewReader(summary.Transaction)
35✔
1489

35✔
1490
        if err := wireTx.Deserialize(txReader); err != nil {
35✔
1491
                return nil, err
×
1492
        }
×
1493

1494
        // isOurAddress is a map containing the output indices controlled by
1495
        // the wallet.
1496
        // Note: We make use of the information in `MyOutputs` provided
1497
        // by the `wallet.TransactionSummary` structure that holds information
1498
        // only if the output is controlled by the wallet.
1499
        isOurAddress := make(map[int]bool, len(summary.MyOutputs))
35✔
1500
        for _, o := range summary.MyOutputs {
74✔
1501
                isOurAddress[int(o.Index)] = true
39✔
1502
        }
39✔
1503

1504
        var outputDetails []lnwallet.OutputDetail
35✔
1505
        for i, txOut := range wireTx.TxOut {
105✔
1506
                var addresses []btcutil.Address
70✔
1507
                sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
70✔
1508
                        txOut.PkScript, chainParams,
70✔
1509
                )
70✔
1510
                if err == nil {
140✔
1511
                        // Add supported addresses.
70✔
1512
                        addresses = outAddresses
70✔
1513
                }
70✔
1514

1515
                outputDetails = append(outputDetails, lnwallet.OutputDetail{
70✔
1516
                        OutputType:   sc,
70✔
1517
                        Addresses:    addresses,
70✔
1518
                        PkScript:     txOut.PkScript,
70✔
1519
                        OutputIndex:  i,
70✔
1520
                        Value:        btcutil.Amount(txOut.Value),
70✔
1521
                        IsOurAddress: isOurAddress[i],
70✔
1522
                })
70✔
1523
        }
1524

1525
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
35✔
1526

35✔
1527
        txDetail := &lnwallet.TransactionDetail{
35✔
1528
                Hash:              *summary.Hash,
35✔
1529
                TotalFees:         int64(summary.Fee),
35✔
1530
                Timestamp:         summary.Timestamp,
35✔
1531
                OutputDetails:     outputDetails,
35✔
1532
                RawTx:             summary.Transaction,
35✔
1533
                Label:             summary.Label,
35✔
1534
                PreviousOutpoints: previousOutpoints,
35✔
1535
        }
35✔
1536

35✔
1537
        balanceDelta, err := extractBalanceDelta(summary, wireTx)
35✔
1538
        if err != nil {
35✔
1539
                return nil, err
×
1540
        }
×
1541
        txDetail.Value = balanceDelta
35✔
1542

35✔
1543
        return txDetail, nil
35✔
1544
}
1545

1546
// ListTransactionDetails returns a list of all transactions which are relevant
1547
// to the wallet over [startHeight;endHeight]. If start height is greater than
1548
// end height, the transactions will be retrieved in reverse order. To include
1549
// unconfirmed transactions, endHeight should be set to the special value -1.
1550
// This will return transactions from the tip of the chain until the start
1551
// height (inclusive) and unconfirmed transactions. The account parameter serves
1552
// as a filter to retrieve the transactions relevant to a specific account. When
1553
// empty, transactions of all wallet accounts are returned.
1554
//
1555
// This is a part of the WalletController interface.
1556
func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
1557
        accountFilter string, indexOffset uint32,
1558
        maxTransactions uint32) ([]*lnwallet.TransactionDetail, uint64, uint64,
1559
        error) {
108✔
1560

108✔
1561
        // Grab the best block the wallet knows of, we'll use this to calculate
108✔
1562
        // # of confirmations shortly below.
108✔
1563
        bestBlock := b.wallet.Manager.SyncedTo()
108✔
1564
        currentHeight := bestBlock.Height
108✔
1565

108✔
1566
        // We'll attempt to find all transactions from start to end height.
108✔
1567
        start := base.NewBlockIdentifierFromHeight(startHeight)
108✔
1568
        stop := base.NewBlockIdentifierFromHeight(endHeight)
108✔
1569
        txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
108✔
1570
        if err != nil {
108✔
1571
                return nil, 0, 0, err
×
1572
        }
×
1573

1574
        txDetails := make([]*lnwallet.TransactionDetail, 0,
108✔
1575
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
108✔
1576

108✔
1577
        // For both confirmed and unconfirmed transactions, create a
108✔
1578
        // TransactionDetail which re-packages the data returned by the base
108✔
1579
        // wallet.
108✔
1580
        for _, blockPackage := range txns.MinedTransactions {
530✔
1581
                details, err := minedTransactionsToDetails(
422✔
1582
                        currentHeight, blockPackage, b.netParams,
422✔
1583
                )
422✔
1584
                if err != nil {
422✔
1585
                        return nil, 0, 0, err
×
1586
                }
×
1587

1588
                txDetails = append(txDetails, details...)
422✔
1589
        }
1590
        for _, tx := range txns.UnminedTransactions {
130✔
1591
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
22✔
1592
                if err != nil {
22✔
1593
                        return nil, 0, 0, err
×
1594
                }
×
1595

1596
                txDetails = append(txDetails, detail)
22✔
1597
        }
1598

1599
        // Return empty transaction list, if offset is more than all
1600
        // transactions.
1601
        if int(indexOffset) >= len(txDetails) {
120✔
1602
                txDetails = []*lnwallet.TransactionDetail{}
12✔
1603

12✔
1604
                return txDetails, 0, 0, nil
12✔
1605
        }
12✔
1606

1607
        end := indexOffset + maxTransactions
96✔
1608

96✔
1609
        // If maxTransactions is set to 0, then we'll return all transactions
96✔
1610
        // starting from the offset.
96✔
1611
        if maxTransactions == 0 {
104✔
1612
                end = uint32(len(txDetails))
8✔
1613
                txDetails = txDetails[indexOffset:end]
8✔
1614

8✔
1615
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
8✔
1616
        }
8✔
1617

1618
        if end > uint32(len(txDetails)) {
168✔
1619
                end = uint32(len(txDetails))
80✔
1620
        }
80✔
1621

1622
        txDetails = txDetails[indexOffset:end]
88✔
1623

88✔
1624
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
88✔
1625
}
1626

1627
// txSubscriptionClient encapsulates the transaction notification client from
1628
// the base wallet. Notifications received from the client will be proxied over
1629
// two distinct channels.
1630
type txSubscriptionClient struct {
1631
        txClient base.TransactionNotificationsClient
1632

1633
        confirmed   chan *lnwallet.TransactionDetail
1634
        unconfirmed chan *lnwallet.TransactionDetail
1635

1636
        w *base.Wallet
1637

1638
        wg   sync.WaitGroup
1639
        quit chan struct{}
1640
}
1641

1642
// ConfirmedTransactions returns a channel which will be sent on as new
1643
// relevant transactions are confirmed.
1644
//
1645
// This is part of the TransactionSubscription interface.
1646
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
12✔
1647
        return t.confirmed
12✔
1648
}
12✔
1649

1650
// UnconfirmedTransactions returns a channel which will be sent on as
1651
// new relevant transactions are seen within the network.
1652
//
1653
// This is part of the TransactionSubscription interface.
1654
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
13✔
1655
        return t.unconfirmed
13✔
1656
}
13✔
1657

1658
// Cancel finalizes the subscription, cleaning up any resources allocated.
1659
//
1660
// This is part of the TransactionSubscription interface.
1661
func (t *txSubscriptionClient) Cancel() {
4✔
1662
        close(t.quit)
4✔
1663
        t.wg.Wait()
4✔
1664

4✔
1665
        t.txClient.Done()
4✔
1666
}
4✔
1667

1668
// notificationProxier proxies the notifications received by the underlying
1669
// wallet's notification client to a higher-level TransactionSubscription
1670
// client.
1671
func (t *txSubscriptionClient) notificationProxier() {
4✔
1672
        defer t.wg.Done()
4✔
1673

4✔
1674
out:
4✔
1675
        for {
25✔
1676
                select {
21✔
1677
                case txNtfn := <-t.txClient.C:
17✔
1678
                        // TODO(roasbeef): handle detached blocks
17✔
1679
                        currentHeight := t.w.Manager.SyncedTo().Height
17✔
1680

17✔
1681
                        // Launch a goroutine to re-package and send
17✔
1682
                        // notifications for any newly confirmed transactions.
17✔
1683
                        //nolint:ll
17✔
1684
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1685
                                for _, block := range txNtfn.AttachedBlocks {
21✔
1686
                                        details, err := minedTransactionsToDetails(
4✔
1687
                                                currentHeight, block,
4✔
1688
                                                t.w.ChainParams(),
4✔
1689
                                        )
4✔
1690
                                        if err != nil {
4✔
1691
                                                continue
×
1692
                                        }
1693

1694
                                        for _, d := range details {
16✔
1695
                                                select {
12✔
1696
                                                case t.confirmed <- d:
12✔
1697
                                                case <-t.quit:
×
1698
                                                        return
×
1699
                                                }
1700
                                        }
1701
                                }
1702
                        }(txNtfn)
1703

1704
                        // Launch a goroutine to re-package and send
1705
                        // notifications for any newly unconfirmed transactions.
1706
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1707
                                for _, tx := range txNtfn.UnminedTransactions {
30✔
1708
                                        detail, err := unminedTransactionsToDetail(
13✔
1709
                                                tx, t.w.ChainParams(),
13✔
1710
                                        )
13✔
1711
                                        if err != nil {
13✔
1712
                                                continue
×
1713
                                        }
1714

1715
                                        select {
13✔
1716
                                        case t.unconfirmed <- detail:
13✔
1717
                                        case <-t.quit:
×
1718
                                                return
×
1719
                                        }
1720
                                }
1721
                        }(txNtfn)
1722
                case <-t.quit:
4✔
1723
                        break out
4✔
1724
                }
1725
        }
1726
}
1727

1728
// SubscribeTransactions returns a TransactionSubscription client which
1729
// is capable of receiving async notifications as new transactions
1730
// related to the wallet are seen within the network, or found in
1731
// blocks.
1732
//
1733
// This is a part of the WalletController interface.
1734
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
4✔
1735
        walletClient := b.wallet.NtfnServer.TransactionNotifications()
4✔
1736

4✔
1737
        txClient := &txSubscriptionClient{
4✔
1738
                txClient:    walletClient,
4✔
1739
                confirmed:   make(chan *lnwallet.TransactionDetail),
4✔
1740
                unconfirmed: make(chan *lnwallet.TransactionDetail),
4✔
1741
                w:           b.wallet,
4✔
1742
                quit:        make(chan struct{}),
4✔
1743
        }
4✔
1744
        txClient.wg.Add(1)
4✔
1745
        go txClient.notificationProxier()
4✔
1746

4✔
1747
        return txClient, nil
4✔
1748
}
4✔
1749

1750
// IsSynced returns a boolean indicating if from the PoV of the wallet, it has
1751
// fully synced to the current best block in the main chain.
1752
//
1753
// This is a part of the WalletController interface.
1754
func (b *BtcWallet) IsSynced() (bool, int64, error) {
218✔
1755
        // Grab the best chain state the wallet is currently aware of.
218✔
1756
        syncState := b.wallet.Manager.SyncedTo()
218✔
1757

218✔
1758
        // We'll also extract the current best wallet timestamp so the caller
218✔
1759
        // can get an idea of where we are in the sync timeline.
218✔
1760
        bestTimestamp := syncState.Timestamp.Unix()
218✔
1761

218✔
1762
        // Next, query the chain backend to grab the info about the tip of the
218✔
1763
        // main chain.
218✔
1764
        bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
218✔
1765
        if err != nil {
218✔
1766
                return false, 0, err
×
1767
        }
×
1768

1769
        // Make sure the backing chain has been considered synced first.
1770
        if !b.wallet.ChainSynced() {
218✔
UNCOV
1771
                bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
×
UNCOV
1772
                if err != nil {
×
1773
                        return false, 0, err
×
1774
                }
×
UNCOV
1775
                bestTimestamp = bestHeader.Timestamp.Unix()
×
UNCOV
1776
                return false, bestTimestamp, nil
×
1777
        }
1778

1779
        // If the wallet hasn't yet fully synced to the node's best chain tip,
1780
        // then we're not yet fully synced.
1781
        if syncState.Height < bestHeight {
294✔
1782
                return false, bestTimestamp, nil
76✔
1783
        }
76✔
1784

1785
        // If the wallet is on par with the current best chain tip, then we
1786
        // still may not yet be synced as the chain backend may still be
1787
        // catching up to the main chain. So we'll grab the block header in
1788
        // order to make a guess based on the current time stamp.
1789
        blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
142✔
1790
        if err != nil {
142✔
1791
                return false, 0, err
×
1792
        }
×
1793

1794
        // If the timestamp on the best header is more than 2 hours in the
1795
        // past, then we're not yet synced.
1796
        minus24Hours := time.Now().Add(-2 * time.Hour)
142✔
1797
        if blockHeader.Timestamp.Before(minus24Hours) {
142✔
UNCOV
1798
                return false, bestTimestamp, nil
×
UNCOV
1799
        }
×
1800

1801
        return true, bestTimestamp, nil
142✔
1802
}
1803

1804
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
1805
// in recovery mode. It also returns a float64, ranging from 0 to 1,
1806
// representing the recovery progress made so far.
1807
//
1808
// This is a part of the WalletController interface.
1809
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
12✔
1810
        isRecoveryMode := true
12✔
1811
        progress := float64(0)
12✔
1812

12✔
1813
        // A zero value in RecoveryWindow indicates there is no trigger of
12✔
1814
        // recovery mode.
12✔
1815
        if b.cfg.RecoveryWindow == 0 {
16✔
1816
                isRecoveryMode = false
4✔
1817
                return isRecoveryMode, progress, nil
4✔
1818
        }
4✔
1819

1820
        // Query the wallet's birthday block height from db.
1821
        var birthdayBlock waddrmgr.BlockStamp
8✔
1822
        err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
16✔
1823
                var err error
8✔
1824
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
8✔
1825
                birthdayBlock, _, err = b.wallet.Manager.BirthdayBlock(addrmgrNs)
8✔
1826
                if err != nil {
8✔
1827
                        return err
×
1828
                }
×
1829
                return nil
8✔
1830
        })
1831

1832
        if err != nil {
8✔
1833
                // The wallet won't start until the backend is synced, thus the birthday
×
1834
                // block won't be set and this particular error will be returned. We'll
×
1835
                // catch this error and return a progress of 0 instead.
×
1836
                if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
×
1837
                        return isRecoveryMode, progress, nil
×
1838
                }
×
1839

1840
                return isRecoveryMode, progress, err
×
1841
        }
1842

1843
        // Grab the best chain state the wallet is currently aware of.
1844
        syncState := b.wallet.Manager.SyncedTo()
8✔
1845

8✔
1846
        // Next, query the chain backend to grab the info about the tip of the
8✔
1847
        // main chain.
8✔
1848
        //
8✔
1849
        // NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
8✔
1850
        // The process purposefully doesn't update the best height. It might create
8✔
1851
        // a small difference between the height queried here and the height used
8✔
1852
        // in the recovery process, ie, the bestHeight used here might be greater,
8✔
1853
        // showing the recovery being unfinished while it's actually done. However,
8✔
1854
        // during a wallet rescan after the recovery, the wallet's synced height
8✔
1855
        // will catch up and this won't be an issue.
8✔
1856
        _, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
8✔
1857
        if err != nil {
8✔
1858
                return isRecoveryMode, progress, err
×
1859
        }
×
1860

1861
        // The birthday block height might be greater than the current synced height
1862
        // in a newly restored wallet, and might be greater than the chain tip if a
1863
        // rollback happens. In that case, we will return zero progress here.
1864
        if syncState.Height < birthdayBlock.Height ||
8✔
1865
                bestHeight < birthdayBlock.Height {
8✔
1866

×
1867
                return isRecoveryMode, progress, nil
×
1868
        }
×
1869

1870
        // progress is the ratio of the [number of blocks processed] over the [total
1871
        // number of blocks] needed in a recovery mode, ranging from 0 to 1, in
1872
        // which,
1873
        // - total number of blocks is the current chain's best height minus the
1874
        //   wallet's birthday height plus 1.
1875
        // - number of blocks processed is the wallet's synced height minus its
1876
        //   birthday height plus 1.
1877
        // - If the wallet is born very recently, the bestHeight can be equal to
1878
        //   the birthdayBlock.Height, and it will recovery instantly.
1879
        progress = float64(syncState.Height-birthdayBlock.Height+1) /
8✔
1880
                float64(bestHeight-birthdayBlock.Height+1)
8✔
1881

8✔
1882
        return isRecoveryMode, progress, nil
8✔
1883
}
1884

1885
// FetchTx attempts to fetch a transaction in the wallet's database identified
1886
// by the passed transaction hash. If the transaction can't be found, then a
1887
// nil pointer is returned.
UNCOV
1888
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
×
UNCOV
1889
        var targetTx *wtxmgr.TxDetails
×
UNCOV
1890
        err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
×
UNCOV
1891
                wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
×
UNCOV
1892
                txDetails, err := b.wallet.TxStore.TxDetails(wtxmgrNs, &txHash)
×
UNCOV
1893
                if err != nil {
×
1894
                        return err
×
1895
                }
×
1896

UNCOV
1897
                targetTx = txDetails
×
UNCOV
1898

×
UNCOV
1899
                return nil
×
1900
        })
UNCOV
1901
        if err != nil {
×
1902
                return nil, err
×
1903
        }
×
1904

UNCOV
1905
        if targetTx == nil {
×
UNCOV
1906
                return nil, nil
×
UNCOV
1907
        }
×
1908

UNCOV
1909
        return &targetTx.TxRecord.MsgTx, nil
×
1910
}
1911

1912
// RemoveDescendants attempts to remove any transaction from the wallet's tx
1913
// store (that may be unconfirmed) that spends outputs created by the passed
1914
// transaction. This remove propagates recursively down the chain of descendent
1915
// transactions.
UNCOV
1916
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
×
UNCOV
1917
        txRecord, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
×
UNCOV
1918
        if err != nil {
×
1919
                return err
×
1920
        }
×
1921

UNCOV
1922
        return walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
×
UNCOV
1923
                wtxmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
×
UNCOV
1924
                return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
×
UNCOV
1925
        })
×
1926
}
1927

1928
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
1929
// the mempool acceptance of a transaction.
1930
func (b *BtcWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
5✔
1931
        // Use a max feerate of 0 means the default value will be used when
5✔
1932
        // testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
5✔
1933
        // or 10,000 sat/vb.
5✔
1934
        results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
5✔
1935
        if err != nil {
7✔
1936
                return err
2✔
1937
        }
2✔
1938

1939
        // Sanity check that the expected single result is returned.
1940
        if len(results) != 1 {
4✔
1941
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
1✔
1942
                        "instead got %v", len(results))
1✔
1943
        }
1✔
1944

1945
        result := results[0]
2✔
1946
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
2✔
1947

2✔
1948
        // Mempool check failed, we now map the reject reason to a proper RPC
2✔
1949
        // error and return it.
2✔
1950
        if !result.Allowed {
3✔
1951
                err := b.chain.MapRPCErr(errors.New(result.RejectReason))
1✔
1952

1✔
1953
                return fmt.Errorf("mempool rejection: %w", err)
1✔
1954
        }
1✔
1955

1956
        return nil
1✔
1957
}
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