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

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

90
        chain chain.Interface
91

92
        db walletdb.DB
93

94
        cfg *Config
95

96
        netParams *chaincfg.Params
97

98
        chainKeyScope waddrmgr.KeyScope
99

100
        blockCache *blockcache.BlockCache
101

102
        *input.MusigSessionManager
103
}
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

266
        return exists, err
×
267
}
268

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

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

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

287
        return ""
×
288
}
289

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

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

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

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

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

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

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

364
                scope = manager
12✔
365
        }
366

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

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

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

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

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

12✔
395
        return nil
12✔
396
}
397

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

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

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

8✔
409
        return nil
8✔
410
}
8✔
411

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

163✔
423
        var balance btcutil.Amount
163✔
424

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

432
        for _, witnessOutput := range witnessOutputs {
2,512✔
433
                balance += witnessOutput.Value
2,349✔
434
        }
2,349✔
435

436
        return balance, nil
163✔
437
}
438

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

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

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

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

471
        return addrKeyScope, accountNumber, nil
×
472
}
473

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

577
                        break
×
578
                }
579

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

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

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

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

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

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

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

650
        return res, nil
×
651
}
652

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

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

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

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

678
        return reserved
80✔
679
}
680

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
777
                        addresses[accntDetails] = addressProperties
×
778
                }
×
779
        }
780

781
        return addresses, nil
×
782
}
783

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1017
        return lockedUntil, nil
140✔
1018
}
1019

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

×
1024
        return b.wallet.ListLeasedOutputs()
×
1025
}
×
1026

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

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

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

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

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

1080
                if addressType == lnwallet.WitnessPubKey ||
3,488✔
1081
                        addressType == lnwallet.NestedWitnessPubKey ||
3,488✔
1082
                        addressType == lnwallet.TaprootPubkey {
6,976✔
1083

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

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

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

1109
        }
1110

1111
        return witnessOutputs, nil
254✔
1112
}
1113

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

3✔
1130
                return lnwallet.ErrDoubleSpend
3✔
1131

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

1139
        return err
52✔
1140
}
1141

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

13✔
1154
                return mapRpcclientError(err)
13✔
1155
        }
13✔
1156

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

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

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

1176
                return err
×
1177
        }
1178

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

1185
        result := results[0]
42✔
1186
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
42✔
1187

42✔
1188
        // Once mempool check passed, we can publish the transaction.
42✔
1189
        if result.Allowed {
69✔
1190
                err = b.wallet.PublishTransaction(tx, label)
27✔
1191

27✔
1192
                return mapRpcclientError(err)
27✔
1193
        }
27✔
1194

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

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

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

6✔
1224
                err := b.wallet.PublishTransaction(tx, label)
6✔
1225
                return mapRpcclientError(err)
6✔
1226
        }
1227

1228
        return mapRpcclientError(err)
9✔
1229
}
1230

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

×
1239
        return b.wallet.LabelTransaction(hash, label, overwrite)
×
1240
}
×
1241

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

1259
        return balanceDelta, nil
2,189✔
1260
}
1261

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

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

1277
        previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
2,194✔
1278
        for idx, txIn := range wireTx.TxIn {
6,362✔
1279
                previousOutpoints[idx] = lnwallet.PreviousOutPoint{
4,168✔
1280
                        OutPoint:    txIn.PreviousOutPoint.String(),
4,168✔
1281
                        IsOurOutput: isOurOutput[uint32(idx)],
4,168✔
1282
                }
4,168✔
1283
        }
4,168✔
1284

1285
        return previousOutpoints
2,194✔
1286
}
1287

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

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

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

1321
                return txDetails[0], nil
4✔
1322
        }
1323

1324
        return unminedTransactionsToDetail(tx.Summary, b.netParams)
×
1325
}
1326

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

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

2,154✔
1340
                if err := wireTx.Deserialize(txReader); err != nil {
2,154✔
1341
                        return nil, err
×
1342
                }
×
1343

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

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

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

1375
                previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
2,154✔
1376

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

2,154✔
1390
                balanceDelta, err := extractBalanceDelta(tx, wireTx)
2,154✔
1391
                if err != nil {
2,154✔
1392
                        return nil, err
×
1393
                }
×
1394
                txDetail.Value = balanceDelta
2,154✔
1395

2,154✔
1396
                details = append(details, txDetail)
2,154✔
1397
        }
1398

1399
        return details, nil
430✔
1400
}
1401

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

35✔
1409
        wireTx := &wire.MsgTx{}
35✔
1410
        txReader := bytes.NewReader(summary.Transaction)
35✔
1411

35✔
1412
        if err := wireTx.Deserialize(txReader); err != nil {
35✔
1413
                return nil, err
×
1414
        }
×
1415

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

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

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

1447
        previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
35✔
1448

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

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

35✔
1465
        return txDetail, nil
35✔
1466
}
1467

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

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

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

1496
        txDetails := make([]*lnwallet.TransactionDetail, 0,
108✔
1497
                len(txns.MinedTransactions)+len(txns.UnminedTransactions))
108✔
1498

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

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

1518
                txDetails = append(txDetails, detail)
22✔
1519
        }
1520

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

12✔
1526
                return txDetails, 0, 0, nil
12✔
1527
        }
12✔
1528

1529
        end := indexOffset + maxTransactions
96✔
1530

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

8✔
1537
                return txDetails, uint64(indexOffset), uint64(end - 1), nil
8✔
1538
        }
8✔
1539

1540
        if end > uint32(len(txDetails)) {
168✔
1541
                end = uint32(len(txDetails))
80✔
1542
        }
80✔
1543

1544
        txDetails = txDetails[indexOffset:end]
88✔
1545

88✔
1546
        return txDetails, uint64(indexOffset), uint64(end - 1), nil
88✔
1547
}
1548

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

1555
        confirmed   chan *lnwallet.TransactionDetail
1556
        unconfirmed chan *lnwallet.TransactionDetail
1557

1558
        w *base.Wallet
1559

1560
        wg   sync.WaitGroup
1561
        quit chan struct{}
1562
}
1563

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

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

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

4✔
1587
        t.txClient.Done()
4✔
1588
}
4✔
1589

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

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

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

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

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

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

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

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

4✔
1669
        return txClient, nil
4✔
1670
}
4✔
1671

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

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

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

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

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

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

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

1723
        return true, bestTimestamp, nil
142✔
1724
}
1725

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

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

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

1752
                return isRecoveryMode, progress, err
×
1753
        }
1754

1755
        // Grab the best chain state the wallet is currently aware of.
1756
        syncState := b.wallet.Manager.SyncedTo()
8✔
1757

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

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

×
1779
                return isRecoveryMode, progress, nil
×
1780
        }
×
1781

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

8✔
1794
        return isRecoveryMode, progress, nil
8✔
1795
}
1796

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

1806
        return tx.Summary.Tx, nil
×
1807
}
1808

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

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

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

1834
        result := results[0]
2✔
1835
        log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
2✔
1836

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

1✔
1842
                return fmt.Errorf("mempool rejection: %w", err)
1✔
1843
        }
1✔
1844

1845
        return nil
1✔
1846
}
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