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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

59.7
/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"
31
        "github.com/lightningnetwork/lnd/input"
32
        "github.com/lightningnetwork/lnd/keychain"
33
        "github.com/lightningnetwork/lnd/kvdb"
34
        "github.com/lightningnetwork/lnd/lnwallet"
35
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
36
)
37

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

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

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

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

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

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

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

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

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

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

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

98
        chain chain.Interface
99

100
        db walletdb.DB
101

102
        cfg *Config
103

104
        netParams *chaincfg.Params
105

106
        chainKeyScope waddrmgr.KeyScope
107

108
        blockCache *blockcache.BlockCache
109

110
        *input.MusigSessionManager
111
}
112

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

274
        return exists, err
×
275
}
276

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

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

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

295
        return ""
×
296
}
297

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

12✔
452
        return nil
12✔
453
}
454

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

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

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

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

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

125✔
480
        var balance btcutil.Amount
125✔
481

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

489
        for _, witnessOutput := range witnessOutputs {
2,250✔
490
                balance += witnessOutput.Value
2,125✔
491
        }
2,125✔
492

493
        return balance, nil
125✔
494
}
495

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

634
                        break
×
635
                }
636

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

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

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

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

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

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

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

UNCOV
707
        return res, nil
×
708
}
709

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

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

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

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

735
        return reserved
80✔
736
}
737

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
859
        return addresses, nil
×
860
}
861

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1095
        return lockedUntil, nil
140✔
1096
}
1097

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

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

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

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

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

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

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

1158
                if addressType == lnwallet.WitnessPubKey ||
3,263✔
1159
                        addressType == lnwallet.NestedWitnessPubKey ||
3,263✔
1160
                        addressType == lnwallet.TaprootPubkey {
6,526✔
1161

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

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

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

1187
        }
1188

1189
        return witnessOutputs, nil
213✔
1190
}
1191

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

11✔
1208
                return lnwallet.ErrDoubleSpend
11✔
1209

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

1217
        return err
60✔
1218
}
1219

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

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

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

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

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

1254
                return err
×
1255
        }
1256

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

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

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

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

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

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

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

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

1306
        return mapRpcclientError(err)
15✔
1307
}
1308

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

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

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

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

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

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

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

1363
        return previousOutpoints
2,034✔
1364
}
1365

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

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

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

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

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

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

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

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

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

1432
                var outputDetails []lnwallet.OutputDetail
1,994✔
1433
                for i, txOut := range wireTx.TxOut {
5,918✔
1434
                        var addresses []btcutil.Address
3,924✔
1435
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
3,924✔
1436
                                txOut.PkScript, chainParams,
3,924✔
1437
                        )
3,924✔
1438
                        if err == nil {
7,848✔
1439
                                // Add supported addresses.
3,924✔
1440
                                addresses = outAddresses
3,924✔
1441
                        }
3,924✔
1442

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

1453
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
1,994✔
1454

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

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

1,994✔
1474
                details = append(details, txDetail)
1,994✔
1475
        }
1476

1477
        return details, nil
398✔
1478
}
1479

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

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

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

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

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

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

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

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

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

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

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

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

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

1572
        txDetails := make([]*lnwallet.TransactionDetail, 0,
80✔
1573
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
80✔
1574

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

1586
                txDetails = append(txDetails, details...)
390✔
1587
        }
1588
        for _, tx := range txns.UnminedTransactions {
102✔
1589
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
22✔
1590
                if err != nil {
22✔
1591
                        return nil, err
×
1592
                }
×
1593

1594
                txDetails = append(txDetails, detail)
22✔
1595
        }
1596

1597
        return txDetails, nil
80✔
1598
}
1599

1600
// txSubscriptionClient encapsulates the transaction notification client from
1601
// the base wallet. Notifications received from the client will be proxied over
1602
// two distinct channels.
1603
type txSubscriptionClient struct {
1604
        txClient base.TransactionNotificationsClient
1605

1606
        confirmed   chan *lnwallet.TransactionDetail
1607
        unconfirmed chan *lnwallet.TransactionDetail
1608

1609
        w *base.Wallet
1610

1611
        wg   sync.WaitGroup
1612
        quit chan struct{}
1613
}
1614

1615
// ConfirmedTransactions returns a channel which will be sent on as new
1616
// relevant transactions are confirmed.
1617
//
1618
// This is part of the TransactionSubscription interface.
1619
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
12✔
1620
        return t.confirmed
12✔
1621
}
12✔
1622

1623
// UnconfirmedTransactions returns a channel which will be sent on as
1624
// new relevant transactions are seen within the network.
1625
//
1626
// This is part of the TransactionSubscription interface.
1627
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
13✔
1628
        return t.unconfirmed
13✔
1629
}
13✔
1630

1631
// Cancel finalizes the subscription, cleaning up any resources allocated.
1632
//
1633
// This is part of the TransactionSubscription interface.
1634
func (t *txSubscriptionClient) Cancel() {
4✔
1635
        close(t.quit)
4✔
1636
        t.wg.Wait()
4✔
1637

4✔
1638
        t.txClient.Done()
4✔
1639
}
4✔
1640

1641
// notificationProxier proxies the notifications received by the underlying
1642
// wallet's notification client to a higher-level TransactionSubscription
1643
// client.
1644
func (t *txSubscriptionClient) notificationProxier() {
4✔
1645
        defer t.wg.Done()
4✔
1646

4✔
1647
out:
4✔
1648
        for {
25✔
1649
                select {
21✔
1650
                case txNtfn := <-t.txClient.C:
17✔
1651
                        // TODO(roasbeef): handle detached blocks
17✔
1652
                        currentHeight := t.w.Manager.SyncedTo().Height
17✔
1653

17✔
1654
                        // Launch a goroutine to re-package and send
17✔
1655
                        // notifications for any newly confirmed transactions.
17✔
1656
                        //nolint:lll
17✔
1657
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1658
                                for _, block := range txNtfn.AttachedBlocks {
21✔
1659
                                        details, err := minedTransactionsToDetails(
4✔
1660
                                                currentHeight, block,
4✔
1661
                                                t.w.ChainParams(),
4✔
1662
                                        )
4✔
1663
                                        if err != nil {
4✔
1664
                                                continue
×
1665
                                        }
1666

1667
                                        for _, d := range details {
16✔
1668
                                                select {
12✔
1669
                                                case t.confirmed <- d:
12✔
1670
                                                case <-t.quit:
×
1671
                                                        return
×
1672
                                                }
1673
                                        }
1674
                                }
1675
                        }(txNtfn)
1676

1677
                        // Launch a goroutine to re-package and send
1678
                        // notifications for any newly unconfirmed transactions.
1679
                        go func(txNtfn *base.TransactionNotifications) {
34✔
1680
                                for _, tx := range txNtfn.UnminedTransactions {
30✔
1681
                                        detail, err := unminedTransactionsToDetail(
13✔
1682
                                                tx, t.w.ChainParams(),
13✔
1683
                                        )
13✔
1684
                                        if err != nil {
13✔
1685
                                                continue
×
1686
                                        }
1687

1688
                                        select {
13✔
1689
                                        case t.unconfirmed <- detail:
13✔
1690
                                        case <-t.quit:
×
1691
                                                return
×
1692
                                        }
1693
                                }
1694
                        }(txNtfn)
1695
                case <-t.quit:
4✔
1696
                        break out
4✔
1697
                }
1698
        }
1699
}
1700

1701
// SubscribeTransactions returns a TransactionSubscription client which
1702
// is capable of receiving async notifications as new transactions
1703
// related to the wallet are seen within the network, or found in
1704
// blocks.
1705
//
1706
// This is a part of the WalletController interface.
1707
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
4✔
1708
        walletClient := b.wallet.NtfnServer.TransactionNotifications()
4✔
1709

4✔
1710
        txClient := &txSubscriptionClient{
4✔
1711
                txClient:    walletClient,
4✔
1712
                confirmed:   make(chan *lnwallet.TransactionDetail),
4✔
1713
                unconfirmed: make(chan *lnwallet.TransactionDetail),
4✔
1714
                w:           b.wallet,
4✔
1715
                quit:        make(chan struct{}),
4✔
1716
        }
4✔
1717
        txClient.wg.Add(1)
4✔
1718
        go txClient.notificationProxier()
4✔
1719

4✔
1720
        return txClient, nil
4✔
1721
}
4✔
1722

1723
// IsSynced returns a boolean indicating if from the PoV of the wallet, it has
1724
// fully synced to the current best block in the main chain.
1725
//
1726
// This is a part of the WalletController interface.
1727
func (b *BtcWallet) IsSynced() (bool, int64, error) {
195✔
1728
        // Grab the best chain state the wallet is currently aware of.
195✔
1729
        syncState := b.wallet.Manager.SyncedTo()
195✔
1730

195✔
1731
        // We'll also extract the current best wallet timestamp so the caller
195✔
1732
        // can get an idea of where we are in the sync timeline.
195✔
1733
        bestTimestamp := syncState.Timestamp.Unix()
195✔
1734

195✔
1735
        // Next, query the chain backend to grab the info about the tip of the
195✔
1736
        // main chain.
195✔
1737
        bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
195✔
1738
        if err != nil {
195✔
1739
                return false, 0, err
×
1740
        }
×
1741

1742
        // Make sure the backing chain has been considered synced first.
1743
        if !b.wallet.ChainSynced() {
195✔
UNCOV
1744
                bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
×
UNCOV
1745
                if err != nil {
×
1746
                        return false, 0, err
×
1747
                }
×
UNCOV
1748
                bestTimestamp = bestHeader.Timestamp.Unix()
×
UNCOV
1749
                return false, bestTimestamp, nil
×
1750
        }
1751

1752
        // If the wallet hasn't yet fully synced to the node's best chain tip,
1753
        // then we're not yet fully synced.
1754
        if syncState.Height < bestHeight {
252✔
1755
                return false, bestTimestamp, nil
57✔
1756
        }
57✔
1757

1758
        // If the wallet is on par with the current best chain tip, then we
1759
        // still may not yet be synced as the chain backend may still be
1760
        // catching up to the main chain. So we'll grab the block header in
1761
        // order to make a guess based on the current time stamp.
1762
        blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
138✔
1763
        if err != nil {
138✔
1764
                return false, 0, err
×
1765
        }
×
1766

1767
        // If the timestamp on the best header is more than 2 hours in the
1768
        // past, then we're not yet synced.
1769
        minus24Hours := time.Now().Add(-2 * time.Hour)
138✔
1770
        if blockHeader.Timestamp.Before(minus24Hours) {
138✔
UNCOV
1771
                return false, bestTimestamp, nil
×
UNCOV
1772
        }
×
1773

1774
        return true, bestTimestamp, nil
138✔
1775
}
1776

1777
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
1778
// in recovery mode. It also returns a float64, ranging from 0 to 1,
1779
// representing the recovery progress made so far.
1780
//
1781
// This is a part of the WalletController interface.
1782
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
12✔
1783
        isRecoveryMode := true
12✔
1784
        progress := float64(0)
12✔
1785

12✔
1786
        // A zero value in RecoveryWindow indicates there is no trigger of
12✔
1787
        // recovery mode.
12✔
1788
        if b.cfg.RecoveryWindow == 0 {
16✔
1789
                isRecoveryMode = false
4✔
1790
                return isRecoveryMode, progress, nil
4✔
1791
        }
4✔
1792

1793
        // Query the wallet's birthday block height from db.
1794
        var birthdayBlock waddrmgr.BlockStamp
8✔
1795
        err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
16✔
1796
                var err error
8✔
1797
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
8✔
1798
                birthdayBlock, _, err = b.wallet.Manager.BirthdayBlock(addrmgrNs)
8✔
1799
                if err != nil {
8✔
1800
                        return err
×
1801
                }
×
1802
                return nil
8✔
1803
        })
1804

1805
        if err != nil {
8✔
1806
                // The wallet won't start until the backend is synced, thus the birthday
×
1807
                // block won't be set and this particular error will be returned. We'll
×
1808
                // catch this error and return a progress of 0 instead.
×
1809
                if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
×
1810
                        return isRecoveryMode, progress, nil
×
1811
                }
×
1812

1813
                return isRecoveryMode, progress, err
×
1814
        }
1815

1816
        // Grab the best chain state the wallet is currently aware of.
1817
        syncState := b.wallet.Manager.SyncedTo()
8✔
1818

8✔
1819
        // Next, query the chain backend to grab the info about the tip of the
8✔
1820
        // main chain.
8✔
1821
        //
8✔
1822
        // NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
8✔
1823
        // The process purposefully doesn't update the best height. It might create
8✔
1824
        // a small difference between the height queried here and the height used
8✔
1825
        // in the recovery process, ie, the bestHeight used here might be greater,
8✔
1826
        // showing the recovery being unfinished while it's actually done. However,
8✔
1827
        // during a wallet rescan after the recovery, the wallet's synced height
8✔
1828
        // will catch up and this won't be an issue.
8✔
1829
        _, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
8✔
1830
        if err != nil {
8✔
1831
                return isRecoveryMode, progress, err
×
1832
        }
×
1833

1834
        // The birthday block height might be greater than the current synced height
1835
        // in a newly restored wallet, and might be greater than the chain tip if a
1836
        // rollback happens. In that case, we will return zero progress here.
1837
        if syncState.Height < birthdayBlock.Height ||
8✔
1838
                bestHeight < birthdayBlock.Height {
8✔
1839

×
1840
                return isRecoveryMode, progress, nil
×
1841
        }
×
1842

1843
        // progress is the ratio of the [number of blocks processed] over the [total
1844
        // number of blocks] needed in a recovery mode, ranging from 0 to 1, in
1845
        // which,
1846
        // - total number of blocks is the current chain's best height minus the
1847
        //   wallet's birthday height plus 1.
1848
        // - number of blocks processed is the wallet's synced height minus its
1849
        //   birthday height plus 1.
1850
        // - If the wallet is born very recently, the bestHeight can be equal to
1851
        //   the birthdayBlock.Height, and it will recovery instantly.
1852
        progress = float64(syncState.Height-birthdayBlock.Height+1) /
8✔
1853
                float64(bestHeight-birthdayBlock.Height+1)
8✔
1854

8✔
1855
        return isRecoveryMode, progress, nil
8✔
1856
}
1857

1858
// FetchTx attempts to fetch a transaction in the wallet's database identified
1859
// by the passed transaction hash. If the transaction can't be found, then a
1860
// nil pointer is returned.
UNCOV
1861
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
×
UNCOV
1862
        var targetTx *wtxmgr.TxDetails
×
UNCOV
1863
        err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
×
UNCOV
1864
                wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
×
UNCOV
1865
                txDetails, err := b.wallet.TxStore.TxDetails(wtxmgrNs, &txHash)
×
UNCOV
1866
                if err != nil {
×
1867
                        return err
×
1868
                }
×
1869

UNCOV
1870
                targetTx = txDetails
×
UNCOV
1871

×
UNCOV
1872
                return nil
×
1873
        })
UNCOV
1874
        if err != nil {
×
1875
                return nil, err
×
1876
        }
×
1877

UNCOV
1878
        if targetTx == nil {
×
UNCOV
1879
                return nil, nil
×
UNCOV
1880
        }
×
1881

UNCOV
1882
        return &targetTx.TxRecord.MsgTx, nil
×
1883
}
1884

1885
// RemoveDescendants attempts to remove any transaction from the wallet's tx
1886
// store (that may be unconfirmed) that spends outputs created by the passed
1887
// transaction. This remove propagates recursively down the chain of descendent
1888
// transactions.
UNCOV
1889
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
×
UNCOV
1890
        txRecord, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
×
UNCOV
1891
        if err != nil {
×
1892
                return err
×
1893
        }
×
1894

UNCOV
1895
        return walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
×
UNCOV
1896
                wtxmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
×
UNCOV
1897
                return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
×
UNCOV
1898
        })
×
1899
}
1900

1901
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
1902
// the mempool acceptance of a transaction.
1903
func (b *BtcWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
5✔
1904
        // Use a max feerate of 0 means the default value will be used when
5✔
1905
        // testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
5✔
1906
        // or 10,000 sat/vb.
5✔
1907
        results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
5✔
1908
        if err != nil {
7✔
1909
                return err
2✔
1910
        }
2✔
1911

1912
        // Sanity check that the expected single result is returned.
1913
        if len(results) != 1 {
4✔
1914
                return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
1✔
1915
                        "instead got %v", len(results))
1✔
1916
        }
1✔
1917

1918
        result := results[0]
2✔
1919
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
2✔
1920

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

1✔
1926
                return fmt.Errorf("mempool rejection: %w", err)
1✔
1927
        }
1✔
1928

1929
        return nil
1✔
1930
}
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