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

lightningnetwork / lnd / 17771623214

16 Sep 2025 03:55PM UTC coverage: 57.218% (-9.4%) from 66.645%
17771623214

Pull #9489

github

web-flow
Merge 7d73e5cff into 9a6d34a02
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

329 of 564 new or added lines in 12 files covered. (58.33%)

28538 existing lines in 459 files now uncovered.

99745 of 174325 relevant lines covered (57.22%)

1.78 hits per line

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

72.38
/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
        base "github.com/btcsuite/btcwallet/wallet"
23
        "github.com/btcsuite/btcwallet/wallet/txauthor"
24
        "github.com/btcsuite/btcwallet/wallet/txrules"
25
        "github.com/btcsuite/btcwallet/walletdb"
26
        "github.com/btcsuite/btcwallet/wtxmgr"
27
        "github.com/lightningnetwork/lnd/blockcache"
28
        "github.com/lightningnetwork/lnd/fn/v2"
29
        "github.com/lightningnetwork/lnd/input"
30
        "github.com/lightningnetwork/lnd/keychain"
31
        "github.com/lightningnetwork/lnd/kvdb"
32
        "github.com/lightningnetwork/lnd/lnutils"
33
        "github.com/lightningnetwork/lnd/lnwallet"
34
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
35
)
36

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

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

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

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

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

58
var (
59
        // lightningAddrSchema is the scope addr schema for all keys that we
60
        // derive. We'll treat them all as p2wkh addresses, as atm we must
61
        // specify a particular type.
62
        lightningAddrSchema = waddrmgr.ScopeAddrSchema{
63
                ExternalAddrType: waddrmgr.WitnessPubKey,
64
                InternalAddrType: waddrmgr.WitnessPubKey,
65
        }
66

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

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

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

89
        chain chain.Interface
90

91
        db walletdb.DB
92

93
        cfg *Config
94

95
        netParams *chaincfg.Params
96

97
        chainKeyScope waddrmgr.KeyScope
98

99
        blockCache *blockcache.BlockCache
100

101
        *input.MusigSessionManager
102
}
103

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

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

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

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

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

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

3✔
173
        finalWallet.MusigSessionManager = input.NewMusigSessionManager(
3✔
174
                finalWallet.fetchPrivKey,
3✔
175
        )
3✔
176

3✔
177
        return finalWallet, nil
3✔
178
}
179

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

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

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

3✔
196
        return func(cfg *loaderCfg) {
6✔
197
                cfg.dbDirPath = dbDirPath
3✔
198
                cfg.noFreelistSync = noFreelistSync
3✔
199
                cfg.dbTimeout = dbTimeout
3✔
200
                cfg.useLocalDB = true
3✔
201
        }
3✔
202
}
203

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

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

3✔
215
        cfg := &loaderCfg{}
3✔
216

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

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

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

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

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

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

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

265
        return exists, err
×
266
}
267

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

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

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

286
        return ""
×
287
}
288

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

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

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

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

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

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

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

365
                scope = manager
3✔
366
        }
367

368
        // If the wallet is not watch-only atm, and the user wants to migrate it
369
        // to watch-only, we will set `convertToWatchOnly` to true so the wallet
370
        // accounts are created and converted.
371
        convertToWatchOnly := !walletIsWatchOnly && b.cfg.WatchOnly &&
3✔
372
                b.cfg.MigrateWatchOnly
3✔
373

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

383
        // Establish an RPC connection in addition to starting the goroutines
384
        // in the underlying wallet.
385
        if err := b.chain.Start(); err != nil {
3✔
386
                return err
×
387
        }
×
388

389
        // Start the underlying btcwallet core.
390
        b.wallet.Start()
3✔
391

3✔
392
        // Pass the rpc client into the wallet so it can sync up to the
3✔
393
        // current main chain.
3✔
394
        b.wallet.SynchronizeRPC(b.chain)
3✔
395

3✔
396
        return nil
3✔
397
}
398

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

3✔
406
        b.wallet.WaitForShutdown()
3✔
407

3✔
408
        b.chain.Stop()
3✔
409

3✔
410
        return nil
3✔
411
}
3✔
412

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

3✔
424
        var balance btcutil.Amount
3✔
425

3✔
426
        witnessOutputs, err := b.ListUnspentWitness(
3✔
427
                confs, math.MaxInt32, accountFilter,
3✔
428
        )
3✔
429
        if err != nil {
3✔
430
                return 0, err
×
431
        }
×
432

433
        for _, witnessOutput := range witnessOutputs {
6✔
434
                balance += witnessOutput.Value
3✔
435
        }
3✔
436

437
        return balance, nil
3✔
438
}
439

440
// keyScopeForAccountAddr determines the appropriate key scope of an account
441
// based on its name/address type.
442
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
443
        addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
3✔
444

3✔
445
        // Map the requested address type to its key scope.
3✔
446
        var addrKeyScope waddrmgr.KeyScope
3✔
447
        switch addrType {
3✔
448
        case lnwallet.WitnessPubKey:
3✔
449
                addrKeyScope = waddrmgr.KeyScopeBIP0084
3✔
450
        case lnwallet.NestedWitnessPubKey:
3✔
451
                addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
3✔
452
        case lnwallet.TaprootPubkey:
3✔
453
                addrKeyScope = waddrmgr.KeyScopeBIP0086
3✔
454
        default:
×
455
                return waddrmgr.KeyScope{}, 0,
×
456
                        fmt.Errorf("unknown address type")
×
457
        }
458

459
        // The default account spans across multiple key scopes, so the
460
        // requested address type should already be valid for this account.
461
        if accountName == lnwallet.DefaultAccountName {
6✔
462
                return addrKeyScope, defaultAccount, nil
3✔
463
        }
3✔
464

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

472
        return addrKeyScope, accountNumber, nil
3✔
473
}
474

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

3✔
485
        // Addresses cannot be derived from the catch-all imported accounts.
3✔
486
        if accountName == waddrmgr.ImportedAddrAccountName {
3✔
487
                return nil, errNoImportedAddrGen
×
488
        }
×
489

490
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
3✔
491
        if err != nil {
3✔
492
                return nil, err
×
493
        }
×
494

495
        if change {
6✔
496
                return b.wallet.NewChangeAddress(account, keyScope)
3✔
497
        }
3✔
498
        return b.wallet.NewAddress(account, keyScope)
3✔
499
}
500

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

3✔
511
        // Addresses cannot be derived from the catch-all imported accounts.
3✔
512
        if accountName == waddrmgr.ImportedAddrAccountName {
3✔
513
                return nil, errNoImportedAddrGen
×
514
        }
×
515

516
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
3✔
517
        if err != nil {
3✔
518
                return nil, err
×
519
        }
×
520

521
        return b.wallet.CurrentAddress(account, keyScope)
3✔
522
}
523

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

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

3✔
539
        return b.wallet.AddressInfo(a)
3✔
540
}
3✔
541

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

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

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

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

578
                        break
×
579
                }
580

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

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

602
                        case err != nil:
×
603
                                return nil, err
×
604
                        }
605

606
                        res = append(res, a)
3✔
607
                }
608
                if len(res) == 0 {
6✔
609
                        return nil, newAccountNotFoundError(name)
3✔
610
                }
3✔
611

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

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

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

651
        return res, nil
3✔
652
}
653

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

3✔
660
        return waddrmgr.ManagerError{
3✔
661
                ErrorCode:   waddrmgr.ErrAccountNotFound,
3✔
662
                Description: str,
3✔
663
        }
3✔
664
}
3✔
665

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

3✔
673
        anchorChanReservedValue := lnwallet.AnchorChanReservedValue
3✔
674
        reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
3✔
675
        if reserved > lnwallet.MaxAnchorChanReservedValue {
3✔
676
                reserved = lnwallet.MaxAnchorChanReservedValue
×
677
        }
×
678

679
        return reserved
3✔
680
}
681

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

3✔
690
        accounts, err := b.ListAccounts(name, nil)
3✔
691
        if err != nil {
3✔
692
                return nil, err
×
693
        }
×
694

695
        addresses := make(lnwallet.AccountAddressMap)
3✔
696
        addressBalance := make(map[string]btcutil.Amount)
3✔
697

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

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

711
                addressBalance[output.Address] += amount
3✔
712
        }
713

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

723
                // Only consider those accounts which have addresses.
724
                if len(managedAddrs) == 0 {
6✔
725
                        continue
3✔
726
                }
727

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

3✔
737
                for idx, managedAddr := range managedAddrs {
6✔
738
                        addr := managedAddr.Address()
3✔
739
                        addressString := addr.String()
3✔
740

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

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

766
                        addressProperties[idx] = lnwallet.AddressProperty{
3✔
767
                                Address:        addressString,
3✔
768
                                Internal:       managedAddr.Internal(),
3✔
769
                                Balance:        addressBalance[addressString],
3✔
770
                                PublicKey:      pubKey,
3✔
771
                                DerivationPath: derivationPath,
3✔
772
                        }
3✔
773
                }
774

775
                if accntScope.Purpose != keychain.BIP0043Purpose ||
3✔
776
                        showCustomAccounts {
6✔
777

3✔
778
                        addresses[accntDetails] = addressProperties
3✔
779
                }
3✔
780
        }
781

782
        return addresses, nil
3✔
783
}
784

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

3✔
815
        // For custom accounts, we first check if there is no existing account
3✔
816
        // with the same name.
3✔
817
        if name != lnwallet.DefaultAccountName &&
3✔
818
                name != waddrmgr.ImportedAddrAccountName {
6✔
819

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

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

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

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

857
        internalAddrs := make([]btcutil.Address, len(intAddrs))
×
858
        for i := 0; i < len(intAddrs); i++ {
×
859
                internalAddrs[i] = intAddrs[i].Address()
×
860
        }
×
861

862
        return accountProps, externalAddrs, internalAddrs, nil
×
863
}
864

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

3✔
875
        return b.wallet.ImportPublicKey(pubKey, addrType)
3✔
876
}
3✔
877

878
// ImportTaprootScript imports a user-provided taproot script into the address
879
// manager. The imported script will act as a pay-to-taproot address.
880
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
881
        tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
3✔
882

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

3✔
889
        // Currently, only v1 (Taproot) scripts are supported. We don't even
3✔
890
        // know what a v2 witness version would look like at this point.
3✔
891
        const witnessVersionTaproot byte = 1
3✔
892

3✔
893
        return b.wallet.ImportTaprootScript(
3✔
894
                scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
3✔
895
        )
3✔
896
}
3✔
897

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

3✔
910
        // Convert our fee rate from sat/kw to sat/kb since it's required by
3✔
911
        // SendOutputs.
3✔
912
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
3✔
913

3✔
914
        // Sanity check outputs.
3✔
915
        if len(outputs) < 1 {
3✔
UNCOV
916
                return nil, lnwallet.ErrNoOutputs
×
UNCOV
917
        }
×
918

919
        // Sanity check minConfs.
920
        if minConfs < 0 {
3✔
921
                return nil, lnwallet.ErrInvalidMinconf
×
922
        }
×
923

924
        // Use selected UTXOs if specified, otherwise default selection.
925
        if len(inputs) != 0 {
6✔
926
                return b.wallet.SendOutputsWithInput(
3✔
927
                        outputs, nil, defaultAccount, minConfs, feeSatPerKB,
3✔
928
                        strategy, label, inputs.ToSlice(),
3✔
929
                )
3✔
930
        }
3✔
931

932
        return b.wallet.SendOutputs(
3✔
933
                outputs, nil, defaultAccount, minConfs, feeSatPerKB,
3✔
934
                strategy, label,
3✔
935
        )
3✔
936
}
937

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

3✔
956
        // The fee rate is passed in using units of sat/kw, so we'll convert
3✔
957
        // this to sat/KB as the CreateSimpleTx method requires this unit.
3✔
958
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
3✔
959

3✔
960
        // Sanity check outputs.
3✔
961
        if len(outputs) < 1 {
3✔
UNCOV
962
                return nil, lnwallet.ErrNoOutputs
×
UNCOV
963
        }
×
964

965
        // Sanity check minConfs.
966
        if minConfs < 0 {
3✔
967
                return nil, lnwallet.ErrInvalidMinconf
×
968
        }
×
969

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

984
        // Add the optional inputs to the transaction.
985
        optFunc := base.WithCustomSelectUtxos(inputs.ToSlice())
3✔
986

3✔
987
        return b.wallet.CreateSimpleTx(
3✔
988
                nil, defaultAccount, outputs, minConfs, feeSatPerKB,
3✔
989
                strategy, dryRun, []base.TxCreateOption{optFunc}...,
3✔
990
        )
3✔
991
}
992

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

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

1013
        lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
3✔
1014
        if err != nil {
3✔
1015
                return time.Time{}, err
×
1016
        }
×
1017

1018
        return lockedUntil, nil
3✔
1019
}
1020

1021
// ListLeasedOutputs returns a list of all currently locked outputs.
1022
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
1023
        error) {
3✔
1024

3✔
1025
        return b.wallet.ListLeasedOutputs()
3✔
1026
}
3✔
1027

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

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

3✔
1052
        // First, grab all the unfiltered currently unspent outputs.
3✔
1053
        unspentOutputs, err := b.wallet.ListUnspent(
3✔
1054
                minConfs, maxConfs, accountFilter,
3✔
1055
        )
3✔
1056
        if err != nil {
3✔
1057
                return nil, err
×
1058
        }
×
1059

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

1069
                addressType := lnwallet.UnknownAddressType
3✔
1070
                if txscript.IsPayToWitnessPubKeyHash(pkScript) {
6✔
1071
                        addressType = lnwallet.WitnessPubKey
3✔
1072
                } else if txscript.IsPayToScriptHash(pkScript) {
9✔
1073
                        // TODO(roasbeef): This assumes all p2sh outputs returned by the
3✔
1074
                        // wallet are nested p2pkh. We can't check the redeem script because
3✔
1075
                        // the btcwallet service does not include it.
3✔
1076
                        addressType = lnwallet.NestedWitnessPubKey
3✔
1077
                } else if txscript.IsPayToTaproot(pkScript) {
9✔
1078
                        addressType = lnwallet.TaprootPubkey
3✔
1079
                }
3✔
1080

1081
                if addressType == lnwallet.WitnessPubKey ||
3✔
1082
                        addressType == lnwallet.NestedWitnessPubKey ||
3✔
1083
                        addressType == lnwallet.TaprootPubkey {
6✔
1084

3✔
1085
                        txid, err := chainhash.NewHashFromStr(output.TxID)
3✔
1086
                        if err != nil {
3✔
1087
                                return nil, err
×
1088
                        }
×
1089

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

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

1110
        }
1111

1112
        return witnessOutputs, nil
3✔
1113
}
1114

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

2✔
1131
                return lnwallet.ErrDoubleSpend
2✔
1132

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

1140
        return err
3✔
1141
}
1142

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

1✔
1155
                return mapRpcclientError(err)
1✔
1156
        }
1✔
1157

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

×
1172
                        err := b.wallet.PublishTransaction(tx, label)
×
1173

×
1174
                        return mapRpcclientError(err)
×
1175
                }
×
1176

1177
                return err
×
1178
        }
1179

1180
        // Sanity check that the expected single result is returned.
1181
        if len(results) != 1 {
2✔
1182
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
1183
                        "instead got %v", len(results))
×
1184
        }
×
1185

1186
        result := results[0]
2✔
1187
        log.Debugf("TestMempoolAccept result: %s",
2✔
1188
                lnutils.SpewLogClosure(result))
2✔
1189

2✔
1190
        // Once mempool check passed, we can publish the transaction.
2✔
1191
        if result.Allowed {
4✔
1192
                err = b.wallet.PublishTransaction(tx, label)
2✔
1193

2✔
1194
                return mapRpcclientError(err)
2✔
1195
        }
2✔
1196

1197
        // If the check failed, there's no need to publish it. We'll handle the
1198
        // error and return.
1199
        log.Warnf("Transaction %v not accepted by mempool: %v",
2✔
1200
                tx.TxHash(), result.RejectReason)
2✔
1201

2✔
1202
        // We need to use the string to create an error type and map it to a
2✔
1203
        // btcwallet error.
2✔
1204
        err = b.chain.MapRPCErr(errors.New(result.RejectReason))
2✔
1205

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

2✔
1226
                err := b.wallet.PublishTransaction(tx, label)
2✔
1227
                return mapRpcclientError(err)
2✔
1228
        }
1229

1230
        return mapRpcclientError(err)
2✔
1231
}
1232

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

3✔
1241
        return b.wallet.LabelTransaction(hash, label, overwrite)
3✔
1242
}
3✔
1243

1244
// extractBalanceDelta extracts the net balance delta from the PoV of the
1245
// wallet given a TransactionSummary.
1246
func extractBalanceDelta(
1247
        txSummary base.TransactionSummary,
1248
        tx *wire.MsgTx,
1249
) (btcutil.Amount, error) {
3✔
1250
        // For each input we debit the wallet's outflow for this transaction,
3✔
1251
        // and for each output we credit the wallet's inflow for this
3✔
1252
        // transaction.
3✔
1253
        var balanceDelta btcutil.Amount
3✔
1254
        for _, input := range txSummary.MyInputs {
6✔
1255
                balanceDelta -= input.PreviousAmount
3✔
1256
        }
3✔
1257
        for _, output := range txSummary.MyOutputs {
6✔
1258
                balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
3✔
1259
        }
3✔
1260

1261
        return balanceDelta, nil
3✔
1262
}
1263

1264
// getPreviousOutpoints is a helper function which gets the previous
1265
// outpoints of a transaction.
1266
func getPreviousOutpoints(wireTx *wire.MsgTx,
1267
        myInputs []base.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
3✔
1268

3✔
1269
        // isOurOutput is a map containing the output indices
3✔
1270
        // controlled by the wallet.
3✔
1271
        // Note: We make use of the information in `myInputs` provided
3✔
1272
        // by the `wallet.TransactionSummary` structure that holds
3✔
1273
        // information only if the input/previous_output is controlled by the wallet.
3✔
1274
        isOurOutput := make(map[uint32]bool, len(myInputs))
3✔
1275
        for _, myInput := range myInputs {
6✔
1276
                isOurOutput[myInput.Index] = true
3✔
1277
        }
3✔
1278

1279
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
3✔
1280
        for idx, txIn := range wireTx.TxIn {
6✔
1281
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
3✔
1282
                        OutPoint:    txIn.PreviousOutPoint.String(),
3✔
1283
                        IsOurOutput: isOurOutput[uint32(idx)],
3✔
1284
                }
3✔
1285
        }
3✔
1286

1287
        return previousOutpoints
3✔
1288
}
1289

1290
// GetTransactionDetails returns details of a transaction given its
1291
// transaction hash.
1292
func (b *BtcWallet) GetTransactionDetails(
1293
        txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
3✔
1294

3✔
1295
        // Grab the best block the wallet knows of, we'll use this to calculate
3✔
1296
        // # of confirmations shortly below.
3✔
1297
        bestBlock := b.wallet.SyncedTo()
3✔
1298
        currentHeight := bestBlock.Height
3✔
1299
        tx, err := b.wallet.GetTransaction(*txHash)
3✔
1300
        if err != nil {
3✔
1301
                return nil, err
×
1302
        }
×
1303

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

1323
                return txDetails[0], nil
3✔
1324
        }
1325

1326
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
3✔
1327
}
1328

1329
// minedTransactionsToDetails is a helper function which converts a summary
1330
// information about mined transactions to a TransactionDetail.
1331
func minedTransactionsToDetails(
1332
        currentHeight int32,
1333
        block base.Block,
1334
        chainParams *chaincfg.Params,
1335
) ([]*lnwallet.TransactionDetail, error) {
3✔
1336

3✔
1337
        details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
3✔
1338
        for _, tx := range block.Transactions {
6✔
1339
                wireTx := &wire.MsgTx{}
3✔
1340
                txReader := bytes.NewReader(tx.Transaction)
3✔
1341

3✔
1342
                if err := wireTx.Deserialize(txReader); err != nil {
3✔
1343
                        return nil, err
×
1344
                }
×
1345

1346
                // isOurAddress is a map containing the output indices
1347
                // controlled by the wallet.
1348
                // Note: We make use of the information in `MyOutputs` provided
1349
                // by the `wallet.TransactionSummary` structure that holds
1350
                // information only if the output is controlled by the wallet.
1351
                isOurAddress := make(map[int]bool, len(tx.MyOutputs))
3✔
1352
                for _, o := range tx.MyOutputs {
6✔
1353
                        isOurAddress[int(o.Index)] = true
3✔
1354
                }
3✔
1355

1356
                var outputDetails []lnwallet.OutputDetail
3✔
1357
                for i, txOut := range wireTx.TxOut {
6✔
1358
                        var addresses []btcutil.Address
3✔
1359
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
3✔
1360
                                txOut.PkScript, chainParams,
3✔
1361
                        )
3✔
1362
                        if err == nil {
6✔
1363
                                // Add supported addresses.
3✔
1364
                                addresses = outAddresses
3✔
1365
                        }
3✔
1366

1367
                        outputDetails = append(outputDetails, lnwallet.OutputDetail{
3✔
1368
                                OutputType:   sc,
3✔
1369
                                Addresses:    addresses,
3✔
1370
                                PkScript:     txOut.PkScript,
3✔
1371
                                OutputIndex:  i,
3✔
1372
                                Value:        btcutil.Amount(txOut.Value),
3✔
1373
                                IsOurAddress: isOurAddress[i],
3✔
1374
                        })
3✔
1375
                }
1376

1377
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
3✔
1378

3✔
1379
                txDetail := &lnwallet.TransactionDetail{
3✔
1380
                        Hash:              *tx.Hash,
3✔
1381
                        NumConfirmations:  currentHeight - block.Height + 1,
3✔
1382
                        BlockHash:         block.Hash,
3✔
1383
                        BlockHeight:       block.Height,
3✔
1384
                        Timestamp:         block.Timestamp,
3✔
1385
                        TotalFees:         int64(tx.Fee),
3✔
1386
                        OutputDetails:     outputDetails,
3✔
1387
                        RawTx:             tx.Transaction,
3✔
1388
                        Label:             tx.Label,
3✔
1389
                        PreviousOutpoints: previousOutpoints,
3✔
1390
                }
3✔
1391

3✔
1392
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
3✔
1393
                if err != nil {
3✔
1394
                        return nil, err
×
1395
                }
×
1396
                txDetail.Value = balanceDelta
3✔
1397

3✔
1398
                details = append(details, txDetail)
3✔
1399
        }
1400

1401
        return details, nil
3✔
1402
}
1403

1404
// unminedTransactionsToDetail is a helper function which converts a summary
1405
// for an unconfirmed transaction to a transaction detail.
1406
func unminedTransactionsToDetail(
1407
        summary base.TransactionSummary,
1408
        chainParams *chaincfg.Params,
1409
) (*lnwallet.TransactionDetail, error) {
3✔
1410

3✔
1411
        wireTx := &wire.MsgTx{}
3✔
1412
        txReader := bytes.NewReader(summary.Transaction)
3✔
1413

3✔
1414
        if err := wireTx.Deserialize(txReader); err != nil {
3✔
1415
                return nil, err
×
1416
        }
×
1417

1418
        // isOurAddress is a map containing the output indices controlled by
1419
        // the wallet.
1420
        // Note: We make use of the information in `MyOutputs` provided
1421
        // by the `wallet.TransactionSummary` structure that holds information
1422
        // only if the output is controlled by the wallet.
1423
        isOurAddress := make(map[int]bool, len(summary.MyOutputs))
3✔
1424
        for _, o := range summary.MyOutputs {
6✔
1425
                isOurAddress[int(o.Index)] = true
3✔
1426
        }
3✔
1427

1428
        var outputDetails []lnwallet.OutputDetail
3✔
1429
        for i, txOut := range wireTx.TxOut {
6✔
1430
                var addresses []btcutil.Address
3✔
1431
                sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
3✔
1432
                        txOut.PkScript, chainParams,
3✔
1433
                )
3✔
1434
                if err == nil {
6✔
1435
                        // Add supported addresses.
3✔
1436
                        addresses = outAddresses
3✔
1437
                }
3✔
1438

1439
                outputDetails = append(outputDetails, lnwallet.OutputDetail{
3✔
1440
                        OutputType:   sc,
3✔
1441
                        Addresses:    addresses,
3✔
1442
                        PkScript:     txOut.PkScript,
3✔
1443
                        OutputIndex:  i,
3✔
1444
                        Value:        btcutil.Amount(txOut.Value),
3✔
1445
                        IsOurAddress: isOurAddress[i],
3✔
1446
                })
3✔
1447
        }
1448

1449
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
3✔
1450

3✔
1451
        txDetail := &lnwallet.TransactionDetail{
3✔
1452
                Hash:              *summary.Hash,
3✔
1453
                TotalFees:         int64(summary.Fee),
3✔
1454
                Timestamp:         summary.Timestamp,
3✔
1455
                OutputDetails:     outputDetails,
3✔
1456
                RawTx:             summary.Transaction,
3✔
1457
                Label:             summary.Label,
3✔
1458
                PreviousOutpoints: previousOutpoints,
3✔
1459
        }
3✔
1460

3✔
1461
        balanceDelta, err := extractBalanceDelta(summary, wireTx)
3✔
1462
        if err != nil {
3✔
1463
                return nil, err
×
1464
        }
×
1465
        txDetail.Value = balanceDelta
3✔
1466

3✔
1467
        return txDetail, nil
3✔
1468
}
1469

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

3✔
1485
        // Grab the best block the wallet knows of, we'll use this to calculate
3✔
1486
        // # of confirmations shortly below.
3✔
1487
        bestBlock := b.wallet.SyncedTo()
3✔
1488
        currentHeight := bestBlock.Height
3✔
1489

3✔
1490
        // We'll attempt to find all transactions from start to end height.
3✔
1491
        start := base.NewBlockIdentifierFromHeight(startHeight)
3✔
1492
        stop := base.NewBlockIdentifierFromHeight(endHeight)
3✔
1493
        txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
3✔
1494
        if err != nil {
3✔
1495
                return nil, 0, 0, err
×
1496
        }
×
1497

1498
        txDetails := make([]*lnwallet.TransactionDetail, 0,
3✔
1499
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
3✔
1500

3✔
1501
        // For both confirmed and unconfirmed transactions, create a
3✔
1502
        // TransactionDetail which re-packages the data returned by the base
3✔
1503
        // wallet.
3✔
1504
        for _, blockPackage := range txns.MinedTransactions {
6✔
1505
                details, err := minedTransactionsToDetails(
3✔
1506
                        currentHeight, blockPackage, b.netParams,
3✔
1507
                )
3✔
1508
                if err != nil {
3✔
1509
                        return nil, 0, 0, err
×
1510
                }
×
1511

1512
                txDetails = append(txDetails, details...)
3✔
1513
        }
1514
        for _, tx := range txns.UnminedTransactions {
6✔
1515
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
3✔
1516
                if err != nil {
3✔
1517
                        return nil, 0, 0, err
×
1518
                }
×
1519

1520
                txDetails = append(txDetails, detail)
3✔
1521
        }
1522

1523
        // Return empty transaction list, if offset is more than all
1524
        // transactions.
1525
        if int(indexOffset) >= len(txDetails) {
3✔
UNCOV
1526
                txDetails = []*lnwallet.TransactionDetail{}
×
UNCOV
1527

×
UNCOV
1528
                return txDetails, 0, 0, nil
×
UNCOV
1529
        }
×
1530

1531
        end := indexOffset + maxTransactions
3✔
1532

3✔
1533
        // If maxTransactions is set to 0, then we'll return all transactions
3✔
1534
        // starting from the offset.
3✔
1535
        if maxTransactions == 0 {
6✔
1536
                end = uint32(len(txDetails))
3✔
1537
                txDetails = txDetails[indexOffset:end]
3✔
1538

3✔
1539
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
3✔
1540
        }
3✔
1541

UNCOV
1542
        if end > uint32(len(txDetails)) {
×
UNCOV
1543
                end = uint32(len(txDetails))
×
UNCOV
1544
        }
×
1545

UNCOV
1546
        txDetails = txDetails[indexOffset:end]
×
UNCOV
1547

×
UNCOV
1548
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
×
1549
}
1550

1551
// txSubscriptionClient encapsulates the transaction notification client from
1552
// the base wallet. Notifications received from the client will be proxied over
1553
// two distinct channels.
1554
type txSubscriptionClient struct {
1555
        txClient base.TransactionNotificationsClient
1556

1557
        confirmed   chan *lnwallet.TransactionDetail
1558
        unconfirmed chan *lnwallet.TransactionDetail
1559

1560
        w base.Interface
1561

1562
        wg   sync.WaitGroup
1563
        quit chan struct{}
1564
}
1565

1566
// ConfirmedTransactions returns a channel which will be sent on as new
1567
// relevant transactions are confirmed.
1568
//
1569
// This is part of the TransactionSubscription interface.
UNCOV
1570
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
×
UNCOV
1571
        return t.confirmed
×
UNCOV
1572
}
×
1573

1574
// UnconfirmedTransactions returns a channel which will be sent on as
1575
// new relevant transactions are seen within the network.
1576
//
1577
// This is part of the TransactionSubscription interface.
UNCOV
1578
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
×
UNCOV
1579
        return t.unconfirmed
×
UNCOV
1580
}
×
1581

1582
// Cancel finalizes the subscription, cleaning up any resources allocated.
1583
//
1584
// This is part of the TransactionSubscription interface.
UNCOV
1585
func (t *txSubscriptionClient) Cancel() {
×
UNCOV
1586
        close(t.quit)
×
UNCOV
1587
        t.wg.Wait()
×
UNCOV
1588

×
UNCOV
1589
        t.txClient.Done()
×
UNCOV
1590
}
×
1591

1592
// notificationProxier proxies the notifications received by the underlying
1593
// wallet's notification client to a higher-level TransactionSubscription
1594
// client.
UNCOV
1595
func (t *txSubscriptionClient) notificationProxier() {
×
UNCOV
1596
        defer t.wg.Done()
×
UNCOV
1597

×
UNCOV
1598
out:
×
UNCOV
1599
        for {
×
UNCOV
1600
                select {
×
UNCOV
1601
                case txNtfn := <-t.txClient.C:
×
UNCOV
1602
                        // TODO(roasbeef): handle detached blocks
×
UNCOV
1603
                        currentHeight := t.w.SyncedTo().Height
×
UNCOV
1604

×
UNCOV
1605
                        // Launch a goroutine to re-package and send
×
UNCOV
1606
                        // notifications for any newly confirmed transactions.
×
UNCOV
1607
                        //nolint:ll
×
UNCOV
1608
                        go func(txNtfn *base.TransactionNotifications) {
×
UNCOV
1609
                                for _, block := range txNtfn.AttachedBlocks {
×
UNCOV
1610
                                        details, err := minedTransactionsToDetails(
×
UNCOV
1611
                                                currentHeight, block,
×
UNCOV
1612
                                                t.w.ChainParams(),
×
UNCOV
1613
                                        )
×
UNCOV
1614
                                        if err != nil {
×
1615
                                                continue
×
1616
                                        }
1617

UNCOV
1618
                                        for _, d := range details {
×
UNCOV
1619
                                                select {
×
UNCOV
1620
                                                case t.confirmed <- d:
×
1621
                                                case <-t.quit:
×
1622
                                                        return
×
1623
                                                }
1624
                                        }
1625
                                }
1626
                        }(txNtfn)
1627

1628
                        // Launch a goroutine to re-package and send
1629
                        // notifications for any newly unconfirmed transactions.
UNCOV
1630
                        go func(txNtfn *base.TransactionNotifications) {
×
UNCOV
1631
                                for _, tx := range txNtfn.UnminedTransactions {
×
UNCOV
1632
                                        detail, err := unminedTransactionsToDetail(
×
UNCOV
1633
                                                tx, t.w.ChainParams(),
×
UNCOV
1634
                                        )
×
UNCOV
1635
                                        if err != nil {
×
1636
                                                continue
×
1637
                                        }
1638

UNCOV
1639
                                        select {
×
UNCOV
1640
                                        case t.unconfirmed <- detail:
×
1641
                                        case <-t.quit:
×
1642
                                                return
×
1643
                                        }
1644
                                }
1645
                        }(txNtfn)
UNCOV
1646
                case <-t.quit:
×
UNCOV
1647
                        break out
×
1648
                }
1649
        }
1650
}
1651

1652
// SubscribeTransactions returns a TransactionSubscription client which
1653
// is capable of receiving async notifications as new transactions
1654
// related to the wallet are seen within the network, or found in
1655
// blocks.
1656
//
1657
// This is a part of the WalletController interface.
UNCOV
1658
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
×
UNCOV
1659
        walletClient := b.wallet.NotificationServer().TransactionNotifications()
×
UNCOV
1660

×
UNCOV
1661
        txClient := &txSubscriptionClient{
×
UNCOV
1662
                txClient:    walletClient,
×
UNCOV
1663
                confirmed:   make(chan *lnwallet.TransactionDetail),
×
UNCOV
1664
                unconfirmed: make(chan *lnwallet.TransactionDetail),
×
UNCOV
1665
                w:           b.wallet,
×
UNCOV
1666
                quit:        make(chan struct{}),
×
UNCOV
1667
        }
×
UNCOV
1668
        txClient.wg.Add(1)
×
UNCOV
1669
        go txClient.notificationProxier()
×
UNCOV
1670

×
UNCOV
1671
        return txClient, nil
×
UNCOV
1672
}
×
1673

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

3✔
1682
        // We'll also extract the current best wallet timestamp so the caller
3✔
1683
        // can get an idea of where we are in the sync timeline.
3✔
1684
        bestTimestamp := syncState.Timestamp.Unix()
3✔
1685

3✔
1686
        // Next, query the chain backend to grab the info about the tip of the
3✔
1687
        // main chain.
3✔
1688
        bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
3✔
1689
        if err != nil {
3✔
1690
                return false, 0, err
×
1691
        }
×
1692

1693
        // Make sure the backing chain has been considered synced first.
1694
        if !b.wallet.ChainSynced() {
6✔
1695
                bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
3✔
1696
                if err != nil {
3✔
1697
                        return false, 0, err
×
1698
                }
×
1699
                bestTimestamp = bestHeader.Timestamp.Unix()
3✔
1700
                return false, bestTimestamp, nil
3✔
1701
        }
1702

1703
        // If the wallet hasn't yet fully synced to the node's best chain tip,
1704
        // then we're not yet fully synced.
1705
        if syncState.Height < bestHeight {
6✔
1706
                return false, bestTimestamp, nil
3✔
1707
        }
3✔
1708

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

1718
        // If the timestamp on the best header is more than 2 hours in the
1719
        // past, then we're not yet synced.
1720
        minus24Hours := time.Now().Add(-2 * time.Hour)
3✔
1721
        if blockHeader.Timestamp.Before(minus24Hours) {
4✔
1722
                return false, bestTimestamp, nil
1✔
1723
        }
1✔
1724

1725
        return true, bestTimestamp, nil
3✔
1726
}
1727

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

3✔
1737
        // A zero value in RecoveryWindow indicates there is no trigger of
3✔
1738
        // recovery mode.
3✔
1739
        if b.cfg.RecoveryWindow == 0 {
6✔
1740
                isRecoveryMode = false
3✔
1741
                return isRecoveryMode, progress, nil
3✔
1742
        }
3✔
1743

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

1754
                return isRecoveryMode, progress, err
×
1755
        }
1756

1757
        // Grab the best chain state the wallet is currently aware of.
1758
        syncState := b.wallet.SyncedTo()
3✔
1759

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

1775
        // The birthday block height might be greater than the current synced height
1776
        // in a newly restored wallet, and might be greater than the chain tip if a
1777
        // rollback happens. In that case, we will return zero progress here.
1778
        if syncState.Height < birthdayBlock.Height ||
3✔
1779
                bestHeight < birthdayBlock.Height {
3✔
1780

×
1781
                return isRecoveryMode, progress, nil
×
1782
        }
×
1783

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

3✔
1796
        return isRecoveryMode, progress, nil
3✔
1797
}
1798

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

1808
        return tx.Summary.Tx, nil
3✔
1809
}
1810

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

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

1830
        // Sanity check that the expected single result is returned.
1831
        if len(results) != 1 {
2✔
UNCOV
1832
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
UNCOV
1833
                        "instead got %v", len(results))
×
UNCOV
1834
        }
×
1835

1836
        result := results[0]
2✔
1837
        log.Debugf("TestMempoolAccept result: %s",
2✔
1838
                lnutils.SpewLogClosure(result))
2✔
1839

2✔
1840
        // Mempool check failed, we now map the reject reason to a proper RPC
2✔
1841
        // error and return it.
2✔
1842
        if !result.Allowed {
4✔
1843
                err := b.chain.MapRPCErr(errors.New(result.RejectReason))
2✔
1844

2✔
1845
                return fmt.Errorf("mempool rejection: %w", err)
2✔
1846
        }
2✔
1847

1848
        return nil
2✔
1849
}
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