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

lightningnetwork / lnd / 14000719599

21 Mar 2025 08:54PM UTC coverage: 58.717% (-10.3%) from 68.989%
14000719599

Pull #8754

github

web-flow
Merge 29f363f18 into 5235f3b24
Pull Request #8754: Add `Outbound` Remote Signer implementation

1562 of 2088 new or added lines in 41 files covered. (74.81%)

28126 existing lines in 464 files now uncovered.

97953 of 166822 relevant lines covered (58.72%)

1.82 hits per line

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

72.98
/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) {
3✔
113
        // Create the key scope for the coin type being managed by this wallet.
3✔
114
        chainKeyScope := waddrmgr.KeyScope{
3✔
115
                Purpose: keychain.BIP0043Purpose,
3✔
116
                Coin:    cfg.CoinType,
3✔
117
        }
3✔
118

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

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

143
                if !walletExists {
6✔
144
                        // Wallet has never been created, perform initial
3✔
145
                        // set up.
3✔
146
                        wallet, err = loader.CreateNewWallet(
3✔
147
                                pubPass, cfg.PrivatePass, cfg.HdSeed,
3✔
148
                                cfg.Birthday,
3✔
149
                        )
3✔
150
                        if err != nil {
3✔
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{
3✔
165
                cfg:           &cfg,
3✔
166
                wallet:        wallet,
3✔
167
                db:            wallet.Database(),
3✔
168
                chain:         cfg.ChainSource,
3✔
169
                netParams:     cfg.NetParams,
3✔
170
                chainKeyScope: chainKeyScope,
3✔
171
                blockCache:    blockCache,
3✔
172
        }
3✔
173

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

3✔
178
        return finalWallet, nil
3✔
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 {
3✔
196

3✔
197
        return func(cfg *loaderCfg) {
6✔
198
                cfg.dbDirPath = dbDirPath
3✔
199
                cfg.noFreelistSync = noFreelistSync
3✔
200
                cfg.dbTimeout = dbTimeout
3✔
201
                cfg.useLocalDB = true
3✔
202
        }
3✔
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) {
3✔
215

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

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

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

228
        if cfg.externalDB != nil {
3✔
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(
3✔
246
                chainParams, cfg.dbDirPath, cfg.noFreelistSync,
3✔
247
                cfg.dbTimeout, recoveryWindow,
3✔
248
        ), nil
3✔
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.
UNCOV
282
func (b *BtcWallet) BackEnd() string {
×
UNCOV
283
        if b.chain != nil {
×
UNCOV
284
                return b.chain.BackEnd()
×
UNCOV
285
        }
×
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 {
3✔
293
        return b.wallet
3✔
294
}
3✔
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 {
3✔
301
        // Is the wallet (according to its database) currently watch-only
3✔
302
        // already? If it is, we won't need to convert it later.
3✔
303
        walletIsWatchOnly := b.wallet.Manager.WatchOnly()
3✔
304

3✔
305
        // If the wallet is watch-only, but we don't expect it to be, then we
3✔
306
        // are in an unexpected state and cannot continue.
3✔
307
        if walletIsWatchOnly && !b.cfg.WatchOnly {
3✔
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 {
6✔
319
                if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
3✔
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 {
6✔
327
                        log.Warnf("Wallet is expected to be in watch-only " +
3✔
328
                                "mode but hasn't been migrated to watch-only " +
3✔
329
                                "yet, it still contains private keys; " +
3✔
330
                                "consider turning on the watch-only wallet " +
3✔
331
                                "migration in remote signing mode")
3✔
332
                }
3✔
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 {
6✔
339
                _, err := b.wallet.Manager.FetchScopedKeyManager(scope)
3✔
340
                if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) {
3✔
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]
×
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)
3✔
353
        if err != nil {
6✔
354
                // If the scope hasn't yet been created (it wouldn't been
3✔
355
                // loaded by default if it was), then we'll manually create the
3✔
356
                // scope for the first time ourselves.
3✔
357
                manager, err := b.wallet.AddScopeManager(
3✔
358
                        b.chainKeyScope, lightningAddrSchema,
3✔
359
                )
3✔
360
                if err != nil {
3✔
361
                        return err
×
362
                }
×
363

364
                scope = manager
3✔
365
        }
366

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

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

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

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

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

3✔
395
        return nil
3✔
396
}
397

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

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

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

3✔
409
        return nil
3✔
410
}
3✔
411

412
// ReadySignal currently signals that the wallet is ready instantly.
413
func (b *BtcWallet) ReadySignal() chan error {
3✔
414
        readyChan := make(chan error, 1)
3✔
415
        readyChan <- nil
3✔
416

3✔
417
        return readyChan
3✔
418
}
3✔
419

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

3✔
431
        var balance btcutil.Amount
3✔
432

3✔
433
        witnessOutputs, err := b.ListUnspentWitness(
3✔
434
                confs, math.MaxInt32, accountFilter,
3✔
435
        )
3✔
436
        if err != nil {
3✔
UNCOV
437
                return 0, err
×
UNCOV
438
        }
×
439

440
        for _, witnessOutput := range witnessOutputs {
6✔
441
                balance += witnessOutput.Value
3✔
442
        }
3✔
443

444
        return balance, nil
3✔
445
}
446

447
// keyScopeForAccountAddr determines the appropriate key scope of an account
448
// based on its name/address type.
449
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
450
        addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
3✔
451

3✔
452
        // Map the requested address type to its key scope.
3✔
453
        var addrKeyScope waddrmgr.KeyScope
3✔
454
        switch addrType {
3✔
455
        case lnwallet.WitnessPubKey:
3✔
456
                addrKeyScope = waddrmgr.KeyScopeBIP0084
3✔
457
        case lnwallet.NestedWitnessPubKey:
3✔
458
                addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
3✔
459
        case lnwallet.TaprootPubkey:
3✔
460
                addrKeyScope = waddrmgr.KeyScopeBIP0086
3✔
UNCOV
461
        default:
×
UNCOV
462
                return waddrmgr.KeyScope{}, 0,
×
UNCOV
463
                        fmt.Errorf("unknown address type")
×
464
        }
465

466
        // The default account spans across multiple key scopes, so the
467
        // requested address type should already be valid for this account.
468
        if accountName == lnwallet.DefaultAccountName {
6✔
469
                return addrKeyScope, defaultAccount, nil
3✔
470
        }
3✔
471

472
        // Otherwise, look up the custom account and if it supports the given
473
        // key scope.
474
        accountNumber, err := b.wallet.AccountNumber(addrKeyScope, accountName)
3✔
475
        if err != nil {
3✔
NEW
476
                return waddrmgr.KeyScope{}, 0, err
×
UNCOV
477
        }
×
478

479
        return addrKeyScope, accountNumber, nil
3✔
480
}
481

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

3✔
492
        // Addresses cannot be derived from the catch-all imported accounts.
3✔
493
        if accountName == waddrmgr.ImportedAddrAccountName {
3✔
494
                return nil, errNoImportedAddrGen
×
495
        }
×
496

497
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
3✔
498
        if err != nil {
3✔
499
                return nil, err
×
500
        }
×
501

502
        if change {
6✔
503
                return b.wallet.NewChangeAddress(account, keyScope)
3✔
504
        }
3✔
505
        return b.wallet.NewAddress(account, keyScope)
3✔
506
}
507

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

3✔
518
        // Addresses cannot be derived from the catch-all imported accounts.
3✔
519
        if accountName == waddrmgr.ImportedAddrAccountName {
3✔
520
                return nil, errNoImportedAddrGen
×
521
        }
×
522

523
        keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
3✔
524
        if err != nil {
3✔
525
                return nil, err
×
526
        }
×
527

528
        return b.wallet.CurrentAddress(account, keyScope)
3✔
529
}
530

531
// IsOurAddress checks if the passed address belongs to this wallet
532
//
533
// This is a part of the WalletController interface.
534
func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool {
3✔
535
        result, err := b.wallet.HaveAddress(a)
3✔
536
        return result && (err == nil)
3✔
537
}
3✔
538

539
// AddressInfo returns the information about an address, if it's known to this
540
// wallet.
541
//
542
// NOTE: This is a part of the WalletController interface.
543
func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress,
544
        error) {
3✔
545

3✔
546
        return b.wallet.AddressInfo(a)
3✔
547
}
3✔
548

549
// ListAccounts retrieves all accounts belonging to the wallet by default. A
550
// name and key scope filter can be provided to filter through all of the wallet
551
// accounts and return only those matching.
552
//
553
// This is a part of the WalletController interface.
554
func (b *BtcWallet) ListAccounts(name string,
555
        keyScope *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
3✔
556

3✔
557
        var res []*waddrmgr.AccountProperties
3✔
558
        switch {
3✔
559
        // If both the name and key scope filters were provided, we'll return
560
        // the existing account matching those.
561
        case name != "" && keyScope != nil:
3✔
562
                account, err := b.wallet.AccountPropertiesByName(*keyScope, name)
3✔
563
                if err != nil {
3✔
564
                        return nil, err
×
565
                }
×
566
                res = append(res, account)
3✔
567

568
        // Only the name filter was provided.
569
        case name != "" && keyScope == nil:
3✔
570
                // If the name corresponds to the default or imported accounts,
3✔
571
                // we'll return them for all our supported key scopes.
3✔
572
                if name == lnwallet.DefaultAccountName ||
3✔
573
                        name == waddrmgr.ImportedAddrAccountName {
3✔
574

×
575
                        for _, defaultScope := range LndDefaultKeyScopes {
×
576
                                a, err := b.wallet.AccountPropertiesByName(
×
577
                                        defaultScope, name,
×
578
                                )
×
579
                                if err != nil {
×
580
                                        return nil, err
×
581
                                }
×
582
                                res = append(res, a)
×
583
                        }
584

585
                        break
×
586
                }
587

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

601
                        // In the specific case of a wallet initialized only by
602
                        // importing account xpubs (watch only wallets), it is
603
                        // possible that some keyscopes will be 'unknown' by the
604
                        // wallet (depending on the xpubs given to initialize
605
                        // it). If the keyscope is not found, just skip it.
606
                        case waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound):
3✔
607
                                continue
3✔
608

609
                        case err != nil:
×
610
                                return nil, err
×
611
                        }
612

613
                        res = append(res, a)
3✔
614
                }
615
                if len(res) == 0 {
6✔
616
                        return nil, newAccountNotFoundError(name)
3✔
617
                }
3✔
618

619
        // Only the key scope filter was provided, so we'll return all accounts
620
        // matching it.
621
        case name == "" && keyScope != nil:
×
622
                accounts, err := b.wallet.Accounts(*keyScope)
×
623
                if err != nil {
×
624
                        return nil, err
×
625
                }
×
626
                for _, account := range accounts.Accounts {
×
627
                        account := account
×
628
                        res = append(res, &account.AccountProperties)
×
629
                }
×
630

631
        // Neither of the filters were provided, so return all accounts for our
632
        // supported key scopes.
633
        case name == "" && keyScope == nil:
3✔
634
                for _, defaultScope := range LndDefaultKeyScopes {
6✔
635
                        accounts, err := b.wallet.Accounts(defaultScope)
3✔
636
                        if err != nil {
3✔
637
                                return nil, err
×
638
                        }
×
639
                        for _, account := range accounts.Accounts {
6✔
640
                                account := account
3✔
641
                                res = append(res, &account.AccountProperties)
3✔
642
                        }
3✔
643
                }
644

645
                accounts, err := b.wallet.Accounts(waddrmgr.KeyScope{
3✔
646
                        Purpose: keychain.BIP0043Purpose,
3✔
647
                        Coin:    b.cfg.CoinType,
3✔
648
                })
3✔
649
                if err != nil {
3✔
650
                        return nil, err
×
651
                }
×
652
                for _, account := range accounts.Accounts {
6✔
653
                        account := account
3✔
654
                        res = append(res, &account.AccountProperties)
3✔
655
                }
3✔
656
        }
657

658
        return res, nil
3✔
659
}
660

661
// newAccountNotFoundError returns an error indicating that the manager didn't
662
// find the specific account. This error is used to be compatible with the old
663
// 'LookupAccount' behaviour previously used.
664
func newAccountNotFoundError(name string) error {
3✔
665
        str := fmt.Sprintf("account name '%s' not found", name)
3✔
666

3✔
667
        return waddrmgr.ManagerError{
3✔
668
                ErrorCode:   waddrmgr.ErrAccountNotFound,
3✔
669
                Description: str,
3✔
670
        }
3✔
671
}
3✔
672

673
// RequiredReserve returns the minimum amount of satoshis that should be
674
// kept in the wallet in order to fee bump anchor channels if necessary.
675
// The value scales with the number of public anchor channels but is
676
// capped at a maximum.
677
func (b *BtcWallet) RequiredReserve(
678
        numAnchorChans uint32) btcutil.Amount {
3✔
679

3✔
680
        anchorChanReservedValue := lnwallet.AnchorChanReservedValue
3✔
681
        reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
3✔
682
        if reserved > lnwallet.MaxAnchorChanReservedValue {
3✔
683
                reserved = lnwallet.MaxAnchorChanReservedValue
×
684
        }
×
685

686
        return reserved
3✔
687
}
688

689
// ListAddresses retrieves all the addresses along with their balance. An
690
// account name filter can be provided to filter through all of the
691
// wallet accounts and return the addresses of only those matching.
692
//
693
// This is a part of the WalletController interface.
694
func (b *BtcWallet) ListAddresses(name string,
695
        showCustomAccounts bool) (lnwallet.AccountAddressMap, error) {
3✔
696

3✔
697
        accounts, err := b.ListAccounts(name, nil)
3✔
698
        if err != nil {
3✔
699
                return nil, err
×
700
        }
×
701

702
        addresses := make(lnwallet.AccountAddressMap)
3✔
703
        addressBalance := make(map[string]btcutil.Amount)
3✔
704

3✔
705
        // Retrieve all the unspent ouputs.
3✔
706
        outputs, err := b.wallet.ListUnspent(0, math.MaxInt32, "")
3✔
707
        if err != nil {
3✔
708
                return nil, err
×
709
        }
×
710

711
        // Calculate the total balance of each address.
712
        for _, output := range outputs {
6✔
713
                amount, err := btcutil.NewAmount(output.Amount)
3✔
714
                if err != nil {
3✔
715
                        return nil, err
×
716
                }
×
717

718
                addressBalance[output.Address] += amount
3✔
719
        }
720

721
        for _, accntDetails := range accounts {
6✔
722
                accntScope := accntDetails.KeyScope
3✔
723
                managedAddrs, err := b.wallet.AccountManagedAddresses(
3✔
724
                        accntDetails.KeyScope, accntDetails.AccountNumber,
3✔
725
                )
3✔
726
                if err != nil {
3✔
727
                        return nil, err
×
728
                }
×
729

730
                // Only consider those accounts which have addresses.
731
                if len(managedAddrs) == 0 {
6✔
732
                        continue
3✔
733
                }
734

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

3✔
744
                for idx, managedAddr := range managedAddrs {
6✔
745
                        addr := managedAddr.Address()
3✔
746
                        addressString := addr.String()
3✔
747

3✔
748
                        // Hex-encode the compressed public key for custom lnd
3✔
749
                        // keys, addresses don't make a lot of sense.
3✔
750
                        var (
3✔
751
                                pubKey         *btcec.PublicKey
3✔
752
                                derivationPath string
3✔
753
                        )
3✔
754
                        pka, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
3✔
755
                        if ok {
6✔
756
                                pubKey = pka.PubKey()
3✔
757

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

773
                        addressProperties[idx] = lnwallet.AddressProperty{
3✔
774
                                Address:        addressString,
3✔
775
                                Internal:       managedAddr.Internal(),
3✔
776
                                Balance:        addressBalance[addressString],
3✔
777
                                PublicKey:      pubKey,
3✔
778
                                DerivationPath: derivationPath,
3✔
779
                        }
3✔
780
                }
781

782
                if accntScope.Purpose != keychain.BIP0043Purpose ||
3✔
783
                        showCustomAccounts {
6✔
784

3✔
785
                        addresses[accntDetails] = addressProperties
3✔
786
                }
3✔
787
        }
788

789
        return addresses, nil
3✔
790
}
791

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

3✔
822
        // For custom accounts, we first check if there is no existing account
3✔
823
        // with the same name.
3✔
824
        if name != lnwallet.DefaultAccountName &&
3✔
825
                name != waddrmgr.ImportedAddrAccountName {
6✔
826

3✔
827
                _, err := b.ListAccounts(name, nil)
3✔
828
                if err == nil {
6✔
829
                        return nil, nil, nil,
3✔
830
                                fmt.Errorf("account '%s' already exists",
3✔
831
                                        name)
3✔
832
                }
3✔
833
                if !waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
3✔
834
                        return nil, nil, nil, err
×
835
                }
×
836
        }
837

838
        if !dryRun {
6✔
839
                accountProps, err := b.wallet.ImportAccount(
3✔
840
                        name, accountPubKey, masterKeyFingerprint, addrType,
3✔
841
                )
3✔
842
                if err != nil {
3✔
843
                        return nil, nil, nil, err
×
844
                }
×
845
                return accountProps, nil, nil, nil
3✔
846
        }
847

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

859
        externalAddrs := make([]btcutil.Address, len(extAddrs))
×
860
        for i := 0; i < len(extAddrs); i++ {
×
861
                externalAddrs[i] = extAddrs[i].Address()
×
862
        }
×
863

864
        internalAddrs := make([]btcutil.Address, len(intAddrs))
×
865
        for i := 0; i < len(intAddrs); i++ {
×
866
                internalAddrs[i] = intAddrs[i].Address()
×
867
        }
×
868

869
        return accountProps, externalAddrs, internalAddrs, nil
×
870
}
871

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

3✔
882
        return b.wallet.ImportPublicKey(pubKey, addrType)
3✔
883
}
3✔
884

885
// ImportTaprootScript imports a user-provided taproot script into the address
886
// manager. The imported script will act as a pay-to-taproot address.
887
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
888
        tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
3✔
889

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

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

3✔
900
        return b.wallet.ImportTaprootScript(
3✔
901
                scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
3✔
902
        )
3✔
903
}
3✔
904

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

3✔
917
        // Convert our fee rate from sat/kw to sat/kb since it's required by
3✔
918
        // SendOutputs.
3✔
919
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
3✔
920

3✔
921
        // Sanity check outputs.
3✔
922
        if len(outputs) < 1 {
3✔
UNCOV
923
                return nil, lnwallet.ErrNoOutputs
×
UNCOV
924
        }
×
925

926
        // Sanity check minConfs.
927
        if minConfs < 0 {
3✔
928
                return nil, lnwallet.ErrInvalidMinconf
×
929
        }
×
930

931
        // Use selected UTXOs if specified, otherwise default selection.
932
        if len(inputs) != 0 {
6✔
933
                return b.wallet.SendOutputsWithInput(
3✔
934
                        outputs, nil, defaultAccount, minConfs, feeSatPerKB,
3✔
935
                        strategy, label, inputs.ToSlice(),
3✔
936
                )
3✔
937
        }
3✔
938

939
        return b.wallet.SendOutputs(
3✔
940
                outputs, nil, defaultAccount, minConfs, feeSatPerKB,
3✔
941
                strategy, label,
3✔
942
        )
3✔
943
}
944

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

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

3✔
967
        // Sanity check outputs.
3✔
968
        if len(outputs) < 1 {
3✔
UNCOV
969
                return nil, lnwallet.ErrNoOutputs
×
UNCOV
970
        }
×
971

972
        // Sanity check minConfs.
973
        if minConfs < 0 {
3✔
974
                return nil, lnwallet.ErrInvalidMinconf
×
975
        }
×
976

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

991
        // Add the optional inputs to the transaction.
992
        optFunc := wallet.WithCustomSelectUtxos(inputs.ToSlice())
3✔
993

3✔
994
        return b.wallet.CreateSimpleTx(
3✔
995
                nil, defaultAccount, outputs, minConfs, feeSatPerKB,
3✔
996
                strategy, dryRun, []wallet.TxCreateOption{optFunc}...,
3✔
997
        )
3✔
998
}
999

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

3✔
1014
        // Make sure we don't attempt to double lock an output that's been
3✔
1015
        // locked by the in-memory implementation.
3✔
1016
        if b.wallet.LockedOutpoint(op) {
3✔
1017
                return time.Time{}, wtxmgr.ErrOutputAlreadyLocked
×
1018
        }
×
1019

1020
        lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
3✔
1021
        if err != nil {
3✔
1022
                return time.Time{}, err
×
1023
        }
×
1024

1025
        return lockedUntil, nil
3✔
1026
}
1027

1028
// ListLeasedOutputs returns a list of all currently locked outputs.
1029
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
1030
        error) {
3✔
1031

3✔
1032
        return b.wallet.ListLeasedOutputs()
3✔
1033
}
3✔
1034

1035
// ReleaseOutput unlocks an output, allowing it to be available for coin
1036
// selection if it remains unspent. The ID should match the one used to
1037
// originally lock the output.
1038
//
1039
// NOTE: This method requires the global coin selection lock to be held.
1040
func (b *BtcWallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error {
3✔
1041
        return b.wallet.ReleaseOutput(id, op)
3✔
1042
}
3✔
1043

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

3✔
1059
        // First, grab all the unfiltered currently unspent outputs.
3✔
1060
        unspentOutputs, err := b.wallet.ListUnspent(
3✔
1061
                minConfs, maxConfs, accountFilter,
3✔
1062
        )
3✔
1063
        if err != nil {
3✔
1064
                return nil, err
×
1065
        }
×
1066

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

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

1088
                if addressType == lnwallet.WitnessPubKey ||
3✔
1089
                        addressType == lnwallet.NestedWitnessPubKey ||
3✔
1090
                        addressType == lnwallet.TaprootPubkey {
6✔
1091

3✔
1092
                        txid, err := chainhash.NewHashFromStr(output.TxID)
3✔
1093
                        if err != nil {
3✔
1094
                                return nil, err
×
1095
                        }
×
1096

1097
                        // We'll ensure we properly convert the amount given in
1098
                        // BTC to satoshis.
1099
                        amt, err := btcutil.NewAmount(output.Amount)
3✔
1100
                        if err != nil {
3✔
1101
                                return nil, err
×
1102
                        }
×
1103

1104
                        utxo := &lnwallet.Utxo{
3✔
1105
                                AddressType: addressType,
3✔
1106
                                Value:       amt,
3✔
1107
                                PkScript:    pkScript,
3✔
1108
                                OutPoint: wire.OutPoint{
3✔
1109
                                        Hash:  *txid,
3✔
1110
                                        Index: output.Vout,
3✔
1111
                                },
3✔
1112
                                Confirmations: output.Confirmations,
3✔
1113
                        }
3✔
1114
                        witnessOutputs = append(witnessOutputs, utxo)
3✔
1115
                }
1116

1117
        }
1118

1119
        return witnessOutputs, nil
3✔
1120
}
1121

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

2✔
1138
                return lnwallet.ErrDoubleSpend
2✔
1139

1140
        // If the wallet reports that fee requirements for accepting the tx
1141
        // into mempool are not met, convert it to our internal ErrMempoolFee
1142
        // and return.
1143
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
×
1144
                return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee, err.Error())
×
1145
        }
1146

1147
        return err
3✔
1148
}
1149

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

1✔
1162
                return mapRpcclientError(err)
1✔
1163
        }
1✔
1164

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

×
1179
                        err := b.wallet.PublishTransaction(tx, label)
×
1180

×
1181
                        return mapRpcclientError(err)
×
1182
                }
×
1183

1184
                return err
×
1185
        }
1186

1187
        // Sanity check that the expected single result is returned.
1188
        if len(results) != 1 {
2✔
1189
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
1190
                        "instead got %v", len(results))
×
1191
        }
×
1192

1193
        result := results[0]
2✔
1194
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
2✔
1195

2✔
1196
        // Once mempool check passed, we can publish the transaction.
2✔
1197
        if result.Allowed {
4✔
1198
                err = b.wallet.PublishTransaction(tx, label)
2✔
1199

2✔
1200
                return mapRpcclientError(err)
2✔
1201
        }
2✔
1202

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

2✔
1208
        // We need to use the string to create an error type and map it to a
2✔
1209
        // btcwallet error.
2✔
1210
        err = b.chain.MapRPCErr(errors.New(result.RejectReason))
2✔
1211

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

2✔
1232
                err := b.wallet.PublishTransaction(tx, label)
2✔
1233
                return mapRpcclientError(err)
2✔
1234
        }
1235

1236
        return mapRpcclientError(err)
2✔
1237
}
1238

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

3✔
1247
        return b.wallet.LabelTransaction(hash, label, overwrite)
3✔
1248
}
3✔
1249

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

1267
        return balanceDelta, nil
3✔
1268
}
1269

1270
// getPreviousOutpoints is a helper function which gets the previous
1271
// outpoints of a transaction.
1272
func getPreviousOutpoints(wireTx *wire.MsgTx,
1273
        myInputs []base.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
3✔
1274

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

1285
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
3✔
1286
        for idx, txIn := range wireTx.TxIn {
6✔
1287
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
3✔
1288
                        OutPoint:    txIn.PreviousOutPoint.String(),
3✔
1289
                        IsOurOutput: isOurOutput[uint32(idx)],
3✔
1290
                }
3✔
1291
        }
3✔
1292

1293
        return previousOutpoints
3✔
1294
}
1295

1296
// GetTransactionDetails returns details of a transaction given its
1297
// transaction hash.
1298
func (b *BtcWallet) GetTransactionDetails(
1299
        txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
3✔
1300

3✔
1301
        // Grab the best block the wallet knows of, we'll use this to calculate
3✔
1302
        // # of confirmations shortly below.
3✔
1303
        bestBlock := b.wallet.Manager.SyncedTo()
3✔
1304
        currentHeight := bestBlock.Height
3✔
1305
        tx, err := b.wallet.GetTransaction(*txHash)
3✔
1306
        if err != nil {
3✔
1307
                return nil, err
×
1308
        }
×
1309

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

1329
                return txDetails[0], nil
3✔
1330
        }
1331

1332
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
3✔
1333
}
1334

1335
// minedTransactionsToDetails is a helper function which converts a summary
1336
// information about mined transactions to a TransactionDetail.
1337
func minedTransactionsToDetails(
1338
        currentHeight int32,
1339
        block base.Block,
1340
        chainParams *chaincfg.Params,
1341
) ([]*lnwallet.TransactionDetail, error) {
3✔
1342

3✔
1343
        details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
3✔
1344
        for _, tx := range block.Transactions {
6✔
1345
                wireTx := &wire.MsgTx{}
3✔
1346
                txReader := bytes.NewReader(tx.Transaction)
3✔
1347

3✔
1348
                if err := wireTx.Deserialize(txReader); err != nil {
3✔
1349
                        return nil, err
×
1350
                }
×
1351

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

1362
                var outputDetails []lnwallet.OutputDetail
3✔
1363
                for i, txOut := range wireTx.TxOut {
6✔
1364
                        var addresses []btcutil.Address
3✔
1365
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
3✔
1366
                                txOut.PkScript, chainParams,
3✔
1367
                        )
3✔
1368
                        if err == nil {
6✔
1369
                                // Add supported addresses.
3✔
1370
                                addresses = outAddresses
3✔
1371
                        }
3✔
1372

1373
                        outputDetails = append(outputDetails, lnwallet.OutputDetail{
3✔
1374
                                OutputType:   sc,
3✔
1375
                                Addresses:    addresses,
3✔
1376
                                PkScript:     txOut.PkScript,
3✔
1377
                                OutputIndex:  i,
3✔
1378
                                Value:        btcutil.Amount(txOut.Value),
3✔
1379
                                IsOurAddress: isOurAddress[i],
3✔
1380
                        })
3✔
1381
                }
1382

1383
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
3✔
1384

3✔
1385
                txDetail := &lnwallet.TransactionDetail{
3✔
1386
                        Hash:              *tx.Hash,
3✔
1387
                        NumConfirmations:  currentHeight - block.Height + 1,
3✔
1388
                        BlockHash:         block.Hash,
3✔
1389
                        BlockHeight:       block.Height,
3✔
1390
                        Timestamp:         block.Timestamp,
3✔
1391
                        TotalFees:         int64(tx.Fee),
3✔
1392
                        OutputDetails:     outputDetails,
3✔
1393
                        RawTx:             tx.Transaction,
3✔
1394
                        Label:             tx.Label,
3✔
1395
                        PreviousOutpoints: previousOutpoints,
3✔
1396
                }
3✔
1397

3✔
1398
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
3✔
1399
                if err != nil {
3✔
1400
                        return nil, err
×
1401
                }
×
1402
                txDetail.Value = balanceDelta
3✔
1403

3✔
1404
                details = append(details, txDetail)
3✔
1405
        }
1406

1407
        return details, nil
3✔
1408
}
1409

1410
// unminedTransactionsToDetail is a helper function which converts a summary
1411
// for an unconfirmed transaction to a transaction detail.
1412
func unminedTransactionsToDetail(
1413
        summary base.TransactionSummary,
1414
        chainParams *chaincfg.Params,
1415
) (*lnwallet.TransactionDetail, error) {
3✔
1416

3✔
1417
        wireTx := &wire.MsgTx{}
3✔
1418
        txReader := bytes.NewReader(summary.Transaction)
3✔
1419

3✔
1420
        if err := wireTx.Deserialize(txReader); err != nil {
3✔
1421
                return nil, err
×
1422
        }
×
1423

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

1434
        var outputDetails []lnwallet.OutputDetail
3✔
1435
        for i, txOut := range wireTx.TxOut {
6✔
1436
                var addresses []btcutil.Address
3✔
1437
                sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
3✔
1438
                        txOut.PkScript, chainParams,
3✔
1439
                )
3✔
1440
                if err == nil {
6✔
1441
                        // Add supported addresses.
3✔
1442
                        addresses = outAddresses
3✔
1443
                }
3✔
1444

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

1455
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
3✔
1456

3✔
1457
        txDetail := &lnwallet.TransactionDetail{
3✔
1458
                Hash:              *summary.Hash,
3✔
1459
                TotalFees:         int64(summary.Fee),
3✔
1460
                Timestamp:         summary.Timestamp,
3✔
1461
                OutputDetails:     outputDetails,
3✔
1462
                RawTx:             summary.Transaction,
3✔
1463
                Label:             summary.Label,
3✔
1464
                PreviousOutpoints: previousOutpoints,
3✔
1465
        }
3✔
1466

3✔
1467
        balanceDelta, err := extractBalanceDelta(summary, wireTx)
3✔
1468
        if err != nil {
3✔
1469
                return nil, err
×
1470
        }
×
1471
        txDetail.Value = balanceDelta
3✔
1472

3✔
1473
        return txDetail, nil
3✔
1474
}
1475

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

3✔
1491
        // Grab the best block the wallet knows of, we'll use this to calculate
3✔
1492
        // # of confirmations shortly below.
3✔
1493
        bestBlock := b.wallet.Manager.SyncedTo()
3✔
1494
        currentHeight := bestBlock.Height
3✔
1495

3✔
1496
        // We'll attempt to find all transactions from start to end height.
3✔
1497
        start := base.NewBlockIdentifierFromHeight(startHeight)
3✔
1498
        stop := base.NewBlockIdentifierFromHeight(endHeight)
3✔
1499
        txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
3✔
1500
        if err != nil {
3✔
1501
                return nil, 0, 0, err
×
1502
        }
×
1503

1504
        txDetails := make([]*lnwallet.TransactionDetail, 0,
3✔
1505
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
3✔
1506

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

1518
                txDetails = append(txDetails, details...)
3✔
1519
        }
1520
        for _, tx := range txns.UnminedTransactions {
6✔
1521
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
3✔
1522
                if err != nil {
3✔
1523
                        return nil, 0, 0, err
×
1524
                }
×
1525

1526
                txDetails = append(txDetails, detail)
3✔
1527
        }
1528

1529
        // Return empty transaction list, if offset is more than all
1530
        // transactions.
1531
        if int(indexOffset) >= len(txDetails) {
3✔
UNCOV
1532
                txDetails = []*lnwallet.TransactionDetail{}
×
UNCOV
1533

×
UNCOV
1534
                return txDetails, 0, 0, nil
×
UNCOV
1535
        }
×
1536

1537
        end := indexOffset + maxTransactions
3✔
1538

3✔
1539
        // If maxTransactions is set to 0, then we'll return all transactions
3✔
1540
        // starting from the offset.
3✔
1541
        if maxTransactions == 0 {
6✔
1542
                end = uint32(len(txDetails))
3✔
1543
                txDetails = txDetails[indexOffset:end]
3✔
1544

3✔
1545
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
3✔
1546
        }
3✔
1547

UNCOV
1548
        if end > uint32(len(txDetails)) {
×
UNCOV
1549
                end = uint32(len(txDetails))
×
UNCOV
1550
        }
×
1551

UNCOV
1552
        txDetails = txDetails[indexOffset:end]
×
UNCOV
1553

×
UNCOV
1554
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
×
1555
}
1556

1557
// txSubscriptionClient encapsulates the transaction notification client from
1558
// the base wallet. Notifications received from the client will be proxied over
1559
// two distinct channels.
1560
type txSubscriptionClient struct {
1561
        txClient base.TransactionNotificationsClient
1562

1563
        confirmed   chan *lnwallet.TransactionDetail
1564
        unconfirmed chan *lnwallet.TransactionDetail
1565

1566
        w *base.Wallet
1567

1568
        wg   sync.WaitGroup
1569
        quit chan struct{}
1570
}
1571

1572
// ConfirmedTransactions returns a channel which will be sent on as new
1573
// relevant transactions are confirmed.
1574
//
1575
// This is part of the TransactionSubscription interface.
UNCOV
1576
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
×
UNCOV
1577
        return t.confirmed
×
UNCOV
1578
}
×
1579

1580
// UnconfirmedTransactions returns a channel which will be sent on as
1581
// new relevant transactions are seen within the network.
1582
//
1583
// This is part of the TransactionSubscription interface.
UNCOV
1584
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
×
UNCOV
1585
        return t.unconfirmed
×
UNCOV
1586
}
×
1587

1588
// Cancel finalizes the subscription, cleaning up any resources allocated.
1589
//
1590
// This is part of the TransactionSubscription interface.
UNCOV
1591
func (t *txSubscriptionClient) Cancel() {
×
UNCOV
1592
        close(t.quit)
×
UNCOV
1593
        t.wg.Wait()
×
UNCOV
1594

×
UNCOV
1595
        t.txClient.Done()
×
UNCOV
1596
}
×
1597

1598
// notificationProxier proxies the notifications received by the underlying
1599
// wallet's notification client to a higher-level TransactionSubscription
1600
// client.
UNCOV
1601
func (t *txSubscriptionClient) notificationProxier() {
×
UNCOV
1602
        defer t.wg.Done()
×
UNCOV
1603

×
UNCOV
1604
out:
×
UNCOV
1605
        for {
×
UNCOV
1606
                select {
×
UNCOV
1607
                case txNtfn := <-t.txClient.C:
×
UNCOV
1608
                        // TODO(roasbeef): handle detached blocks
×
UNCOV
1609
                        currentHeight := t.w.Manager.SyncedTo().Height
×
UNCOV
1610

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

UNCOV
1624
                                        for _, d := range details {
×
UNCOV
1625
                                                select {
×
UNCOV
1626
                                                case t.confirmed <- d:
×
1627
                                                case <-t.quit:
×
1628
                                                        return
×
1629
                                                }
1630
                                        }
1631
                                }
1632
                        }(txNtfn)
1633

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

UNCOV
1645
                                        select {
×
UNCOV
1646
                                        case t.unconfirmed <- detail:
×
1647
                                        case <-t.quit:
×
1648
                                                return
×
1649
                                        }
1650
                                }
1651
                        }(txNtfn)
UNCOV
1652
                case <-t.quit:
×
UNCOV
1653
                        break out
×
1654
                }
1655
        }
1656
}
1657

1658
// SubscribeTransactions returns a TransactionSubscription client which
1659
// is capable of receiving async notifications as new transactions
1660
// related to the wallet are seen within the network, or found in
1661
// blocks.
1662
//
1663
// This is a part of the WalletController interface.
UNCOV
1664
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
×
UNCOV
1665
        walletClient := b.wallet.NtfnServer.TransactionNotifications()
×
UNCOV
1666

×
UNCOV
1667
        txClient := &txSubscriptionClient{
×
UNCOV
1668
                txClient:    walletClient,
×
UNCOV
1669
                confirmed:   make(chan *lnwallet.TransactionDetail),
×
UNCOV
1670
                unconfirmed: make(chan *lnwallet.TransactionDetail),
×
UNCOV
1671
                w:           b.wallet,
×
UNCOV
1672
                quit:        make(chan struct{}),
×
UNCOV
1673
        }
×
UNCOV
1674
        txClient.wg.Add(1)
×
UNCOV
1675
        go txClient.notificationProxier()
×
UNCOV
1676

×
UNCOV
1677
        return txClient, nil
×
UNCOV
1678
}
×
1679

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

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

3✔
1692
        // Next, query the chain backend to grab the info about the tip of the
3✔
1693
        // main chain.
3✔
1694
        bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
3✔
1695
        if err != nil {
3✔
1696
                return false, 0, err
×
1697
        }
×
1698

1699
        // Make sure the backing chain has been considered synced first.
1700
        if !b.wallet.ChainSynced() {
6✔
1701
                bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
3✔
1702
                if err != nil {
3✔
1703
                        return false, 0, err
×
1704
                }
×
1705
                bestTimestamp = bestHeader.Timestamp.Unix()
3✔
1706
                return false, bestTimestamp, nil
3✔
1707
        }
1708

1709
        // If the wallet hasn't yet fully synced to the node's best chain tip,
1710
        // then we're not yet fully synced.
1711
        if syncState.Height < bestHeight {
5✔
1712
                return false, bestTimestamp, nil
2✔
1713
        }
2✔
1714

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

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

1731
        return true, bestTimestamp, nil
3✔
1732
}
1733

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

3✔
1743
        // A zero value in RecoveryWindow indicates there is no trigger of
3✔
1744
        // recovery mode.
3✔
1745
        if b.cfg.RecoveryWindow == 0 {
6✔
1746
                isRecoveryMode = false
3✔
1747
                return isRecoveryMode, progress, nil
3✔
1748
        }
3✔
1749

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

1760
                return isRecoveryMode, progress, err
×
1761
        }
1762

1763
        // Grab the best chain state the wallet is currently aware of.
1764
        syncState := b.wallet.Manager.SyncedTo()
3✔
1765

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

1781
        // The birthday block height might be greater than the current synced height
1782
        // in a newly restored wallet, and might be greater than the chain tip if a
1783
        // rollback happens. In that case, we will return zero progress here.
1784
        if syncState.Height < birthdayBlock.Height ||
3✔
1785
                bestHeight < birthdayBlock.Height {
3✔
1786

×
1787
                return isRecoveryMode, progress, nil
×
1788
        }
×
1789

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

3✔
1802
        return isRecoveryMode, progress, nil
3✔
1803
}
1804

1805
// FetchTx attempts to fetch a transaction in the wallet's database identified
1806
// by the passed transaction hash. If the transaction can't be found, then a
1807
// nil pointer is returned.
1808
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
3✔
1809
        tx, err := b.wallet.GetTransaction(txHash)
3✔
1810
        if err != nil {
5✔
1811
                return nil, err
2✔
1812
        }
2✔
1813

1814
        return tx.Summary.Tx, nil
3✔
1815
}
1816

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

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

1836
        // Sanity check that the expected single result is returned.
1837
        if len(results) != 1 {
2✔
UNCOV
1838
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
×
UNCOV
1839
                        "instead got %v", len(results))
×
UNCOV
1840
        }
×
1841

1842
        result := results[0]
2✔
1843
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
2✔
1844

2✔
1845
        // Mempool check failed, we now map the reject reason to a proper RPC
2✔
1846
        // error and return it.
2✔
1847
        if !result.Allowed {
4✔
1848
                err := b.chain.MapRPCErr(errors.New(result.RejectReason))
2✔
1849

2✔
1850
                return fmt.Errorf("mempool rejection: %w", err)
2✔
1851
        }
2✔
1852

1853
        return nil
2✔
1854
}
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