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

lightningnetwork / lnd / 18016273007

25 Sep 2025 05:55PM UTC coverage: 54.653% (-12.0%) from 66.622%
18016273007

Pull #10248

github

web-flow
Merge 128443298 into b09b20c69
Pull Request #10248: Enforce TLV when creating a Route

25 of 30 new or added lines in 4 files covered. (83.33%)

23906 existing lines in 281 files now uncovered.

109536 of 200421 relevant lines covered (54.65%)

21816.97 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

89
        chain chain.Interface
90

91
        db walletdb.DB
92

93
        cfg *Config
94

95
        netParams *chaincfg.Params
96

97
        chainKeyScope waddrmgr.KeyScope
98

99
        blockCache *blockcache.BlockCache
100

101
        *input.MusigSessionManager
102
}
103

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

265
        return exists, err
×
266
}
267

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

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

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

286
        return ""
×
287
}
288

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

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

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

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

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

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

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

365
                scope = manager
12✔
366
        }
367

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

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

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

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

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

12✔
396
        return nil
12✔
397
}
398

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

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

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

8✔
410
        return nil
8✔
411
}
8✔
412

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

118✔
424
        var balance btcutil.Amount
118✔
425

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

433
        for _, witnessOutput := range witnessOutputs {
2,059✔
434
                balance += witnessOutput.Value
1,941✔
435
        }
1,941✔
436

437
        return balance, nil
118✔
438
}
439

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

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

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

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

UNCOV
472
        return addrKeyScope, accountNumber, nil
×
473
}
474

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

578
                        break
×
579
                }
580

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

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

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

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

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

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

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

UNCOV
651
        return res, nil
×
652
}
653

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

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

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

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

679
        return reserved
80✔
680
}
681

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

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

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

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

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

UNCOV
711
                addressBalance[output.Address] += amount
×
712
        }
713

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

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

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

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

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

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

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

UNCOV
775
                if accntScope.Purpose != keychain.BIP0043Purpose ||
×
UNCOV
776
                        showCustomAccounts {
×
UNCOV
777

×
UNCOV
778
                        addresses[accntDetails] = addressProperties
×
UNCOV
779
                }
×
780
        }
781

UNCOV
782
        return addresses, nil
×
783
}
784

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

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

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

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

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

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

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

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

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

×
UNCOV
875
        return b.wallet.ImportPublicKey(pubKey, addrType)
×
UNCOV
876
}
×
877

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1018
        return lockedUntil, nil
140✔
1019
}
1020

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

×
UNCOV
1025
        return b.wallet.ListLeasedOutputs()
×
UNCOV
1026
}
×
1027

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

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

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

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

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

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

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

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

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

1110
        }
1111

1112
        return witnessOutputs, nil
206✔
1113
}
1114

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

3✔
1131
                return lnwallet.ErrDoubleSpend
3✔
1132

1133
        // If the wallet reports that fee requirements for accepting the tx
1134
        // into mempool are not met, convert it to our internal ErrMempoolFee
1135
        // and return.
1136
        case errors.Is(err, chain.ErrMempoolMinFeeNotMet),
1137
                errors.Is(err, chain.ErrMinRelayFeeNotMet):
×
1138

×
1139
                return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee, err.Error())
×
1140
        }
1141

1142
        return err
52✔
1143
}
1144

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

13✔
1157
                return mapRpcclientError(err)
13✔
1158
        }
13✔
1159

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

×
1174
                        err := b.wallet.PublishTransaction(tx, label)
×
1175

×
1176
                        return mapRpcclientError(err)
×
1177
                }
×
1178

1179
                return err
×
1180
        }
1181

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

1188
        result := results[0]
42✔
1189
        log.Debugf("TestMempoolAccept result: %s",
42✔
1190
                lnutils.SpewLogClosure(result))
42✔
1191

42✔
1192
        // Once mempool check passed, we can publish the transaction.
42✔
1193
        if result.Allowed {
69✔
1194
                err = b.wallet.PublishTransaction(tx, label)
27✔
1195

27✔
1196
                return mapRpcclientError(err)
27✔
1197
        }
27✔
1198

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

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

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

6✔
1228
                err := b.wallet.PublishTransaction(tx, label)
6✔
1229
                return mapRpcclientError(err)
6✔
1230
        }
1231

1232
        return mapRpcclientError(err)
9✔
1233
}
1234

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

×
UNCOV
1243
        return b.wallet.LabelTransaction(hash, label, overwrite)
×
UNCOV
1244
}
×
1245

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

1263
        return balanceDelta, nil
2,189✔
1264
}
1265

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

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

1281
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
2,194✔
1282
        for idx, txIn := range wireTx.TxIn {
6,324✔
1283
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
4,130✔
1284
                        OutPoint:    txIn.PreviousOutPoint.String(),
4,130✔
1285
                        IsOurOutput: isOurOutput[uint32(idx)],
4,130✔
1286
                }
4,130✔
1287
        }
4,130✔
1288

1289
        return previousOutpoints
2,194✔
1290
}
1291

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

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

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

1325
                return txDetails[0], nil
4✔
1326
        }
1327

UNCOV
1328
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
×
1329
}
1330

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

430✔
1339
        details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
430✔
1340
        for _, tx := range block.Transactions {
2,584✔
1341
                wireTx := &wire.MsgTx{}
2,154✔
1342
                txReader := bytes.NewReader(tx.Transaction)
2,154✔
1343

2,154✔
1344
                if err := wireTx.Deserialize(txReader); err != nil {
2,154✔
1345
                        return nil, err
×
1346
                }
×
1347

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

1358
                var outputDetails []lnwallet.OutputDetail
2,154✔
1359
                for i, txOut := range wireTx.TxOut {
6,398✔
1360
                        var addresses []btcutil.Address
4,244✔
1361
                        sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
4,244✔
1362
                                txOut.PkScript, chainParams,
4,244✔
1363
                        )
4,244✔
1364
                        if err == nil {
8,488✔
1365
                                // Add supported addresses.
4,244✔
1366
                                addresses = outAddresses
4,244✔
1367
                        }
4,244✔
1368

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

1379
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
2,154✔
1380

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

2,154✔
1394
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
2,154✔
1395
                if err != nil {
2,154✔
1396
                        return nil, err
×
1397
                }
×
1398
                txDetail.Value = balanceDelta
2,154✔
1399

2,154✔
1400
                details = append(details, txDetail)
2,154✔
1401
        }
1402

1403
        return details, nil
430✔
1404
}
1405

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

35✔
1413
        wireTx := &wire.MsgTx{}
35✔
1414
        txReader := bytes.NewReader(summary.Transaction)
35✔
1415

35✔
1416
        if err := wireTx.Deserialize(txReader); err != nil {
35✔
1417
                return nil, err
×
1418
        }
×
1419

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

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

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

1451
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
35✔
1452

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

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

35✔
1469
        return txDetail, nil
35✔
1470
}
1471

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

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

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

1500
        txDetails := make([]*lnwallet.TransactionDetail, 0,
108✔
1501
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
108✔
1502

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

1514
                txDetails = append(txDetails, details...)
422✔
1515
        }
1516
        for _, tx := range txns.UnminedTransactions {
130✔
1517
                detail, err := unminedTransactionsToDetail(tx, b.netParams)
22✔
1518
                if err != nil {
22✔
1519
                        return nil, 0, 0, err
×
1520
                }
×
1521

1522
                txDetails = append(txDetails, detail)
22✔
1523
        }
1524

1525
        // Return empty transaction list, if offset is more than all
1526
        // transactions.
1527
        if int(indexOffset) >= len(txDetails) {
120✔
1528
                txDetails = []*lnwallet.TransactionDetail{}
12✔
1529

12✔
1530
                return txDetails, 0, 0, nil
12✔
1531
        }
12✔
1532

1533
        end := indexOffset + maxTransactions
96✔
1534

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

8✔
1541
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
8✔
1542
        }
8✔
1543

1544
        if end > uint32(len(txDetails)) {
168✔
1545
                end = uint32(len(txDetails))
80✔
1546
        }
80✔
1547

1548
        txDetails = txDetails[indexOffset:end]
88✔
1549

88✔
1550
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
88✔
1551
}
1552

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

1559
        confirmed   chan *lnwallet.TransactionDetail
1560
        unconfirmed chan *lnwallet.TransactionDetail
1561

1562
        w base.Interface
1563

1564
        wg   sync.WaitGroup
1565
        quit chan struct{}
1566
}
1567

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

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

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

4✔
1591
        t.txClient.Done()
4✔
1592
}
4✔
1593

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

4✔
1600
out:
4✔
1601
        for {
25✔
1602
                select {
21✔
1603
                case txNtfn := <-t.txClient.C:
17✔
1604
                        // TODO(roasbeef): handle detached blocks
17✔
1605
                        currentHeight := t.w.SyncedTo().Height
17✔
1606

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

1620
                                        for _, d := range details {
16✔
1621
                                                select {
12✔
1622
                                                case t.confirmed <- d:
12✔
1623
                                                case <-t.quit:
×
1624
                                                        return
×
1625
                                                }
1626
                                        }
1627
                                }
1628
                        }(txNtfn)
1629

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

1641
                                        select {
13✔
1642
                                        case t.unconfirmed <- detail:
13✔
1643
                                        case <-t.quit:
×
1644
                                                return
×
1645
                                        }
1646
                                }
1647
                        }(txNtfn)
1648
                case <-t.quit:
4✔
1649
                        break out
4✔
1650
                }
1651
        }
1652
}
1653

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

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

4✔
1673
        return txClient, nil
4✔
1674
}
4✔
1675

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

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

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

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

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

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

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

1727
        return true, bestTimestamp, nil
142✔
1728
}
1729

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

12✔
1739
        // A zero value in RecoveryWindow indicates there is no trigger of
12✔
1740
        // recovery mode.
12✔
1741
        if b.cfg.RecoveryWindow == 0 {
16✔
1742
                isRecoveryMode = false
4✔
1743
                return isRecoveryMode, progress, nil
4✔
1744
        }
4✔
1745

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

1756
                return isRecoveryMode, progress, err
×
1757
        }
1758

1759
        // Grab the best chain state the wallet is currently aware of.
1760
        syncState := b.wallet.SyncedTo()
8✔
1761

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

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

×
1783
                return isRecoveryMode, progress, nil
×
1784
        }
×
1785

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

8✔
1798
        return isRecoveryMode, progress, nil
8✔
1799
}
1800

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

UNCOV
1810
        return tx.Summary.Tx, nil
×
1811
}
1812

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

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

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

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

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

1✔
1847
                return fmt.Errorf("mempool rejection: %w", err)
1✔
1848
        }
1✔
1849

1850
        return nil
1✔
1851
}
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