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

lightningnetwork / lnd / 13975285956

20 Mar 2025 05:12PM UTC coverage: 68.667%. First build
13975285956

Pull #9609

github

web-flow
Merge c842aa78c into bcc80e7f9
Pull Request #9609: Fix inaccurate `listunspent` result

29 of 38 new or added lines in 8 files covered. (76.32%)

130412 of 189920 relevant lines covered (68.67%)

23529.31 hits per line

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

80.04
/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
        // lightningAddrSchema is the scope addr schema for all keys that we
61
        // derive. We'll treat them all as p2wkh addresses, as atm we must
62
        // specify a particular type.
63
        lightningAddrSchema = waddrmgr.ScopeAddrSchema{
64
                ExternalAddrType: waddrmgr.WitnessPubKey,
65
                InternalAddrType: waddrmgr.WitnessPubKey,
66
        }
67

68
        // LndDefaultKeyScopes is the list of default key scopes that lnd adds
69
        // to its wallet.
70
        LndDefaultKeyScopes = []waddrmgr.KeyScope{
71
                waddrmgr.KeyScopeBIP0049Plus,
72
                waddrmgr.KeyScopeBIP0084,
73
                waddrmgr.KeyScopeBIP0086,
74
        }
75

76
        // errNoImportedAddrGen is an error returned when a new address is
77
        // requested for the default imported account within the wallet.
78
        errNoImportedAddrGen = errors.New("addresses cannot be generated for " +
79
                "the default imported account")
80
)
81

82
// BtcWallet is an implementation of the lnwallet.WalletController interface
83
// backed by an active instance of btcwallet. At the time of the writing of
84
// this documentation, this implementation requires a full btcd node to
85
// operate.
86
type BtcWallet struct {
87
        // wallet is an active instance of btcwallet.
88
        wallet *base.Wallet
89

90
        chain chain.Interface
91

92
        db walletdb.DB
93

94
        cfg *Config
95

96
        netParams *chaincfg.Params
97

98
        chainKeyScope waddrmgr.KeyScope
99

100
        blockCache *blockcache.BlockCache
101

102
        *input.MusigSessionManager
103
}
104

105
// A compile time check to ensure that BtcWallet implements the
106
// WalletController and BlockChainIO interfaces.
107
var _ lnwallet.WalletController = (*BtcWallet)(nil)
108
var _ lnwallet.BlockChainIO = (*BtcWallet)(nil)
109

110
// New returns a new fully initialized instance of BtcWallet given a valid
111
// configuration struct.
112
func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
15✔
113
        // Create the key scope for the coin type being managed by this wallet.
15✔
114
        chainKeyScope := waddrmgr.KeyScope{
15✔
115
                Purpose: keychain.BIP0043Purpose,
15✔
116
                Coin:    cfg.CoinType,
15✔
117
        }
15✔
118

15✔
119
        // Maybe the wallet has already been opened and unlocked by the
15✔
120
        // WalletUnlocker. So if we get a non-nil value from the config,
15✔
121
        // we assume everything is in order.
15✔
122
        var wallet = cfg.Wallet
15✔
123
        if wallet == nil {
30✔
124
                // No ready wallet was passed, so try to open an existing one.
15✔
125
                var pubPass []byte
15✔
126
                if cfg.PublicPass == nil {
27✔
127
                        pubPass = defaultPubPassphrase
12✔
128
                } else {
15✔
129
                        pubPass = cfg.PublicPass
3✔
130
                }
3✔
131

132
                loader, err := NewWalletLoader(
15✔
133
                        cfg.NetParams, cfg.RecoveryWindow, cfg.LoaderOptions...,
15✔
134
                )
15✔
135
                if err != nil {
15✔
136
                        return nil, err
×
137
                }
×
138
                walletExists, err := loader.WalletExists()
15✔
139
                if err != nil {
15✔
140
                        return nil, err
×
141
                }
×
142

143
                if !walletExists {
30✔
144
                        // Wallet has never been created, perform initial
15✔
145
                        // set up.
15✔
146
                        wallet, err = loader.CreateNewWallet(
15✔
147
                                pubPass, cfg.PrivatePass, cfg.HdSeed,
15✔
148
                                cfg.Birthday,
15✔
149
                        )
15✔
150
                        if err != nil {
15✔
151
                                return nil, err
×
152
                        }
×
153
                } else {
3✔
154
                        // Wallet has been created and been initialized at
3✔
155
                        // this point, open it along with all the required DB
3✔
156
                        // namespaces, and the DB itself.
3✔
157
                        wallet, err = loader.OpenExistingWallet(pubPass, false)
3✔
158
                        if err != nil {
3✔
159
                                return nil, err
×
160
                        }
×
161
                }
162
        }
163

164
        finalWallet := &BtcWallet{
15✔
165
                cfg:           &cfg,
15✔
166
                wallet:        wallet,
15✔
167
                db:            wallet.Database(),
15✔
168
                chain:         cfg.ChainSource,
15✔
169
                netParams:     cfg.NetParams,
15✔
170
                chainKeyScope: chainKeyScope,
15✔
171
                blockCache:    blockCache,
15✔
172
        }
15✔
173

15✔
174
        finalWallet.MusigSessionManager = input.NewMusigSessionManager(
15✔
175
                finalWallet.fetchPrivKey,
15✔
176
        )
15✔
177

15✔
178
        return finalWallet, nil
15✔
179
}
180

181
// loaderCfg holds optional wallet loader configuration.
182
type loaderCfg struct {
183
        dbDirPath      string
184
        noFreelistSync bool
185
        dbTimeout      time.Duration
186
        useLocalDB     bool
187
        externalDB     kvdb.Backend
188
}
189

190
// LoaderOption is a functional option to update the optional loader config.
191
type LoaderOption func(*loaderCfg)
192

193
// LoaderWithLocalWalletDB configures the wallet loader to use the local db.
194
func LoaderWithLocalWalletDB(dbDirPath string, noFreelistSync bool,
195
        dbTimeout time.Duration) LoaderOption {
23✔
196

23✔
197
        return func(cfg *loaderCfg) {
55✔
198
                cfg.dbDirPath = dbDirPath
32✔
199
                cfg.noFreelistSync = noFreelistSync
32✔
200
                cfg.dbTimeout = dbTimeout
32✔
201
                cfg.useLocalDB = true
32✔
202
        }
32✔
203
}
204

205
// LoaderWithExternalWalletDB configures the wallet loadr to use an external db.
206
func LoaderWithExternalWalletDB(db kvdb.Backend) LoaderOption {
×
207
        return func(cfg *loaderCfg) {
×
208
                cfg.externalDB = db
×
209
        }
×
210
}
211

212
// NewWalletLoader constructs a wallet loader.
213
func NewWalletLoader(chainParams *chaincfg.Params, recoveryWindow uint32,
214
        opts ...LoaderOption) (*base.Loader, error) {
32✔
215

32✔
216
        cfg := &loaderCfg{}
32✔
217

32✔
218
        // Apply all functional options.
32✔
219
        for _, o := range opts {
64✔
220
                o(cfg)
32✔
221
        }
32✔
222

223
        if cfg.externalDB != nil && cfg.useLocalDB {
32✔
224
                return nil, fmt.Errorf("wallet can either be in the local or " +
×
225
                        "an external db")
×
226
        }
×
227

228
        if cfg.externalDB != nil {
32✔
229
                loader, err := base.NewLoaderWithDB(
×
230
                        chainParams, recoveryWindow, cfg.externalDB,
×
231
                        func() (bool, error) {
×
232
                                return externalWalletExists(cfg.externalDB)
×
233
                        },
×
234
                )
235
                if err != nil {
×
236
                        return nil, err
×
237
                }
×
238

239
                // Decorate wallet db with out own key such that we
240
                // can always check whether the wallet exists or not.
241
                loader.OnWalletCreated(onWalletCreated)
×
242
                return loader, nil
×
243
        }
244

245
        return base.NewLoader(
32✔
246
                chainParams, cfg.dbDirPath, cfg.noFreelistSync,
32✔
247
                cfg.dbTimeout, recoveryWindow,
32✔
248
        ), nil
32✔
249
}
250

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

263
                return nil
×
264
        }, func() {})
×
265

266
        return exists, err
×
267
}
268

269
// onWalletCreated is executed when btcwallet creates the wallet the first time.
270
func onWalletCreated(tx kvdb.RwTx) error {
×
271
        metaBucket, err := tx.CreateTopLevelBucket([]byte(walletMetaBucket))
×
272
        if err != nil {
×
273
                return err
×
274
        }
×
275

276
        return metaBucket.Put([]byte(walletReadyKey), []byte(walletReadyKey))
×
277
}
278

279
// BackEnd returns the underlying ChainService's name as a string.
280
//
281
// This is a part of the WalletController interface.
282
func (b *BtcWallet) BackEnd() string {
32✔
283
        if b.chain != nil {
64✔
284
                return b.chain.BackEnd()
32✔
285
        }
32✔
286

287
        return ""
×
288
}
289

290
// InternalWallet returns a pointer to the internal base wallet which is the
291
// core of btcwallet.
292
func (b *BtcWallet) InternalWallet() *base.Wallet {
12✔
293
        return b.wallet
12✔
294
}
12✔
295

296
// Start initializes the underlying rpc connection, the wallet itself, and
297
// begins syncing to the current available blockchain state.
298
//
299
// This is a part of the WalletController interface.
300
func (b *BtcWallet) Start() error {
15✔
301
        // Is the wallet (according to its database) currently watch-only
15✔
302
        // already? If it is, we won't need to convert it later.
15✔
303
        walletIsWatchOnly := b.wallet.Manager.WatchOnly()
15✔
304

15✔
305
        // If the wallet is watch-only, but we don't expect it to be, then we
15✔
306
        // are in an unexpected state and cannot continue.
15✔
307
        if walletIsWatchOnly && !b.cfg.WatchOnly {
15✔
308
                return fmt.Errorf("wallet is watch-only but we expect it " +
×
309
                        "not to be; check if remote signing was disabled by " +
×
310
                        "accident")
×
311
        }
×
312

313
        // We'll start by unlocking the wallet and ensuring that the KeyScope:
314
        // (1017, 1) exists within the internal waddrmgr. We'll need this in
315
        // order to properly generate the keys required for signing various
316
        // contracts. If this is a watch-only wallet, we don't have any private
317
        // keys and therefore unlocking is not necessary.
318
        if !walletIsWatchOnly {
30✔
319
                if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
15✔
320
                        return err
×
321
                }
×
322

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

335
        // Because we might add new "default" key scopes over time, they are
336
        // created correctly for new wallets. Existing wallets don't
337
        // automatically add them, we need to do that manually now.
338
        for _, scope := range LndDefaultKeyScopes {
54✔
339
                _, err := b.wallet.Manager.FetchScopedKeyManager(scope)
39✔
340
                if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) {
39✔
341
                        // The default scope wasn't found, that probably means
×
342
                        // it was added recently and older wallets don't know it
×
343
                        // yet. Let's add it now.
×
344
                        addrSchema := waddrmgr.ScopeAddrMap[scope]
×
NEW
345
                        _, err := b.wallet.AddScopeManager(scope, addrSchema)
×
346
                        if err != nil {
×
347
                                return err
×
348
                        }
×
349
                }
350
        }
351

352
        scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
15✔
353
        if err != nil {
30✔
354
                // If the scope hasn't yet been created (it wouldn't been
15✔
355
                // loaded by default if it was), then we'll manually create the
15✔
356
                // scope for the first time ourselves.
15✔
357
                manager, err := b.wallet.AddScopeManager(
15✔
358
                        b.chainKeyScope, lightningAddrSchema,
15✔
359
                )
15✔
360
                if err != nil {
15✔
361
                        return err
×
362
                }
×
363

364
                scope = manager
15✔
365
        }
366

367
        watchOnly := !walletIsWatchOnly && b.cfg.WatchOnly &&
15✔
368
                b.cfg.MigrateWatchOnly
15✔
369

15✔
370
        // Now that the wallet is unlocked, we'll go ahead and make sure we
15✔
371
        // create accounts for all the key families we're going to use. This
15✔
372
        // will make it possible to list all the account/family xpubs in the
15✔
373
        // wallet list RPC.
15✔
374
        err = b.wallet.InitAccounts(scope, watchOnly, 255)
15✔
375
        if err != nil {
15✔
376
                return err
×
377
        }
×
378

379
        // Establish an RPC connection in addition to starting the goroutines
380
        // in the underlying wallet.
381
        if err := b.chain.Start(); err != nil {
15✔
382
                return err
×
383
        }
×
384

385
        // Start the underlying btcwallet core.
386
        b.wallet.Start()
15✔
387

15✔
388
        // Pass the rpc client into the wallet so it can sync up to the
15✔
389
        // current main chain.
15✔
390
        b.wallet.SynchronizeRPC(b.chain)
15✔
391

15✔
392
        return nil
15✔
393
}
394

395
// Stop signals the wallet for shutdown. Shutdown may entail closing
396
// any active sockets, database handles, stopping goroutines, etc.
397
//
398
// This is a part of the WalletController interface.
399
func (b *BtcWallet) Stop() error {
11✔
400
        b.wallet.Stop()
11✔
401

11✔
402
        b.wallet.WaitForShutdown()
11✔
403

11✔
404
        b.chain.Stop()
11✔
405

11✔
406
        return nil
11✔
407
}
11✔
408

409
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
410
// have at least confs confirmations. If confs is set to zero, then all unspent
411
// outputs, including those currently in the mempool will be included in the
412
// final sum. The account parameter serves as a filter to retrieve the balance
413
// for a specific account. When empty, the confirmed balance of all wallet
414
// accounts is returned.
415
//
416
// This is a part of the WalletController interface.
417
func (b *BtcWallet) ConfirmedBalance(confs int32,
418
        accountFilter string) (btcutil.Amount, error) {
176✔
419

176✔
420
        var balance btcutil.Amount
176✔
421

176✔
422
        witnessOutputs, err := b.ListUnspentWitness(
176✔
423
                confs, math.MaxInt32, accountFilter,
176✔
424
        )
176✔
425
        if err != nil {
176✔
426
                return 0, err
×
427
        }
×
428

429
        for _, witnessOutput := range witnessOutputs {
2,652✔
430
                balance += witnessOutput.Value
2,476✔
431
        }
2,476✔
432

433
        return balance, nil
176✔
434
}
435

436
// keyScopeForAccountAddr determines the appropriate key scope of an account
437
// based on its name/address type.
438
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
439
        addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
533✔
440

533✔
441
        // Map the requested address type to its key scope.
533✔
442
        var addrKeyScope waddrmgr.KeyScope
533✔
443
        switch addrType {
533✔
444
        case lnwallet.WitnessPubKey:
495✔
445
                addrKeyScope = waddrmgr.KeyScopeBIP0084
495✔
446
        case lnwallet.NestedWitnessPubKey:
15✔
447
                addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
15✔
448
        case lnwallet.TaprootPubkey:
29✔
449
                addrKeyScope = waddrmgr.KeyScopeBIP0086
29✔
450
        default:
×
451
                return waddrmgr.KeyScope{}, 0,
×
452
                        fmt.Errorf("unknown address type")
×
453
        }
454

455
        // The default account spans across multiple key scopes, so the
456
        // requested address type should already be valid for this account.
457
        if accountName == lnwallet.DefaultAccountName {
1,066✔
458
                return addrKeyScope, defaultAccount, nil
533✔
459
        }
533✔
460

461
        // Otherwise, look up the custom account and if it supports the given
462
        // key scope.
463
        accountNumber, err := b.wallet.AccountNumber(addrKeyScope, accountName)
3✔
464
        if err != nil {
3✔
465
                return waddrmgr.KeyScope{}, 0, err
×
466
        }
×
467

468
        return addrKeyScope, accountNumber, nil
3✔
469
}
470

471
// NewAddress returns the next external or internal address for the wallet
472
// dictated by the value of the `change` parameter. If change is true, then an
473
// internal address will be returned, otherwise an external address should be
474
// returned. The account parameter must be non-empty as it determines which
475
// account the address should be generated from.
476
//
477
// This is a part of the WalletController interface.
478
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool,
479
        accountName string) (btcutil.Address, error) {
509✔
480

509✔
481
        // Addresses cannot be derived from the catch-all imported accounts.
509✔
482
        if accountName == waddrmgr.ImportedAddrAccountName {
509✔
483
                return nil, errNoImportedAddrGen
×
484
        }
×
485

486
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
509✔
487
        if err != nil {
509✔
488
                return nil, err
×
489
        }
×
490

491
        if change {
540✔
492
                return b.wallet.NewChangeAddress(account, keyScope)
31✔
493
        }
31✔
494
        return b.wallet.NewAddress(account, keyScope)
481✔
495
}
496

497
// LastUnusedAddress returns the last *unused* address known by the wallet. An
498
// address is unused if it hasn't received any payments. This can be useful in
499
// UIs in order to continually show the "freshest" address without having to
500
// worry about "address inflation" caused by continual refreshing. Similar to
501
// NewAddress it can derive a specified address type, and also optionally a
502
// change address. The account parameter must be non-empty as it determines
503
// which account the address should be generated from.
504
func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType,
505
        accountName string) (btcutil.Address, error) {
27✔
506

27✔
507
        // Addresses cannot be derived from the catch-all imported accounts.
27✔
508
        if accountName == waddrmgr.ImportedAddrAccountName {
27✔
509
                return nil, errNoImportedAddrGen
×
510
        }
×
511

512
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
27✔
513
        if err != nil {
27✔
514
                return nil, err
×
515
        }
×
516

517
        return b.wallet.CurrentAddress(account, keyScope)
27✔
518
}
519

520
// IsOurAddress checks if the passed address belongs to this wallet
521
//
522
// This is a part of the WalletController interface.
523
func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool {
35✔
524
        result, err := b.wallet.HaveAddress(a)
35✔
525
        return result && (err == nil)
35✔
526
}
35✔
527

528
// AddressInfo returns the information about an address, if it's known to this
529
// wallet.
530
//
531
// NOTE: This is a part of the WalletController interface.
532
func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress,
533
        error) {
5✔
534

5✔
535
        return b.wallet.AddressInfo(a)
5✔
536
}
5✔
537

538
// ListAccounts retrieves all accounts belonging to the wallet by default. A
539
// name and key scope filter can be provided to filter through all of the wallet
540
// accounts and return only those matching.
541
//
542
// This is a part of the WalletController interface.
543
func (b *BtcWallet) ListAccounts(name string,
544
        keyScope *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
3✔
545

3✔
546
        var res []*waddrmgr.AccountProperties
3✔
547
        switch {
3✔
548
        // If both the name and key scope filters were provided, we'll return
549
        // the existing account matching those.
550
        case name != "" && keyScope != nil:
3✔
551
                account, err := b.wallet.AccountPropertiesByName(*keyScope, name)
3✔
552
                if err != nil {
3✔
553
                        return nil, err
×
554
                }
×
555
                res = append(res, account)
3✔
556

557
        // Only the name filter was provided.
558
        case name != "" && keyScope == nil:
3✔
559
                // If the name corresponds to the default or imported accounts,
3✔
560
                // we'll return them for all our supported key scopes.
3✔
561
                if name == lnwallet.DefaultAccountName ||
3✔
562
                        name == waddrmgr.ImportedAddrAccountName {
3✔
563

×
564
                        for _, defaultScope := range LndDefaultKeyScopes {
×
565
                                a, err := b.wallet.AccountPropertiesByName(
×
566
                                        defaultScope, name,
×
567
                                )
×
568
                                if err != nil {
×
569
                                        return nil, err
×
570
                                }
×
571
                                res = append(res, a)
×
572
                        }
573

574
                        break
×
575
                }
576

577
                // In theory, there should be only one custom account for the
578
                // given name. However, due to a lack of check, users could
579
                // create custom accounts with various key scopes. This
580
                // behaviour has been fixed but, we return all potential custom
581
                // accounts with the given name.
582
                for _, scope := range waddrmgr.DefaultKeyScopes {
6✔
583
                        a, err := b.wallet.AccountPropertiesByName(
3✔
584
                                scope, name,
3✔
585
                        )
3✔
586
                        switch {
3✔
587
                        case waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound):
3✔
588
                                continue
3✔
589

590
                        // In the specific case of a wallet initialized only by
591
                        // importing account xpubs (watch only wallets), it is
592
                        // possible that some keyscopes will be 'unknown' by the
593
                        // wallet (depending on the xpubs given to initialize
594
                        // it). If the keyscope is not found, just skip it.
595
                        case waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound):
3✔
596
                                continue
3✔
597

598
                        case err != nil:
×
599
                                return nil, err
×
600
                        }
601

602
                        res = append(res, a)
3✔
603
                }
604
                if len(res) == 0 {
6✔
605
                        return nil, newAccountNotFoundError(name)
3✔
606
                }
3✔
607

608
        // Only the key scope filter was provided, so we'll return all accounts
609
        // matching it.
610
        case name == "" && keyScope != nil:
×
611
                accounts, err := b.wallet.Accounts(*keyScope)
×
612
                if err != nil {
×
613
                        return nil, err
×
614
                }
×
615
                for _, account := range accounts.Accounts {
×
616
                        account := account
×
617
                        res = append(res, &account.AccountProperties)
×
618
                }
×
619

620
        // Neither of the filters were provided, so return all accounts for our
621
        // supported key scopes.
622
        case name == "" && keyScope == nil:
3✔
623
                for _, defaultScope := range LndDefaultKeyScopes {
6✔
624
                        accounts, err := b.wallet.Accounts(defaultScope)
3✔
625
                        if err != nil {
3✔
626
                                return nil, err
×
627
                        }
×
628
                        for _, account := range accounts.Accounts {
6✔
629
                                account := account
3✔
630
                                res = append(res, &account.AccountProperties)
3✔
631
                        }
3✔
632
                }
633

634
                accounts, err := b.wallet.Accounts(waddrmgr.KeyScope{
3✔
635
                        Purpose: keychain.BIP0043Purpose,
3✔
636
                        Coin:    b.cfg.CoinType,
3✔
637
                })
3✔
638
                if err != nil {
3✔
639
                        return nil, err
×
640
                }
×
641
                for _, account := range accounts.Accounts {
6✔
642
                        account := account
3✔
643
                        res = append(res, &account.AccountProperties)
3✔
644
                }
3✔
645
        }
646

647
        return res, nil
3✔
648
}
649

650
// newAccountNotFoundError returns an error indicating that the manager didn't
651
// find the specific account. This error is used to be compatible with the old
652
// 'LookupAccount' behaviour previously used.
653
func newAccountNotFoundError(name string) error {
3✔
654
        str := fmt.Sprintf("account name '%s' not found", name)
3✔
655

3✔
656
        return waddrmgr.ManagerError{
3✔
657
                ErrorCode:   waddrmgr.ErrAccountNotFound,
3✔
658
                Description: str,
3✔
659
        }
3✔
660
}
3✔
661

662
// RequiredReserve returns the minimum amount of satoshis that should be
663
// kept in the wallet in order to fee bump anchor channels if necessary.
664
// The value scales with the number of public anchor channels but is
665
// capped at a maximum.
666
func (b *BtcWallet) RequiredReserve(
667
        numAnchorChans uint32) btcutil.Amount {
83✔
668

83✔
669
        anchorChanReservedValue := lnwallet.AnchorChanReservedValue
83✔
670
        reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
83✔
671
        if reserved > lnwallet.MaxAnchorChanReservedValue {
83✔
672
                reserved = lnwallet.MaxAnchorChanReservedValue
×
673
        }
×
674

675
        return reserved
83✔
676
}
677

678
// ListAddresses retrieves all the addresses along with their balance. An
679
// account name filter can be provided to filter through all of the
680
// wallet accounts and return the addresses of only those matching.
681
//
682
// This is a part of the WalletController interface.
683
func (b *BtcWallet) ListAddresses(name string,
684
        showCustomAccounts bool) (lnwallet.AccountAddressMap, error) {
3✔
685

3✔
686
        accounts, err := b.ListAccounts(name, nil)
3✔
687
        if err != nil {
3✔
688
                return nil, err
×
689
        }
×
690

691
        addresses := make(lnwallet.AccountAddressMap)
3✔
692
        addressBalance := make(map[string]btcutil.Amount)
3✔
693

3✔
694
        // Retrieve all the unspent ouputs.
3✔
695
        outputs, err := b.wallet.ListUnspent(0, math.MaxInt32, "")
3✔
696
        if err != nil {
3✔
697
                return nil, err
×
698
        }
×
699

700
        // Calculate the total balance of each address.
701
        for _, output := range outputs {
6✔
702
                amount, err := btcutil.NewAmount(output.Amount)
3✔
703
                if err != nil {
3✔
704
                        return nil, err
×
705
                }
×
706

707
                addressBalance[output.Address] += amount
3✔
708
        }
709

710
        for _, accntDetails := range accounts {
6✔
711
                accntScope := accntDetails.KeyScope
3✔
712
                managedAddrs, err := b.wallet.AccountManagedAddresses(
3✔
713
                        accntDetails.KeyScope, accntDetails.AccountNumber,
3✔
714
                )
3✔
715
                if err != nil {
3✔
716
                        return nil, err
×
717
                }
×
718

719
                // Only consider those accounts which have addresses.
720
                if len(managedAddrs) == 0 {
6✔
721
                        continue
3✔
722
                }
723

724
                // All the lnd internal/custom keys for channels and other
725
                // functionality are derived from the same scope. Since they
726
                // aren't really used as addresses and will never have an
727
                // on-chain balance, we'll want to show the public key instead.
728
                isLndCustom := accntScope.Purpose == keychain.BIP0043Purpose
3✔
729
                addressProperties := make(
3✔
730
                        []lnwallet.AddressProperty, len(managedAddrs),
3✔
731
                )
3✔
732

3✔
733
                for idx, managedAddr := range managedAddrs {
6✔
734
                        addr := managedAddr.Address()
3✔
735
                        addressString := addr.String()
3✔
736

3✔
737
                        // Hex-encode the compressed public key for custom lnd
3✔
738
                        // keys, addresses don't make a lot of sense.
3✔
739
                        var (
3✔
740
                                pubKey         *btcec.PublicKey
3✔
741
                                derivationPath string
3✔
742
                        )
3✔
743
                        pka, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
3✔
744
                        if ok {
6✔
745
                                pubKey = pka.PubKey()
3✔
746

3✔
747
                                // There can be an error in two cases: Either
3✔
748
                                // the address isn't a managed pubkey address,
3✔
749
                                // which we already checked above, or the
3✔
750
                                // address is imported in which case we don't
3✔
751
                                // know the derivation path, and it will just be
3✔
752
                                // empty anyway.
3✔
753
                                _, _, derivationPath, _ =
3✔
754
                                        Bip32DerivationFromAddress(pka)
3✔
755
                        }
3✔
756
                        if pubKey != nil && isLndCustom {
6✔
757
                                addressString = hex.EncodeToString(
3✔
758
                                        pubKey.SerializeCompressed(),
3✔
759
                                )
3✔
760
                        }
3✔
761

762
                        addressProperties[idx] = lnwallet.AddressProperty{
3✔
763
                                Address:        addressString,
3✔
764
                                Internal:       managedAddr.Internal(),
3✔
765
                                Balance:        addressBalance[addressString],
3✔
766
                                PublicKey:      pubKey,
3✔
767
                                DerivationPath: derivationPath,
3✔
768
                        }
3✔
769
                }
770

771
                if accntScope.Purpose != keychain.BIP0043Purpose ||
3✔
772
                        showCustomAccounts {
6✔
773

3✔
774
                        addresses[accntDetails] = addressProperties
3✔
775
                }
3✔
776
        }
777

778
        return addresses, nil
3✔
779
}
780

781
// ImportAccount imports an account backed by an account extended public key.
782
// The master key fingerprint denotes the fingerprint of the root key
783
// corresponding to the account public key (also known as the key with
784
// derivation path m/). This may be required by some hardware wallets for proper
785
// identification and signing.
786
//
787
// The address type can usually be inferred from the key's version, but may be
788
// required for certain keys to map them into the proper scope.
789
//
790
// For custom accounts, we will first check if there is no account with the same
791
// name (even with a different key scope). No custom account should have various
792
// key scopes as it will result in non-deterministic behaviour.
793
//
794
// For BIP-0044 keys, an address type must be specified as we intend to not
795
// support importing BIP-0044 keys into the wallet using the legacy
796
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
797
// the standard BIP-0049 derivation scheme, while a witness address type will
798
// force the standard BIP-0084 derivation scheme.
799
//
800
// For BIP-0049 keys, an address type must also be specified to make a
801
// distinction between the standard BIP-0049 address schema (nested witness
802
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
803
// externally, witness pubkeys internally).
804
//
805
// This is a part of the WalletController interface.
806
func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
807
        masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
808
        dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
809
        []btcutil.Address, error) {
3✔
810

3✔
811
        // For custom accounts, we first check if there is no existing account
3✔
812
        // with the same name.
3✔
813
        if name != lnwallet.DefaultAccountName &&
3✔
814
                name != waddrmgr.ImportedAddrAccountName {
6✔
815

3✔
816
                _, err := b.ListAccounts(name, nil)
3✔
817
                if err == nil {
6✔
818
                        return nil, nil, nil,
3✔
819
                                fmt.Errorf("account '%s' already exists",
3✔
820
                                        name)
3✔
821
                }
3✔
822
                if !waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
3✔
823
                        return nil, nil, nil, err
×
824
                }
×
825
        }
826

827
        if !dryRun {
6✔
828
                accountProps, err := b.wallet.ImportAccount(
3✔
829
                        name, accountPubKey, masterKeyFingerprint, addrType,
3✔
830
                )
3✔
831
                if err != nil {
3✔
832
                        return nil, nil, nil, err
×
833
                }
×
834
                return accountProps, nil, nil, nil
3✔
835
        }
836

837
        // Derive addresses from both the external and internal branches of the
838
        // account. There's no risk of address inflation as this is only done
839
        // for dry runs.
840
        accountProps, extAddrs, intAddrs, err := b.wallet.ImportAccountDryRun(
×
841
                name, accountPubKey, masterKeyFingerprint, addrType,
×
842
                dryRunImportAccountNumAddrs,
×
843
        )
×
844
        if err != nil {
×
845
                return nil, nil, nil, err
×
846
        }
×
847

848
        externalAddrs := make([]btcutil.Address, len(extAddrs))
×
849
        for i := 0; i < len(extAddrs); i++ {
×
850
                externalAddrs[i] = extAddrs[i].Address()
×
851
        }
×
852

853
        internalAddrs := make([]btcutil.Address, len(intAddrs))
×
854
        for i := 0; i < len(intAddrs); i++ {
×
855
                internalAddrs[i] = intAddrs[i].Address()
×
856
        }
×
857

858
        return accountProps, externalAddrs, internalAddrs, nil
×
859
}
860

861
// ImportPublicKey imports a single derived public key into the wallet. The
862
// address type can usually be inferred from the key's version, but in the case
863
// of legacy versions (xpub, tpub), an address type must be specified as we
864
// intend to not support importing BIP-44 keys into the wallet using the legacy
865
// pay-to-pubkey-hash (P2PKH) scheme.
866
//
867
// This is a part of the WalletController interface.
868
func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey,
869
        addrType waddrmgr.AddressType) error {
3✔
870

3✔
871
        return b.wallet.ImportPublicKey(pubKey, addrType)
3✔
872
}
3✔
873

874
// ImportTaprootScript imports a user-provided taproot script into the address
875
// manager. The imported script will act as a pay-to-taproot address.
876
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
877
        tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
5✔
878

5✔
879
        // We want to be able to import script addresses into a watch-only
5✔
880
        // wallet, which is only possible if we don't encrypt the script with
5✔
881
        // the private key encryption key. By specifying the script as being
5✔
882
        // "not secret", we can also decrypt the script in a watch-only wallet.
5✔
883
        const isSecretScript = false
5✔
884

5✔
885
        // Currently, only v1 (Taproot) scripts are supported. We don't even
5✔
886
        // know what a v2 witness version would look like at this point.
5✔
887
        const witnessVersionTaproot byte = 1
5✔
888

5✔
889
        return b.wallet.ImportTaprootScript(
5✔
890
                scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
5✔
891
        )
5✔
892
}
5✔
893

894
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
895
// the specified outputs. In the case the wallet has insufficient funds, or the
896
// outputs are non-standard, a non-nil error will be returned.
897
//
898
// NOTE: This method requires the global coin selection lock to be held.
899
//
900
// This is a part of the WalletController interface.
901
func (b *BtcWallet) SendOutputs(inputs fn.Set[wire.OutPoint],
902
        outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
903
        minConfs int32, label string,
904
        strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) {
137✔
905

137✔
906
        // Convert our fee rate from sat/kw to sat/kb since it's required by
137✔
907
        // SendOutputs.
137✔
908
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
137✔
909

137✔
910
        // Sanity check outputs.
137✔
911
        if len(outputs) < 1 {
145✔
912
                return nil, lnwallet.ErrNoOutputs
8✔
913
        }
8✔
914

915
        // Sanity check minConfs.
916
        if minConfs < 0 {
129✔
917
                return nil, lnwallet.ErrInvalidMinconf
×
918
        }
×
919

920
        // Use selected UTXOs if specified, otherwise default selection.
921
        if len(inputs) != 0 {
132✔
922
                return b.wallet.SendOutputsWithInput(
3✔
923
                        outputs, nil, defaultAccount, minConfs, feeSatPerKB,
3✔
924
                        strategy, label, inputs.ToSlice(),
3✔
925
                )
3✔
926
        }
3✔
927

928
        return b.wallet.SendOutputs(
129✔
929
                outputs, nil, defaultAccount, minConfs, feeSatPerKB,
129✔
930
                strategy, label,
129✔
931
        )
129✔
932
}
933

934
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
935
// outputs. The transaction is not broadcasted to the network, but a new change
936
// address might be created in the wallet database. In the case the wallet has
937
// insufficient funds, or the outputs are non-standard, an error should be
938
// returned. This method also takes the target fee expressed in sat/kw that
939
// should be used when crafting the transaction.
940
//
941
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
942
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
943
//
944
// NOTE: This method requires the global coin selection lock to be held.
945
//
946
// This is a part of the WalletController interface.
947
func (b *BtcWallet) CreateSimpleTx(inputs fn.Set[wire.OutPoint],
948
        outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, minConfs int32,
949
        strategy base.CoinSelectionStrategy, dryRun bool) (
950
        *txauthor.AuthoredTx, error) {
59✔
951

59✔
952
        // The fee rate is passed in using units of sat/kw, so we'll convert
59✔
953
        // this to sat/KB as the CreateSimpleTx method requires this unit.
59✔
954
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
59✔
955

59✔
956
        // Sanity check outputs.
59✔
957
        if len(outputs) < 1 {
67✔
958
                return nil, lnwallet.ErrNoOutputs
8✔
959
        }
8✔
960

961
        // Sanity check minConfs.
962
        if minConfs < 0 {
51✔
963
                return nil, lnwallet.ErrInvalidMinconf
×
964
        }
×
965

966
        for _, output := range outputs {
270✔
967
                // When checking an output for things like dusty-ness, we'll
219✔
968
                // use the default mempool relay fee rather than the target
219✔
969
                // effective fee rate to ensure accuracy. Otherwise, we may
219✔
970
                // mistakenly mark small-ish, but not quite dust output as
219✔
971
                // dust.
219✔
972
                err := txrules.CheckOutput(
219✔
973
                        output, txrules.DefaultRelayFeePerKb,
219✔
974
                )
219✔
975
                if err != nil {
227✔
976
                        return nil, err
8✔
977
                }
8✔
978
        }
979

980
        // Add the optional inputs to the transaction.
981
        optFunc := wallet.WithCustomSelectUtxos(inputs.ToSlice())
43✔
982

43✔
983
        return b.wallet.CreateSimpleTx(
43✔
984
                nil, defaultAccount, outputs, minConfs, feeSatPerKB,
43✔
985
                strategy, dryRun, []wallet.TxCreateOption{optFunc}...,
43✔
986
        )
43✔
987
}
988

989
// LeaseOutput locks an output to the given ID, preventing it from being
990
// available for any future coin selection attempts. The absolute time of the
991
// lock's expiration is returned. The expiration of the lock can be extended by
992
// successive invocations of this call. Outputs can be unlocked before their
993
// expiration through `ReleaseOutput`.
994
//
995
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
996
// output has already been locked to a different ID, then
997
// wtxmgr.ErrOutputAlreadyLocked is returned.
998
//
999
// NOTE: This method requires the global coin selection lock to be held.
1000
func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
1001
        duration time.Duration) (time.Time, error) {
143✔
1002

143✔
1003
        // Make sure we don't attempt to double lock an output that's been
143✔
1004
        // locked by the in-memory implementation.
143✔
1005
        if b.wallet.LockedOutpoint(op) {
143✔
1006
                return time.Time{}, wtxmgr.ErrOutputAlreadyLocked
×
1007
        }
×
1008

1009
        lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
143✔
1010
        if err != nil {
143✔
1011
                return time.Time{}, err
×
1012
        }
×
1013

1014
        return lockedUntil, nil
143✔
1015
}
1016

1017
// ListLeasedOutputs returns a list of all currently locked outputs.
1018
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
1019
        error) {
3✔
1020

3✔
1021
        return b.wallet.ListLeasedOutputs()
3✔
1022
}
3✔
1023

1024
// ReleaseOutput unlocks an output, allowing it to be available for coin
1025
// selection if it remains unspent. The ID should match the one used to
1026
// originally lock the output.
1027
//
1028
// NOTE: This method requires the global coin selection lock to be held.
1029
func (b *BtcWallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error {
107✔
1030
        return b.wallet.ReleaseOutput(id, op)
107✔
1031
}
107✔
1032

1033
// ListUnspentWitness returns all unspent outputs which are version 0 witness
1034
// programs. The 'minConfs' and 'maxConfs' parameters indicate the minimum
1035
// and maximum number of confirmations an output needs in order to be returned
1036
// by this method. Passing -1 as 'minConfs' indicates that even unconfirmed
1037
// outputs should be returned. Using MaxInt32 as 'maxConfs' implies returning
1038
// all outputs with at least 'minConfs'. The account parameter serves as a
1039
// filter to retrieve the unspent outputs for a specific account.  When empty,
1040
// the unspent outputs of all wallet accounts are returned.
1041
//
1042
// NOTE: This method requires the global coin selection lock to be held.
1043
//
1044
// This is a part of the WalletController interface.
1045
func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32,
1046
        accountFilter string) ([]*lnwallet.Utxo, error) {
267✔
1047

267✔
1048
        // First, grab all the unfiltered currently unspent outputs.
267✔
1049
        unspentOutputs, err := b.wallet.ListUnspent(
267✔
1050
                minConfs, maxConfs, accountFilter,
267✔
1051
        )
267✔
1052
        if err != nil {
267✔
1053
                return nil, err
×
1054
        }
×
1055

1056
        // Next, we'll run through all the regular outputs, only saving those
1057
        // which are p2wkh outputs or a p2wsh output nested within a p2sh output.
1058
        witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
267✔
1059
        for _, output := range unspentOutputs {
3,881✔
1060
                pkScript, err := hex.DecodeString(output.ScriptPubKey)
3,614✔
1061
                if err != nil {
3,614✔
1062
                        return nil, err
×
1063
                }
×
1064

1065
                addressType := lnwallet.UnknownAddressType
3,614✔
1066
                if txscript.IsPayToWitnessPubKeyHash(pkScript) {
6,455✔
1067
                        addressType = lnwallet.WitnessPubKey
2,841✔
1068
                } else if txscript.IsPayToScriptHash(pkScript) {
3,684✔
1069
                        // TODO(roasbeef): This assumes all p2sh outputs returned by the
67✔
1070
                        // wallet are nested p2pkh. We can't check the redeem script because
67✔
1071
                        // the btcwallet service does not include it.
67✔
1072
                        addressType = lnwallet.NestedWitnessPubKey
67✔
1073
                } else if txscript.IsPayToTaproot(pkScript) {
1,491✔
1074
                        addressType = lnwallet.TaprootPubkey
712✔
1075
                }
712✔
1076

1077
                if addressType == lnwallet.WitnessPubKey ||
3,614✔
1078
                        addressType == lnwallet.NestedWitnessPubKey ||
3,614✔
1079
                        addressType == lnwallet.TaprootPubkey {
7,228✔
1080

3,614✔
1081
                        txid, err := chainhash.NewHashFromStr(output.TxID)
3,614✔
1082
                        if err != nil {
3,614✔
1083
                                return nil, err
×
1084
                        }
×
1085

1086
                        // We'll ensure we properly convert the amount given in
1087
                        // BTC to satoshis.
1088
                        amt, err := btcutil.NewAmount(output.Amount)
3,614✔
1089
                        if err != nil {
3,614✔
1090
                                return nil, err
×
1091
                        }
×
1092

1093
                        utxo := &lnwallet.Utxo{
3,614✔
1094
                                AddressType: addressType,
3,614✔
1095
                                Value:       amt,
3,614✔
1096
                                PkScript:    pkScript,
3,614✔
1097
                                OutPoint: wire.OutPoint{
3,614✔
1098
                                        Hash:  *txid,
3,614✔
1099
                                        Index: output.Vout,
3,614✔
1100
                                },
3,614✔
1101
                                Confirmations: output.Confirmations,
3,614✔
1102
                        }
3,614✔
1103
                        witnessOutputs = append(witnessOutputs, utxo)
3,614✔
1104
                }
1105

1106
        }
1107

1108
        return witnessOutputs, nil
267✔
1109
}
1110

1111
// mapRpcclientError maps an error from the `btcwallet/chain` package to
1112
// defined error in this package.
1113
//
1114
// NOTE: we are mapping the errors returned from `sendrawtransaction` RPC or
1115
// the reject reason from `testmempoolaccept` RPC.
1116
func mapRpcclientError(err error) error {
58✔
1117
        // If we failed to publish the transaction, check whether we got an
58✔
1118
        // error of known type.
58✔
1119
        switch {
58✔
1120
        // If the wallet reports a double spend, convert it to our internal
1121
        // ErrDoubleSpend and return.
1122
        case errors.Is(err, chain.ErrMempoolConflict),
1123
                errors.Is(err, chain.ErrMissingInputs),
1124
                errors.Is(err, chain.ErrTxAlreadyKnown),
1125
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
5✔
1126

5✔
1127
                return lnwallet.ErrDoubleSpend
5✔
1128

1129
        // If the wallet reports that fee requirements for accepting the tx
1130
        // into mempool are not met, convert it to our internal ErrMempoolFee
1131
        // and return.
1132
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
×
1133
                return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee, err.Error())
×
1134
        }
1135

1136
        return err
55✔
1137
}
1138

1139
// PublishTransaction performs cursory validation (dust checks, etc), then
1140
// finally broadcasts the passed transaction to the Bitcoin network. If
1141
// publishing the transaction fails, an error describing the reason is returned
1142
// and mapped to the wallet's internal error types. If the transaction is
1143
// already published to the network (either in the mempool or chain) no error
1144
// will be returned.
1145
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
58✔
1146
        // For neutrino backend there's no mempool, so we return early by
58✔
1147
        // publishing the transaction.
58✔
1148
        if b.chain.BackEnd() == "neutrino" {
72✔
1149
                err := b.wallet.PublishTransaction(tx, label)
14✔
1150

14✔
1151
                return mapRpcclientError(err)
14✔
1152
        }
14✔
1153

1154
        // For non-neutrino nodes, we will first check whether the transaction
1155
        // can be accepted by the mempool.
1156
        // Use a max feerate of 0 means the default value will be used when
1157
        // testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
1158
        // or 10,000 sat/vb.
1159
        results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
44✔
1160
        if err != nil {
44✔
1161
                // If the chain backend doesn't support the mempool acceptance
×
1162
                // test RPC, we'll just attempt to publish the transaction.
×
1163
                if errors.Is(err, rpcclient.ErrBackendVersion) {
×
1164
                        log.Warnf("TestMempoolAccept not supported by "+
×
1165
                                "backend, consider upgrading %s to a newer "+
×
1166
                                "version", b.chain.BackEnd())
×
1167

×
1168
                        err := b.wallet.PublishTransaction(tx, label)
×
1169

×
1170
                        return mapRpcclientError(err)
×
1171
                }
×
1172

1173
                return err
×
1174
        }
1175

1176
        // Sanity check that the expected single result is returned.
1177
        if len(results) != 1 {
44✔
1178
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
1179
                        "instead got %v", len(results))
×
1180
        }
×
1181

1182
        result := results[0]
44✔
1183
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
44✔
1184

44✔
1185
        // Once mempool check passed, we can publish the transaction.
44✔
1186
        if result.Allowed {
73✔
1187
                err = b.wallet.PublishTransaction(tx, label)
29✔
1188

29✔
1189
                return mapRpcclientError(err)
29✔
1190
        }
29✔
1191

1192
        // If the check failed, there's no need to publish it. We'll handle the
1193
        // error and return.
1194
        log.Warnf("Transaction %v not accepted by mempool: %v",
17✔
1195
                tx.TxHash(), result.RejectReason)
17✔
1196

17✔
1197
        // We need to use the string to create an error type and map it to a
17✔
1198
        // btcwallet error.
17✔
1199
        err = b.chain.MapRPCErr(errors.New(result.RejectReason))
17✔
1200

17✔
1201
        //nolint:ll
17✔
1202
        // These two errors are ignored inside `PublishTransaction`:
17✔
1203
        // https://github.com/btcsuite/btcwallet/blob/master/wallet/wallet.go#L3763
17✔
1204
        // To keep our current behavior, we need to ignore the same errors
17✔
1205
        // returned from TestMempoolAccept.
17✔
1206
        //
17✔
1207
        // TODO(yy): since `LightningWallet.PublishTransaction` always publish
17✔
1208
        // the same tx twice, we'd always get ErrTxAlreadyInMempool. We should
17✔
1209
        // instead create a new rebroadcaster that monitors the mempool, and
17✔
1210
        // only rebroadcast when the tx is evicted. This way we don't need to
17✔
1211
        // broadcast twice, and can instead return these errors here.
17✔
1212
        switch {
17✔
1213
        // NOTE: In addition to ignoring these errors, we need to call
1214
        // `PublishTransaction` again because we need to mark the label in the
1215
        // wallet. We can remove this exception once we have the above TODO
1216
        // fixed.
1217
        case errors.Is(err, chain.ErrTxAlreadyInMempool),
1218
                errors.Is(err, chain.ErrTxAlreadyKnown),
1219
                errors.Is(err, chain.ErrTxAlreadyConfirmed):
8✔
1220

8✔
1221
                err := b.wallet.PublishTransaction(tx, label)
8✔
1222
                return mapRpcclientError(err)
8✔
1223
        }
1224

1225
        return mapRpcclientError(err)
11✔
1226
}
1227

1228
// LabelTransaction adds a label to a transaction. If the tx already
1229
// has a label, this call will fail unless the overwrite parameter
1230
// is set. Labels must not be empty, and they are limited to 500 chars.
1231
//
1232
// Note: it is part of the WalletController interface.
1233
func (b *BtcWallet) LabelTransaction(hash chainhash.Hash, label string,
1234
        overwrite bool) error {
3✔
1235

3✔
1236
        return b.wallet.LabelTransaction(hash, label, overwrite)
3✔
1237
}
3✔
1238

1239
// extractBalanceDelta extracts the net balance delta from the PoV of the
1240
// wallet given a TransactionSummary.
1241
func extractBalanceDelta(
1242
        txSummary base.TransactionSummary,
1243
        tx *wire.MsgTx,
1244
) (btcutil.Amount, error) {
2,192✔
1245
        // For each input we debit the wallet's outflow for this transaction,
2,192✔
1246
        // and for each output we credit the wallet's inflow for this
2,192✔
1247
        // transaction.
2,192✔
1248
        var balanceDelta btcutil.Amount
2,192✔
1249
        for _, input := range txSummary.MyInputs {
3,345✔
1250
                balanceDelta -= input.PreviousAmount
1,153✔
1251
        }
1,153✔
1252
        for _, output := range txSummary.MyOutputs {
4,364✔
1253
                balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
2,172✔
1254
        }
2,172✔
1255

1256
        return balanceDelta, nil
2,192✔
1257
}
1258

1259
// getPreviousOutpoints is a helper function which gets the previous
1260
// outpoints of a transaction.
1261
func getPreviousOutpoints(wireTx *wire.MsgTx,
1262
        myInputs []base.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
2,197✔
1263

2,197✔
1264
        // isOurOutput is a map containing the output indices
2,197✔
1265
        // controlled by the wallet.
2,197✔
1266
        // Note: We make use of the information in `myInputs` provided
2,197✔
1267
        // by the `wallet.TransactionSummary` structure that holds
2,197✔
1268
        // information only if the input/previous_output is controlled by the wallet.
2,197✔
1269
        isOurOutput := make(map[uint32]bool, len(myInputs))
2,197✔
1270
        for _, myInput := range myInputs {
3,358✔
1271
                isOurOutput[myInput.Index] = true
1,161✔
1272
        }
1,161✔
1273

1274
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
2,197✔
1275
        for idx, txIn := range wireTx.TxIn {
6,362✔
1276
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
4,165✔
1277
                        OutPoint:    txIn.PreviousOutPoint.String(),
4,165✔
1278
                        IsOurOutput: isOurOutput[uint32(idx)],
4,165✔
1279
                }
4,165✔
1280
        }
4,165✔
1281

1282
        return previousOutpoints
2,197✔
1283
}
1284

1285
// GetTransactionDetails returns details of a transaction given its
1286
// transaction hash.
1287
func (b *BtcWallet) GetTransactionDetails(
1288
        txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
7✔
1289

7✔
1290
        // Grab the best block the wallet knows of, we'll use this to calculate
7✔
1291
        // # of confirmations shortly below.
7✔
1292
        bestBlock := b.wallet.Manager.SyncedTo()
7✔
1293
        currentHeight := bestBlock.Height
7✔
1294
        tx, err := b.wallet.GetTransaction(*txHash)
7✔
1295
        if err != nil {
7✔
1296
                return nil, err
×
1297
        }
×
1298

1299
        // For both confirmed and unconfirmed transactions, create a
1300
        // TransactionDetail which re-packages the data returned by the base
1301
        // wallet.
1302
        if tx.Confirmations > 0 {
14✔
1303
                txDetails, err := minedTransactionsToDetails(
7✔
1304
                        currentHeight,
7✔
1305
                        base.Block{
7✔
1306
                                Transactions: []base.TransactionSummary{
7✔
1307
                                        tx.Summary,
7✔
1308
                                },
7✔
1309
                                Hash:      tx.BlockHash,
7✔
1310
                                Height:    tx.Height,
7✔
1311
                                Timestamp: tx.Summary.Timestamp},
7✔
1312
                        b.netParams,
7✔
1313
                )
7✔
1314
                if err != nil {
7✔
1315
                        return nil, err
×
1316
                }
×
1317

1318
                return txDetails[0], nil
7✔
1319
        }
1320

1321
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
3✔
1322
}
1323

1324
// minedTransactionsToDetails is a helper function which converts a summary
1325
// information about mined transactions to a TransactionDetail.
1326
func minedTransactionsToDetails(
1327
        currentHeight int32,
1328
        block base.Block,
1329
        chainParams *chaincfg.Params,
1330
) ([]*lnwallet.TransactionDetail, error) {
433✔
1331

433✔
1332
        details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
433✔
1333
        for _, tx := range block.Transactions {
2,590✔
1334
                wireTx := &wire.MsgTx{}
2,157✔
1335
                txReader := bytes.NewReader(tx.Transaction)
2,157✔
1336

2,157✔
1337
                if err := wireTx.Deserialize(txReader); err != nil {
2,157✔
1338
                        return nil, err
×
1339
                }
×
1340

1341
                // isOurAddress is a map containing the output indices
1342
                // controlled by the wallet.
1343
                // Note: We make use of the information in `MyOutputs` provided
1344
                // by the `wallet.TransactionSummary` structure that holds
1345
                // information only if the output is controlled by the wallet.
1346
                isOurAddress := make(map[int]bool, len(tx.MyOutputs))
2,157✔
1347
                for _, o := range tx.MyOutputs {
4,290✔
1348
                        isOurAddress[int(o.Index)] = true
2,133✔
1349
                }
2,133✔
1350

1351
                var outputDetails []lnwallet.OutputDetail
2,157✔
1352
                for i, txOut := range wireTx.TxOut {
6,404✔
1353
                        var addresses []btcutil.Address
4,247✔
1354
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
4,247✔
1355
                                txOut.PkScript, chainParams,
4,247✔
1356
                        )
4,247✔
1357
                        if err == nil {
8,494✔
1358
                                // Add supported addresses.
4,247✔
1359
                                addresses = outAddresses
4,247✔
1360
                        }
4,247✔
1361

1362
                        outputDetails = append(outputDetails, lnwallet.OutputDetail{
4,247✔
1363
                                OutputType:   sc,
4,247✔
1364
                                Addresses:    addresses,
4,247✔
1365
                                PkScript:     txOut.PkScript,
4,247✔
1366
                                OutputIndex:  i,
4,247✔
1367
                                Value:        btcutil.Amount(txOut.Value),
4,247✔
1368
                                IsOurAddress: isOurAddress[i],
4,247✔
1369
                        })
4,247✔
1370
                }
1371

1372
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
2,157✔
1373

2,157✔
1374
                txDetail := &lnwallet.TransactionDetail{
2,157✔
1375
                        Hash:              *tx.Hash,
2,157✔
1376
                        NumConfirmations:  currentHeight - block.Height + 1,
2,157✔
1377
                        BlockHash:         block.Hash,
2,157✔
1378
                        BlockHeight:       block.Height,
2,157✔
1379
                        Timestamp:         block.Timestamp,
2,157✔
1380
                        TotalFees:         int64(tx.Fee),
2,157✔
1381
                        OutputDetails:     outputDetails,
2,157✔
1382
                        RawTx:             tx.Transaction,
2,157✔
1383
                        Label:             tx.Label,
2,157✔
1384
                        PreviousOutpoints: previousOutpoints,
2,157✔
1385
                }
2,157✔
1386

2,157✔
1387
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
2,157✔
1388
                if err != nil {
2,157✔
1389
                        return nil, err
×
1390
                }
×
1391
                txDetail.Value = balanceDelta
2,157✔
1392

2,157✔
1393
                details = append(details, txDetail)
2,157✔
1394
        }
1395

1396
        return details, nil
433✔
1397
}
1398

1399
// unminedTransactionsToDetail is a helper function which converts a summary
1400
// for an unconfirmed transaction to a transaction detail.
1401
func unminedTransactionsToDetail(
1402
        summary base.TransactionSummary,
1403
        chainParams *chaincfg.Params,
1404
) (*lnwallet.TransactionDetail, error) {
38✔
1405

38✔
1406
        wireTx := &wire.MsgTx{}
38✔
1407
        txReader := bytes.NewReader(summary.Transaction)
38✔
1408

38✔
1409
        if err := wireTx.Deserialize(txReader); err != nil {
38✔
1410
                return nil, err
×
1411
        }
×
1412

1413
        // isOurAddress is a map containing the output indices controlled by
1414
        // the wallet.
1415
        // Note: We make use of the information in `MyOutputs` provided
1416
        // by the `wallet.TransactionSummary` structure that holds information
1417
        // only if the output is controlled by the wallet.
1418
        isOurAddress := make(map[int]bool, len(summary.MyOutputs))
38✔
1419
        for _, o := range summary.MyOutputs {
80✔
1420
                isOurAddress[int(o.Index)] = true
42✔
1421
        }
42✔
1422

1423
        var outputDetails []lnwallet.OutputDetail
38✔
1424
        for i, txOut := range wireTx.TxOut {
111✔
1425
                var addresses []btcutil.Address
73✔
1426
                sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
73✔
1427
                        txOut.PkScript, chainParams,
73✔
1428
                )
73✔
1429
                if err == nil {
146✔
1430
                        // Add supported addresses.
73✔
1431
                        addresses = outAddresses
73✔
1432
                }
73✔
1433

1434
                outputDetails = append(outputDetails, lnwallet.OutputDetail{
73✔
1435
                        OutputType:   sc,
73✔
1436
                        Addresses:    addresses,
73✔
1437
                        PkScript:     txOut.PkScript,
73✔
1438
                        OutputIndex:  i,
73✔
1439
                        Value:        btcutil.Amount(txOut.Value),
73✔
1440
                        IsOurAddress: isOurAddress[i],
73✔
1441
                })
73✔
1442
        }
1443

1444
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
38✔
1445

38✔
1446
        txDetail := &lnwallet.TransactionDetail{
38✔
1447
                Hash:              *summary.Hash,
38✔
1448
                TotalFees:         int64(summary.Fee),
38✔
1449
                Timestamp:         summary.Timestamp,
38✔
1450
                OutputDetails:     outputDetails,
38✔
1451
                RawTx:             summary.Transaction,
38✔
1452
                Label:             summary.Label,
38✔
1453
                PreviousOutpoints: previousOutpoints,
38✔
1454
        }
38✔
1455

38✔
1456
        balanceDelta, err := extractBalanceDelta(summary, wireTx)
38✔
1457
        if err != nil {
38✔
1458
                return nil, err
×
1459
        }
×
1460
        txDetail.Value = balanceDelta
38✔
1461

38✔
1462
        return txDetail, nil
38✔
1463
}
1464

1465
// ListTransactionDetails returns a list of all transactions which are relevant
1466
// to the wallet over [startHeight;endHeight]. If start height is greater than
1467
// end height, the transactions will be retrieved in reverse order. To include
1468
// unconfirmed transactions, endHeight should be set to the special value -1.
1469
// This will return transactions from the tip of the chain until the start
1470
// height (inclusive) and unconfirmed transactions. The account parameter serves
1471
// as a filter to retrieve the transactions relevant to a specific account. When
1472
// empty, transactions of all wallet accounts are returned.
1473
//
1474
// This is a part of the WalletController interface.
1475
func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
1476
        accountFilter string, indexOffset uint32,
1477
        maxTransactions uint32) ([]*lnwallet.TransactionDetail, uint64, uint64,
1478
        error) {
111✔
1479

111✔
1480
        // Grab the best block the wallet knows of, we'll use this to calculate
111✔
1481
        // # of confirmations shortly below.
111✔
1482
        bestBlock := b.wallet.Manager.SyncedTo()
111✔
1483
        currentHeight := bestBlock.Height
111✔
1484

111✔
1485
        // We'll attempt to find all transactions from start to end height.
111✔
1486
        start := base.NewBlockIdentifierFromHeight(startHeight)
111✔
1487
        stop := base.NewBlockIdentifierFromHeight(endHeight)
111✔
1488
        txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
111✔
1489
        if err != nil {
111✔
1490
                return nil, 0, 0, err
×
1491
        }
×
1492

1493
        txDetails := make([]*lnwallet.TransactionDetail, 0,
111✔
1494
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
111✔
1495

111✔
1496
        // For both confirmed and unconfirmed transactions, create a
111✔
1497
        // TransactionDetail which re-packages the data returned by the base
111✔
1498
        // wallet.
111✔
1499
        for _, blockPackage := range txns.MinedTransactions {
536✔
1500
                details, err := minedTransactionsToDetails(
425✔
1501
                        currentHeight, blockPackage, b.netParams,
425✔
1502
                )
425✔
1503
                if err != nil {
425✔
1504
                        return nil, 0, 0, err
×
1505
                }
×
1506

1507
                txDetails = append(txDetails, details...)
425✔
1508
        }
1509
        for _, tx := range txns.UnminedTransactions {
136✔
1510
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
25✔
1511
                if err != nil {
25✔
1512
                        return nil, 0, 0, err
×
1513
                }
×
1514

1515
                txDetails = append(txDetails, detail)
25✔
1516
        }
1517

1518
        // Return empty transaction list, if offset is more than all
1519
        // transactions.
1520
        if int(indexOffset) >= len(txDetails) {
123✔
1521
                txDetails = []*lnwallet.TransactionDetail{}
12✔
1522

12✔
1523
                return txDetails, 0, 0, nil
12✔
1524
        }
12✔
1525

1526
        end := indexOffset + maxTransactions
99✔
1527

99✔
1528
        // If maxTransactions is set to 0, then we'll return all transactions
99✔
1529
        // starting from the offset.
99✔
1530
        if maxTransactions == 0 {
110✔
1531
                end = uint32(len(txDetails))
11✔
1532
                txDetails = txDetails[indexOffset:end]
11✔
1533

11✔
1534
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
11✔
1535
        }
11✔
1536

1537
        if end > uint32(len(txDetails)) {
168✔
1538
                end = uint32(len(txDetails))
80✔
1539
        }
80✔
1540

1541
        txDetails = txDetails[indexOffset:end]
88✔
1542

88✔
1543
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
88✔
1544
}
1545

1546
// txSubscriptionClient encapsulates the transaction notification client from
1547
// the base wallet. Notifications received from the client will be proxied over
1548
// two distinct channels.
1549
type txSubscriptionClient struct {
1550
        txClient base.TransactionNotificationsClient
1551

1552
        confirmed   chan *lnwallet.TransactionDetail
1553
        unconfirmed chan *lnwallet.TransactionDetail
1554

1555
        w *base.Wallet
1556

1557
        wg   sync.WaitGroup
1558
        quit chan struct{}
1559
}
1560

1561
// ConfirmedTransactions returns a channel which will be sent on as new
1562
// relevant transactions are confirmed.
1563
//
1564
// This is part of the TransactionSubscription interface.
1565
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
12✔
1566
        return t.confirmed
12✔
1567
}
12✔
1568

1569
// UnconfirmedTransactions returns a channel which will be sent on as
1570
// new relevant transactions are seen within the network.
1571
//
1572
// This is part of the TransactionSubscription interface.
1573
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
13✔
1574
        return t.unconfirmed
13✔
1575
}
13✔
1576

1577
// Cancel finalizes the subscription, cleaning up any resources allocated.
1578
//
1579
// This is part of the TransactionSubscription interface.
1580
func (t *txSubscriptionClient) Cancel() {
4✔
1581
        close(t.quit)
4✔
1582
        t.wg.Wait()
4✔
1583

4✔
1584
        t.txClient.Done()
4✔
1585
}
4✔
1586

1587
// notificationProxier proxies the notifications received by the underlying
1588
// wallet's notification client to a higher-level TransactionSubscription
1589
// client.
1590
func (t *txSubscriptionClient) notificationProxier() {
4✔
1591
        defer t.wg.Done()
4✔
1592

4✔
1593
out:
4✔
1594
        for {
25✔
1595
                select {
21✔
1596
                case txNtfn := <-t.txClient.C:
17✔
1597
                        // TODO(roasbeef): handle detached blocks
17✔
1598
                        currentHeight := t.w.Manager.SyncedTo().Height
17✔
1599

17✔
1600
                        // Launch a goroutine to re-package and send
17✔
1601
                        // notifications for any newly confirmed transactions.
17✔
1602
                        //nolint:ll
17✔
1603
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1604
                                for _, block := range txNtfn.AttachedBlocks {
21✔
1605
                                        details, err := minedTransactionsToDetails(
4✔
1606
                                                currentHeight, block,
4✔
1607
                                                t.w.ChainParams(),
4✔
1608
                                        )
4✔
1609
                                        if err != nil {
4✔
1610
                                                continue
×
1611
                                        }
1612

1613
                                        for _, d := range details {
16✔
1614
                                                select {
12✔
1615
                                                case t.confirmed <- d:
12✔
1616
                                                case <-t.quit:
×
1617
                                                        return
×
1618
                                                }
1619
                                        }
1620
                                }
1621
                        }(txNtfn)
1622

1623
                        // Launch a goroutine to re-package and send
1624
                        // notifications for any newly unconfirmed transactions.
1625
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1626
                                for _, tx := range txNtfn.UnminedTransactions {
30✔
1627
                                        detail, err := unminedTransactionsToDetail(
13✔
1628
                                                tx, t.w.ChainParams(),
13✔
1629
                                        )
13✔
1630
                                        if err != nil {
13✔
1631
                                                continue
×
1632
                                        }
1633

1634
                                        select {
13✔
1635
                                        case t.unconfirmed <- detail:
13✔
1636
                                        case <-t.quit:
×
1637
                                                return
×
1638
                                        }
1639
                                }
1640
                        }(txNtfn)
1641
                case <-t.quit:
4✔
1642
                        break out
4✔
1643
                }
1644
        }
1645
}
1646

1647
// SubscribeTransactions returns a TransactionSubscription client which
1648
// is capable of receiving async notifications as new transactions
1649
// related to the wallet are seen within the network, or found in
1650
// blocks.
1651
//
1652
// This is a part of the WalletController interface.
1653
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
4✔
1654
        walletClient := b.wallet.NtfnServer.TransactionNotifications()
4✔
1655

4✔
1656
        txClient := &txSubscriptionClient{
4✔
1657
                txClient:    walletClient,
4✔
1658
                confirmed:   make(chan *lnwallet.TransactionDetail),
4✔
1659
                unconfirmed: make(chan *lnwallet.TransactionDetail),
4✔
1660
                w:           b.wallet,
4✔
1661
                quit:        make(chan struct{}),
4✔
1662
        }
4✔
1663
        txClient.wg.Add(1)
4✔
1664
        go txClient.notificationProxier()
4✔
1665

4✔
1666
        return txClient, nil
4✔
1667
}
4✔
1668

1669
// IsSynced returns a boolean indicating if from the PoV of the wallet, it has
1670
// fully synced to the current best block in the main chain.
1671
//
1672
// This is a part of the WalletController interface.
1673
func (b *BtcWallet) IsSynced() (bool, int64, error) {
187✔
1674
        // Grab the best chain state the wallet is currently aware of.
187✔
1675
        syncState := b.wallet.Manager.SyncedTo()
187✔
1676

187✔
1677
        // We'll also extract the current best wallet timestamp so the caller
187✔
1678
        // can get an idea of where we are in the sync timeline.
187✔
1679
        bestTimestamp := syncState.Timestamp.Unix()
187✔
1680

187✔
1681
        // Next, query the chain backend to grab the info about the tip of the
187✔
1682
        // main chain.
187✔
1683
        bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
187✔
1684
        if err != nil {
187✔
1685
                return false, 0, err
×
1686
        }
×
1687

1688
        // Make sure the backing chain has been considered synced first.
1689
        if !b.wallet.ChainSynced() {
190✔
1690
                bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
3✔
1691
                if err != nil {
3✔
1692
                        return false, 0, err
×
1693
                }
×
1694
                bestTimestamp = bestHeader.Timestamp.Unix()
3✔
1695
                return false, bestTimestamp, nil
3✔
1696
        }
1697

1698
        // If the wallet hasn't yet fully synced to the node's best chain tip,
1699
        // then we're not yet fully synced.
1700
        if syncState.Height < bestHeight {
231✔
1701
                return false, bestTimestamp, nil
44✔
1702
        }
44✔
1703

1704
        // If the wallet is on par with the current best chain tip, then we
1705
        // still may not yet be synced as the chain backend may still be
1706
        // catching up to the main chain. So we'll grab the block header in
1707
        // order to make a guess based on the current time stamp.
1708
        blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
145✔
1709
        if err != nil {
145✔
1710
                return false, 0, err
×
1711
        }
×
1712

1713
        // If the timestamp on the best header is more than 2 hours in the
1714
        // past, then we're not yet synced.
1715
        minus24Hours := time.Now().Add(-2 * time.Hour)
145✔
1716
        if blockHeader.Timestamp.Before(minus24Hours) {
146✔
1717
                return false, bestTimestamp, nil
1✔
1718
        }
1✔
1719

1720
        return true, bestTimestamp, nil
145✔
1721
}
1722

1723
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
1724
// in recovery mode. It also returns a float64, ranging from 0 to 1,
1725
// representing the recovery progress made so far.
1726
//
1727
// This is a part of the WalletController interface.
1728
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
15✔
1729
        isRecoveryMode := true
15✔
1730
        progress := float64(0)
15✔
1731

15✔
1732
        // A zero value in RecoveryWindow indicates there is no trigger of
15✔
1733
        // recovery mode.
15✔
1734
        if b.cfg.RecoveryWindow == 0 {
22✔
1735
                isRecoveryMode = false
7✔
1736
                return isRecoveryMode, progress, nil
7✔
1737
        }
7✔
1738

1739
        // Query the wallet's birthday block from db.
1740
        birthdayBlock, err := b.wallet.BirthdayBlock()
11✔
1741
        if err != nil {
11✔
1742
                // The wallet won't start until the backend is synced, thus the birthday
×
1743
                // block won't be set and this particular error will be returned. We'll
×
1744
                // catch this error and return a progress of 0 instead.
×
1745
                if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
×
1746
                        return isRecoveryMode, progress, nil
×
1747
                }
×
1748

1749
                return isRecoveryMode, progress, err
×
1750
        }
1751

1752
        // Grab the best chain state the wallet is currently aware of.
1753
        syncState := b.wallet.Manager.SyncedTo()
11✔
1754

11✔
1755
        // Next, query the chain backend to grab the info about the tip of the
11✔
1756
        // main chain.
11✔
1757
        //
11✔
1758
        // NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
11✔
1759
        // The process purposefully doesn't update the best height. It might create
11✔
1760
        // a small difference between the height queried here and the height used
11✔
1761
        // in the recovery process, ie, the bestHeight used here might be greater,
11✔
1762
        // showing the recovery being unfinished while it's actually done. However,
11✔
1763
        // during a wallet rescan after the recovery, the wallet's synced height
11✔
1764
        // will catch up and this won't be an issue.
11✔
1765
        _, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
11✔
1766
        if err != nil {
11✔
1767
                return isRecoveryMode, progress, err
×
1768
        }
×
1769

1770
        // The birthday block height might be greater than the current synced height
1771
        // in a newly restored wallet, and might be greater than the chain tip if a
1772
        // rollback happens. In that case, we will return zero progress here.
1773
        if syncState.Height < birthdayBlock.Height ||
11✔
1774
                bestHeight < birthdayBlock.Height {
11✔
1775

×
1776
                return isRecoveryMode, progress, nil
×
1777
        }
×
1778

1779
        // progress is the ratio of the [number of blocks processed] over the [total
1780
        // number of blocks] needed in a recovery mode, ranging from 0 to 1, in
1781
        // which,
1782
        // - total number of blocks is the current chain's best height minus the
1783
        //   wallet's birthday height plus 1.
1784
        // - number of blocks processed is the wallet's synced height minus its
1785
        //   birthday height plus 1.
1786
        // - If the wallet is born very recently, the bestHeight can be equal to
1787
        //   the birthdayBlock.Height, and it will recovery instantly.
1788
        progress = float64(syncState.Height-birthdayBlock.Height+1) /
11✔
1789
                float64(bestHeight-birthdayBlock.Height+1)
11✔
1790

11✔
1791
        return isRecoveryMode, progress, nil
11✔
1792
}
1793

1794
// FetchTx attempts to fetch a transaction in the wallet's database identified
1795
// by the passed transaction hash. If the transaction can't be found, then a
1796
// nil pointer is returned.
1797
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
3✔
1798
        tx, err := b.wallet.GetTransaction(txHash)
3✔
1799
        if err != nil {
6✔
1800
                return nil, err
3✔
1801
        }
3✔
1802

1803
        return tx.Summary.Tx, nil
3✔
1804
}
1805

1806
// RemoveDescendants attempts to remove any transaction from the wallet's tx
1807
// store (that may be unconfirmed) that spends outputs created by the passed
1808
// transaction. This remove propagates recursively down the chain of descendent
1809
// transactions.
1810
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
3✔
1811
        return b.wallet.RemoveDescendants(tx)
3✔
1812
}
3✔
1813

1814
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
1815
// the mempool acceptance of a transaction.
1816
func (b *BtcWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
8✔
1817
        // Use a max feerate of 0 means the default value will be used when
8✔
1818
        // testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
8✔
1819
        // or 10,000 sat/vb.
8✔
1820
        results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
8✔
1821
        if err != nil {
11✔
1822
                return err
3✔
1823
        }
3✔
1824

1825
        // Sanity check that the expected single result is returned.
1826
        if len(results) != 1 {
6✔
1827
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
1✔
1828
                        "instead got %v", len(results))
1✔
1829
        }
1✔
1830

1831
        result := results[0]
4✔
1832
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
4✔
1833

4✔
1834
        // Mempool check failed, we now map the reject reason to a proper RPC
4✔
1835
        // error and return it.
4✔
1836
        if !result.Allowed {
7✔
1837
                err := b.chain.MapRPCErr(errors.New(result.RejectReason))
3✔
1838

3✔
1839
                return fmt.Errorf("mempool rejection: %w", err)
3✔
1840
        }
3✔
1841

1842
        return nil
3✔
1843
}
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