2016-08-13 00:29:38 +02:00
|
|
|
package btcwallet
|
|
|
|
|
|
|
|
import (
|
2016-10-15 05:15:50 +02:00
|
|
|
"bytes"
|
2016-08-13 00:29:38 +02:00
|
|
|
"encoding/hex"
|
2021-02-20 02:41:45 +01:00
|
|
|
"errors"
|
2016-08-13 00:29:38 +02:00
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"sync"
|
2016-12-09 03:29:55 +01:00
|
|
|
"time"
|
2016-08-13 00:29:38 +02:00
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
2018-06-05 03:34:16 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcwallet/chain"
|
|
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
2022-06-27 12:15:49 +02:00
|
|
|
"github.com/btcsuite/btcwallet/wallet"
|
2018-06-05 03:34:16 +02:00
|
|
|
base "github.com/btcsuite/btcwallet/wallet"
|
2019-03-05 14:22:30 +01:00
|
|
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
|
|
|
"github.com/btcsuite/btcwallet/wallet/txrules"
|
2018-06-05 03:34:16 +02:00
|
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
2020-05-21 00:33:56 +02:00
|
|
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
2021-03-18 13:08:53 +01:00
|
|
|
"github.com/lightningnetwork/lnd/blockcache"
|
2022-04-27 22:20:33 +02:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2018-07-26 04:33:46 +02:00
|
|
|
"github.com/lightningnetwork/lnd/keychain"
|
2021-04-26 19:08:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
2018-07-26 04:33:46 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2019-10-31 03:43:05 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2016-08-13 00:29:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2021-02-20 02:41:45 +01:00
|
|
|
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
|
|
|
|
importedAccount = uint32(waddrmgr.ImportedAddrAccount)
|
2020-05-05 21:10:06 +02:00
|
|
|
|
2021-05-05 00:56:26 +02:00
|
|
|
// dryRunImportAccountNumAddrs represents the number of addresses we'll
|
|
|
|
// derive for an imported account's external and internal branch when a
|
|
|
|
// dry run is attempted.
|
|
|
|
dryRunImportAccountNumAddrs = 5
|
|
|
|
|
2020-05-05 21:10:06 +02:00
|
|
|
// UnconfirmedHeight is the special case end height that is used to
|
|
|
|
// obtain unconfirmed transactions from ListTransactionDetails.
|
|
|
|
UnconfirmedHeight int32 = -1
|
2021-03-04 23:13:06 +01:00
|
|
|
|
|
|
|
// walletMetaBucket is used to store wallet metadata.
|
|
|
|
walletMetaBucket = "lnwallet"
|
|
|
|
|
|
|
|
// walletReadyKey is used to indicate that the wallet has been
|
|
|
|
// initialized.
|
|
|
|
walletReadyKey = "ready"
|
2016-08-13 00:29:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-02-18 00:03:59 +01:00
|
|
|
// waddrmgrNamespaceKey is the namespace key that the waddrmgr state is
|
|
|
|
// stored within the top-level waleltdb buckets of btcwallet.
|
2017-04-24 04:19:17 +02:00
|
|
|
waddrmgrNamespaceKey = []byte("waddrmgr")
|
2018-02-18 00:03:59 +01:00
|
|
|
|
sweep: remove all unconfirmed descendant transactions when a sweep conflicts
Before this commit, we we were trying to sweep an anchor output, and
that output was spent by someone else (not the sweeper), then we would
report this back to the original resolver (allowing it to be cleaned
up), and also remove the set of inputs spent by that transaction from
the set we need to sweep.
However, it's possible that if a user is spending unconfirmed outputs,
then the wallet is holding onto an invalid transaction, as the outputs
that were used as inputs have been double spent elsewhere.
In this commit, we fix this issue by recursively removing all descendant
transactions of our past sweeps that have an intersecting input set as
the spending transaction. In cases where a user spent an unconfirmed
output to funding a channel, and that output was a descendant of the now
swept anchor output, the funds will now properly be marked as available.
Fixes #6241
2022-02-18 03:25:30 +01:00
|
|
|
// wtxmgrNamespaceKey is the namespace key that the wtxmgr state is
|
|
|
|
// stored within the top-level waleltdb buckets of btcwallet.
|
|
|
|
wtxmgrNamespaceKey = []byte("wtxmgr")
|
|
|
|
|
2018-02-18 00:03:59 +01:00
|
|
|
// lightningAddrSchema is the scope addr schema for all keys that we
|
|
|
|
// derive. We'll treat them all as p2wkh addresses, as atm we must
|
|
|
|
// specify a particular type.
|
|
|
|
lightningAddrSchema = waddrmgr.ScopeAddrSchema{
|
|
|
|
ExternalAddrType: waddrmgr.WitnessPubKey,
|
|
|
|
InternalAddrType: waddrmgr.WitnessPubKey,
|
|
|
|
}
|
2021-02-20 02:41:45 +01:00
|
|
|
|
|
|
|
// errNoImportedAddrGen is an error returned when a new address is
|
|
|
|
// requested for the default imported account within the wallet.
|
|
|
|
errNoImportedAddrGen = errors.New("addresses cannot be generated for " +
|
|
|
|
"the default imported account")
|
2021-05-26 00:59:59 +02:00
|
|
|
|
2021-05-26 01:08:23 +02:00
|
|
|
// errIncompatibleAccountAddr is an error returned when the type of a
|
|
|
|
// new address being requested is incompatible with the account.
|
2021-05-26 00:59:59 +02:00
|
|
|
errIncompatibleAccountAddr = errors.New("incompatible address type " +
|
|
|
|
"for account")
|
2016-08-13 00:29:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// BtcWallet is an implementation of the lnwallet.WalletController interface
|
|
|
|
// backed by an active instance of btcwallet. At the time of the writing of
|
|
|
|
// this documentation, this implementation requires a full btcd node to
|
|
|
|
// operate.
|
|
|
|
type BtcWallet struct {
|
|
|
|
// wallet is an active instance of btcwallet.
|
|
|
|
wallet *base.Wallet
|
|
|
|
|
2017-05-25 02:34:03 +02:00
|
|
|
chain chain.Interface
|
2016-08-13 00:29:38 +02:00
|
|
|
|
2017-04-24 04:19:17 +02:00
|
|
|
db walletdb.DB
|
|
|
|
|
|
|
|
cfg *Config
|
2016-08-13 00:29:38 +02:00
|
|
|
|
|
|
|
netParams *chaincfg.Params
|
|
|
|
|
2018-03-13 01:33:11 +01:00
|
|
|
chainKeyScope waddrmgr.KeyScope
|
2021-03-18 13:08:53 +01:00
|
|
|
|
|
|
|
blockCache *blockcache.BlockCache
|
2022-04-27 22:20:33 +02:00
|
|
|
|
|
|
|
musig2Sessions map[input.MuSig2SessionID]*muSig2State
|
|
|
|
musig2SessionsMtx sync.Mutex
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// A compile time check to ensure that BtcWallet implements the
|
2018-08-24 15:30:23 +02:00
|
|
|
// WalletController and BlockChainIO interfaces.
|
2016-08-13 00:29:38 +02:00
|
|
|
var _ lnwallet.WalletController = (*BtcWallet)(nil)
|
2018-08-24 15:30:23 +02:00
|
|
|
var _ lnwallet.BlockChainIO = (*BtcWallet)(nil)
|
2016-08-13 00:29:38 +02:00
|
|
|
|
|
|
|
// New returns a new fully initialized instance of BtcWallet given a valid
|
2016-10-16 01:02:09 +02:00
|
|
|
// configuration struct.
|
2021-03-18 13:08:53 +01:00
|
|
|
func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
|
2018-03-13 01:33:11 +01:00
|
|
|
// Create the key scope for the coin type being managed by this wallet.
|
|
|
|
chainKeyScope := waddrmgr.KeyScope{
|
|
|
|
Purpose: keychain.BIP0043Purpose,
|
|
|
|
Coin: cfg.CoinType,
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:28:51 +02:00
|
|
|
// Maybe the wallet has already been opened and unlocked by the
|
|
|
|
// WalletUnlocker. So if we get a non-nil value from the config,
|
|
|
|
// we assume everything is in order.
|
|
|
|
var wallet = cfg.Wallet
|
|
|
|
if wallet == nil {
|
|
|
|
// No ready wallet was passed, so try to open an existing one.
|
|
|
|
var pubPass []byte
|
|
|
|
if cfg.PublicPass == nil {
|
|
|
|
pubPass = defaultPubPassphrase
|
|
|
|
} else {
|
|
|
|
pubPass = cfg.PublicPass
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
2021-03-04 23:13:06 +01:00
|
|
|
|
|
|
|
loader, err := NewWalletLoader(
|
|
|
|
cfg.NetParams, cfg.RecoveryWindow, cfg.LoaderOptions...,
|
2019-10-04 17:10:49 +02:00
|
|
|
)
|
2021-03-04 23:13:06 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-22 09:28:51 +02:00
|
|
|
walletExists, err := loader.WalletExists()
|
2018-02-18 00:03:59 +01:00
|
|
|
if err != nil {
|
2016-08-13 00:29:38 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-22 09:28:51 +02:00
|
|
|
|
|
|
|
if !walletExists {
|
|
|
|
// Wallet has never been created, perform initial
|
|
|
|
// set up.
|
|
|
|
wallet, err = loader.CreateNewWallet(
|
|
|
|
pubPass, cfg.PrivatePass, cfg.HdSeed,
|
|
|
|
cfg.Birthday,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Wallet has been created and been initialized at
|
|
|
|
// this point, open it along with all the required DB
|
|
|
|
// namespaces, and the DB itself.
|
|
|
|
wallet, err = loader.OpenExistingWallet(pubPass, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return &BtcWallet{
|
2022-04-27 22:20:33 +02:00
|
|
|
cfg: &cfg,
|
|
|
|
wallet: wallet,
|
|
|
|
db: wallet.Database(),
|
|
|
|
chain: cfg.ChainSource,
|
|
|
|
netParams: cfg.NetParams,
|
|
|
|
chainKeyScope: chainKeyScope,
|
|
|
|
blockCache: blockCache,
|
|
|
|
musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State),
|
2016-08-13 00:29:38 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-03-04 23:13:06 +01:00
|
|
|
// loaderCfg holds optional wallet loader configuration.
|
|
|
|
type loaderCfg struct {
|
|
|
|
dbDirPath string
|
|
|
|
noFreelistSync bool
|
|
|
|
dbTimeout time.Duration
|
|
|
|
useLocalDB bool
|
|
|
|
externalDB kvdb.Backend
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoaderOption is a functional option to update the optional loader config.
|
|
|
|
type LoaderOption func(*loaderCfg)
|
|
|
|
|
|
|
|
// LoaderWithLocalWalletDB configures the wallet loader to use the local db.
|
|
|
|
func LoaderWithLocalWalletDB(dbDirPath string, noFreelistSync bool,
|
|
|
|
dbTimeout time.Duration) LoaderOption {
|
|
|
|
|
|
|
|
return func(cfg *loaderCfg) {
|
|
|
|
cfg.dbDirPath = dbDirPath
|
|
|
|
cfg.noFreelistSync = noFreelistSync
|
|
|
|
cfg.dbTimeout = dbTimeout
|
|
|
|
cfg.useLocalDB = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoaderWithExternalWalletDB configures the wallet loadr to use an external db.
|
|
|
|
func LoaderWithExternalWalletDB(db kvdb.Backend) LoaderOption {
|
|
|
|
return func(cfg *loaderCfg) {
|
|
|
|
cfg.externalDB = db
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWalletLoader constructs a wallet loader.
|
|
|
|
func NewWalletLoader(chainParams *chaincfg.Params, recoveryWindow uint32,
|
2022-02-07 13:58:25 +01:00
|
|
|
opts ...LoaderOption) (*base.Loader, error) {
|
2021-03-04 23:13:06 +01:00
|
|
|
|
|
|
|
cfg := &loaderCfg{}
|
|
|
|
|
|
|
|
// Apply all functional options.
|
|
|
|
for _, o := range opts {
|
|
|
|
o(cfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.externalDB != nil && cfg.useLocalDB {
|
|
|
|
return nil, fmt.Errorf("wallet can either be in the local or " +
|
|
|
|
"an external db")
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.externalDB != nil {
|
|
|
|
loader, err := base.NewLoaderWithDB(
|
|
|
|
chainParams, recoveryWindow, cfg.externalDB,
|
|
|
|
func() (bool, error) {
|
|
|
|
return externalWalletExists(cfg.externalDB)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decorate wallet db with out own key such that we
|
|
|
|
// can always check whether the wallet exists or not.
|
|
|
|
loader.OnWalletCreated(onWalletCreated)
|
|
|
|
return loader, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return base.NewLoader(
|
|
|
|
chainParams, cfg.dbDirPath, cfg.noFreelistSync,
|
|
|
|
cfg.dbTimeout, recoveryWindow,
|
|
|
|
), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// externalWalletExists is a helper function that we use to template btcwallet's
|
|
|
|
// Loader in order to be able check if the wallet database has been initialized
|
|
|
|
// in an external DB.
|
|
|
|
func externalWalletExists(db kvdb.Backend) (bool, error) {
|
|
|
|
exists := false
|
|
|
|
err := kvdb.View(db, func(tx kvdb.RTx) error {
|
|
|
|
metaBucket := tx.ReadBucket([]byte(walletMetaBucket))
|
|
|
|
if metaBucket != nil {
|
|
|
|
walletReady := metaBucket.Get([]byte(walletReadyKey))
|
|
|
|
exists = string(walletReady) == walletReadyKey
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}, func() {})
|
|
|
|
|
|
|
|
return exists, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// onWalletCreated is executed when btcwallet creates the wallet the first time.
|
|
|
|
func onWalletCreated(tx kvdb.RwTx) error {
|
|
|
|
metaBucket, err := tx.CreateTopLevelBucket([]byte(walletMetaBucket))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return metaBucket.Put([]byte(walletReadyKey), []byte(walletReadyKey))
|
|
|
|
}
|
|
|
|
|
2017-11-10 01:30:20 +01:00
|
|
|
// BackEnd returns the underlying ChainService's name as a string.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) BackEnd() string {
|
|
|
|
if b.chain != nil {
|
|
|
|
return b.chain.BackEnd()
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-02-18 00:03:59 +01:00
|
|
|
// InternalWallet returns a pointer to the internal base wallet which is the
|
|
|
|
// core of btcwallet.
|
|
|
|
func (b *BtcWallet) InternalWallet() *base.Wallet {
|
|
|
|
return b.wallet
|
|
|
|
}
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
// Start initializes the underlying rpc connection, the wallet itself, and
|
|
|
|
// begins syncing to the current available blockchain state.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) Start() error {
|
2022-01-05 11:04:34 +01:00
|
|
|
// Is the wallet (according to its database) currently watch-only
|
|
|
|
// already? If it is, we won't need to convert it later.
|
|
|
|
walletIsWatchOnly := b.wallet.Manager.WatchOnly()
|
|
|
|
|
|
|
|
// If the wallet is watch-only, but we don't expect it to be, then we
|
|
|
|
// are in an unexpected state and cannot continue.
|
|
|
|
if walletIsWatchOnly && !b.cfg.WatchOnly {
|
|
|
|
return fmt.Errorf("wallet is watch-only but we expect it " +
|
|
|
|
"not to be; check if remote signing was disabled by " +
|
|
|
|
"accident")
|
|
|
|
}
|
|
|
|
|
2018-11-14 05:07:54 +01:00
|
|
|
// We'll start by unlocking the wallet and ensuring that the KeyScope:
|
|
|
|
// (1017, 1) exists within the internal waddrmgr. We'll need this in
|
|
|
|
// order to properly generate the keys required for signing various
|
2021-10-14 15:42:45 +02:00
|
|
|
// contracts. If this is a watch-only wallet, we don't have any private
|
|
|
|
// keys and therefore unlocking is not necessary.
|
2022-01-05 11:04:34 +01:00
|
|
|
if !walletIsWatchOnly {
|
2021-10-14 15:42:45 +02:00
|
|
|
if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-05 11:04:34 +01:00
|
|
|
|
|
|
|
// If the wallet isn't about to be converted, we need to inform
|
|
|
|
// the user that this wallet still contains all private key
|
|
|
|
// material and that they need to migrate the existing wallet.
|
|
|
|
if b.cfg.WatchOnly && !b.cfg.MigrateWatchOnly {
|
|
|
|
log.Warnf("Wallet is expected to be in watch-only " +
|
|
|
|
"mode but hasn't been migrated to watch-only " +
|
|
|
|
"yet, it still contains private keys; " +
|
|
|
|
"consider turning on the watch-only wallet " +
|
|
|
|
"migration in remote signing mode")
|
|
|
|
}
|
2017-04-24 04:17:54 +02:00
|
|
|
}
|
2021-10-14 15:42:45 +02:00
|
|
|
|
2022-03-29 17:52:25 +02:00
|
|
|
// Because we might add new "default" key scopes over time, they are
|
|
|
|
// created correctly for new wallets. Existing wallets don't
|
|
|
|
// automatically add them, we need to do that manually now.
|
|
|
|
for _, scope := range waddrmgr.DefaultKeyScopes {
|
|
|
|
_, err := b.wallet.Manager.FetchScopedKeyManager(scope)
|
|
|
|
if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) {
|
|
|
|
// The default scope wasn't found, that probably means
|
|
|
|
// it was added recently and older wallets don't know it
|
|
|
|
// yet. Let's add it now.
|
|
|
|
addrSchema := waddrmgr.ScopeAddrMap[scope]
|
|
|
|
err := walletdb.Update(
|
|
|
|
b.db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
addrmgrNs := tx.ReadWriteBucket(
|
|
|
|
waddrmgrNamespaceKey,
|
|
|
|
)
|
|
|
|
|
|
|
|
_, err := b.wallet.Manager.NewScopedKeyManager(
|
|
|
|
addrmgrNs, scope, addrSchema,
|
|
|
|
)
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 15:42:56 +02:00
|
|
|
scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
|
2018-02-18 00:03:59 +01:00
|
|
|
if err != nil {
|
|
|
|
// If the scope hasn't yet been created (it wouldn't been
|
|
|
|
// loaded by default if it was), then we'll manually create the
|
|
|
|
// scope for the first time ourselves.
|
|
|
|
err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
|
|
|
2021-10-14 15:42:56 +02:00
|
|
|
scope, err = b.wallet.Manager.NewScopedKeyManager(
|
2018-03-13 01:33:11 +01:00
|
|
|
addrmgrNs, b.chainKeyScope, lightningAddrSchema,
|
2018-02-18 00:03:59 +01:00
|
|
|
)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 15:42:56 +02:00
|
|
|
// Now that the wallet is unlocked, we'll go ahead and make sure we
|
|
|
|
// create accounts for all the key families we're going to use. This
|
|
|
|
// will make it possible to list all the account/family xpubs in the
|
|
|
|
// wallet list RPC.
|
|
|
|
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
|
|
|
2022-01-05 11:04:32 +01:00
|
|
|
// Generate all accounts that we could ever need. This includes
|
|
|
|
// all lnd key families as well as some key families used in
|
|
|
|
// external liquidity tools.
|
|
|
|
for keyFam := uint32(1); keyFam <= 255; keyFam++ {
|
2021-10-14 15:42:56 +02:00
|
|
|
// Otherwise, we'll check if the account already exists,
|
|
|
|
// if so, we can once again bail early.
|
2022-01-05 11:04:32 +01:00
|
|
|
_, err := scope.AccountName(addrmgrNs, keyFam)
|
2021-10-14 15:42:56 +02:00
|
|
|
if err == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we reach this point, then the account hasn't yet
|
|
|
|
// been created, so we'll need to create it before we
|
|
|
|
// can proceed.
|
2022-01-05 11:04:32 +01:00
|
|
|
err = scope.NewRawAccount(addrmgrNs, keyFam)
|
2021-10-14 15:42:56 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:34 +01:00
|
|
|
// If this is the first startup with remote signing and wallet
|
|
|
|
// migration turned on and the wallet wasn't previously
|
|
|
|
// migrated, we can do that now that we made sure all accounts
|
|
|
|
// that we need were derived correctly.
|
|
|
|
if !walletIsWatchOnly && b.cfg.WatchOnly &&
|
|
|
|
b.cfg.MigrateWatchOnly {
|
|
|
|
|
|
|
|
log.Infof("Migrating wallet to watch-only mode, " +
|
|
|
|
"purging all private key material")
|
|
|
|
|
|
|
|
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
|
|
err = b.wallet.Manager.ConvertToWatchingOnly(ns)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 15:42:56 +02:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-14 05:07:54 +01:00
|
|
|
// Establish an RPC connection in addition to starting the goroutines
|
|
|
|
// in the underlying wallet.
|
|
|
|
if err := b.chain.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the underlying btcwallet core.
|
|
|
|
b.wallet.Start()
|
|
|
|
|
|
|
|
// Pass the rpc client into the wallet so it can sync up to the
|
|
|
|
// current main chain.
|
|
|
|
b.wallet.SynchronizeRPC(b.chain)
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
|
|
|
// any active sockets, database handles, stopping goroutines, etc.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) Stop() error {
|
|
|
|
b.wallet.Stop()
|
|
|
|
|
|
|
|
b.wallet.WaitForShutdown()
|
|
|
|
|
2017-05-25 02:34:03 +02:00
|
|
|
b.chain.Stop()
|
2016-08-13 00:29:38 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
|
|
|
|
// have at least confs confirmations. If confs is set to zero, then all unspent
|
|
|
|
// outputs, including those currently in the mempool will be included in the
|
2021-02-20 02:41:50 +01:00
|
|
|
// final sum. The account parameter serves as a filter to retrieve the balance
|
|
|
|
// for a specific account. When empty, the confirmed balance of all wallet
|
|
|
|
// accounts is returned.
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
2021-02-20 02:41:50 +01:00
|
|
|
func (b *BtcWallet) ConfirmedBalance(confs int32,
|
|
|
|
accountFilter string) (btcutil.Amount, error) {
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
var balance btcutil.Amount
|
|
|
|
|
2021-02-20 02:41:50 +01:00
|
|
|
witnessOutputs, err := b.ListUnspentWitness(
|
|
|
|
confs, math.MaxInt32, accountFilter,
|
|
|
|
)
|
2018-02-18 00:36:53 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
2018-02-18 00:36:53 +01:00
|
|
|
for _, witnessOutput := range witnessOutputs {
|
|
|
|
balance += witnessOutput.Value
|
|
|
|
}
|
2018-02-18 00:03:59 +01:00
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
return balance, nil
|
|
|
|
}
|
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
// keyScopeForAccountAddr determines the appropriate key scope of an account
|
|
|
|
// based on its name/address type.
|
|
|
|
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
|
|
|
|
addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
|
|
|
|
|
|
|
|
// Map the requested address type to its key scope.
|
|
|
|
var addrKeyScope waddrmgr.KeyScope
|
|
|
|
switch addrType {
|
|
|
|
case lnwallet.WitnessPubKey:
|
|
|
|
addrKeyScope = waddrmgr.KeyScopeBIP0084
|
|
|
|
case lnwallet.NestedWitnessPubKey:
|
|
|
|
addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
|
2022-03-18 18:37:46 +01:00
|
|
|
case lnwallet.TaprootPubkey:
|
|
|
|
addrKeyScope = waddrmgr.KeyScopeBIP0086
|
2021-05-26 00:59:59 +02:00
|
|
|
default:
|
|
|
|
return waddrmgr.KeyScope{}, 0,
|
|
|
|
fmt.Errorf("unknown address type")
|
|
|
|
}
|
|
|
|
|
|
|
|
// The default account spans across multiple key scopes, so the
|
|
|
|
// requested address type should already be valid for this account.
|
|
|
|
if accountName == lnwallet.DefaultAccountName {
|
|
|
|
return addrKeyScope, defaultAccount, nil
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:08:23 +02:00
|
|
|
// Otherwise, look up the account's key scope and check that it supports
|
|
|
|
// the requested address type.
|
2021-05-26 00:59:59 +02:00
|
|
|
keyScope, account, err := b.wallet.LookupAccount(accountName)
|
|
|
|
if err != nil {
|
|
|
|
return waddrmgr.KeyScope{}, 0, err
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:08:23 +02:00
|
|
|
if keyScope != addrKeyScope {
|
|
|
|
return waddrmgr.KeyScope{}, 0, errIncompatibleAccountAddr
|
|
|
|
}
|
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
return keyScope, account, nil
|
|
|
|
}
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
// NewAddress returns the next external or internal address for the wallet
|
2017-11-24 05:02:33 +01:00
|
|
|
// dictated by the value of the `change` parameter. If change is true, then an
|
2016-08-13 00:29:38 +02:00
|
|
|
// internal address will be returned, otherwise an external address should be
|
2021-02-20 02:41:45 +01:00
|
|
|
// returned. The account parameter must be non-empty as it determines which
|
|
|
|
// account the address should be generated from.
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
2021-02-20 02:41:45 +01:00
|
|
|
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool,
|
|
|
|
accountName string) (btcutil.Address, error) {
|
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
// Addresses cannot be derived from the catch-all imported accounts.
|
|
|
|
if accountName == waddrmgr.ImportedAddrAccountName {
|
2021-02-20 02:41:45 +01:00
|
|
|
return nil, errNoImportedAddrGen
|
2021-05-26 00:59:59 +02:00
|
|
|
}
|
2021-02-20 02:41:45 +01:00
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if change {
|
2021-02-20 02:41:45 +01:00
|
|
|
return b.wallet.NewChangeAddress(account, keyScope)
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
2021-02-20 02:41:45 +01:00
|
|
|
return b.wallet.NewAddress(account, keyScope)
|
2019-02-20 04:16:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// LastUnusedAddress returns the last *unused* address known by the wallet. An
|
|
|
|
// address is unused if it hasn't received any payments. This can be useful in
|
|
|
|
// UIs in order to continually show the "freshest" address without having to
|
|
|
|
// worry about "address inflation" caused by continual refreshing. Similar to
|
|
|
|
// NewAddress it can derive a specified address type, and also optionally a
|
2021-02-20 02:41:45 +01:00
|
|
|
// change address. The account parameter must be non-empty as it determines
|
|
|
|
// which account the address should be generated from.
|
|
|
|
func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType,
|
|
|
|
accountName string) (btcutil.Address, error) {
|
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
// Addresses cannot be derived from the catch-all imported accounts.
|
|
|
|
if accountName == waddrmgr.ImportedAddrAccountName {
|
2021-02-20 02:41:45 +01:00
|
|
|
return nil, errNoImportedAddrGen
|
2021-05-26 00:59:59 +02:00
|
|
|
}
|
2021-02-20 02:41:45 +01:00
|
|
|
|
2021-05-26 00:59:59 +02:00
|
|
|
keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-02-20 04:16:39 +01:00
|
|
|
}
|
|
|
|
|
2021-02-20 02:41:45 +01:00
|
|
|
return b.wallet.CurrentAddress(account, keyScope)
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
2018-09-28 05:58:46 +02:00
|
|
|
// IsOurAddress checks if the passed address belongs to this wallet
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
2018-09-28 05:58:46 +02:00
|
|
|
func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool {
|
|
|
|
result, err := b.wallet.HaveAddress(a)
|
|
|
|
return result && (err == nil)
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
2022-03-16 15:11:13 +01:00
|
|
|
// AddressInfo returns the information about an address, if it's known to this
|
|
|
|
// wallet.
|
|
|
|
//
|
|
|
|
// NOTE: This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress,
|
|
|
|
error) {
|
|
|
|
|
|
|
|
return b.wallet.AddressInfo(a)
|
|
|
|
}
|
|
|
|
|
2021-02-20 02:42:02 +01:00
|
|
|
// ListAccounts retrieves all accounts belonging to the wallet by default. A
|
|
|
|
// name and key scope filter can be provided to filter through all of the wallet
|
|
|
|
// accounts and return only those matching.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) ListAccounts(name string,
|
|
|
|
keyScope *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
|
|
|
|
|
|
|
|
var res []*waddrmgr.AccountProperties
|
|
|
|
switch {
|
|
|
|
// If both the name and key scope filters were provided, we'll return
|
|
|
|
// the existing account matching those.
|
|
|
|
case name != "" && keyScope != nil:
|
|
|
|
account, err := b.wallet.AccountPropertiesByName(*keyScope, name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, account)
|
|
|
|
|
|
|
|
// Only the name filter was provided.
|
|
|
|
case name != "" && keyScope == nil:
|
|
|
|
// If the name corresponds to the default or imported accounts,
|
2022-03-18 18:37:46 +01:00
|
|
|
// we'll return them for all our supported key scopes.
|
2021-02-20 02:42:02 +01:00
|
|
|
if name == lnwallet.DefaultAccountName ||
|
|
|
|
name == waddrmgr.ImportedAddrAccountName {
|
|
|
|
|
|
|
|
a1, err := b.wallet.AccountPropertiesByName(
|
|
|
|
waddrmgr.KeyScopeBIP0049Plus, name,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, a1)
|
|
|
|
|
|
|
|
a2, err := b.wallet.AccountPropertiesByName(
|
|
|
|
waddrmgr.KeyScopeBIP0084, name,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, a2)
|
2022-03-18 18:37:46 +01:00
|
|
|
|
|
|
|
a3, err := b.wallet.AccountPropertiesByName(
|
|
|
|
waddrmgr.KeyScopeBIP0086, name,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, a3)
|
2021-02-20 02:42:02 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll retrieve the single account that's mapped by
|
|
|
|
// the given name.
|
|
|
|
scope, acctNum, err := b.wallet.LookupAccount(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
account, err := b.wallet.AccountProperties(scope, acctNum)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, account)
|
|
|
|
|
|
|
|
// Only the key scope filter was provided, so we'll return all accounts
|
|
|
|
// matching it.
|
|
|
|
case name == "" && keyScope != nil:
|
|
|
|
accounts, err := b.wallet.Accounts(*keyScope)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, account := range accounts.Accounts {
|
|
|
|
account := account
|
|
|
|
res = append(res, &account.AccountProperties)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Neither of the filters were provided, so return all accounts for our
|
|
|
|
// supported key scopes.
|
|
|
|
case name == "" && keyScope == nil:
|
|
|
|
accounts, err := b.wallet.Accounts(waddrmgr.KeyScopeBIP0049Plus)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, account := range accounts.Accounts {
|
|
|
|
account := account
|
|
|
|
res = append(res, &account.AccountProperties)
|
|
|
|
}
|
|
|
|
|
|
|
|
accounts, err = b.wallet.Accounts(waddrmgr.KeyScopeBIP0084)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, account := range accounts.Accounts {
|
|
|
|
account := account
|
|
|
|
res = append(res, &account.AccountProperties)
|
|
|
|
}
|
2021-10-14 15:42:56 +02:00
|
|
|
|
2022-03-18 18:37:46 +01:00
|
|
|
accounts, err = b.wallet.Accounts(waddrmgr.KeyScopeBIP0086)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, account := range accounts.Accounts {
|
|
|
|
account := account
|
|
|
|
res = append(res, &account.AccountProperties)
|
|
|
|
}
|
|
|
|
|
2021-10-14 15:42:56 +02:00
|
|
|
accounts, err = b.wallet.Accounts(waddrmgr.KeyScope{
|
|
|
|
Purpose: keychain.BIP0043Purpose,
|
|
|
|
Coin: b.cfg.CoinType,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, account := range accounts.Accounts {
|
|
|
|
account := account
|
|
|
|
res = append(res, &account.AccountProperties)
|
|
|
|
}
|
2021-02-20 02:42:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2022-06-17 17:37:17 +02:00
|
|
|
// RequiredReserve returns the minimum amount of satoshis that should be
|
|
|
|
// kept in the wallet in order to fee bump anchor channels if necessary.
|
|
|
|
// The value scales with the number of public anchor channels but is
|
|
|
|
// capped at a maximum.
|
|
|
|
func (b *BtcWallet) RequiredReserve(
|
|
|
|
numAnchorChans uint32) btcutil.Amount {
|
|
|
|
|
|
|
|
anchorChanReservedValue := lnwallet.AnchorChanReservedValue
|
|
|
|
reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
|
|
|
|
if reserved > lnwallet.MaxAnchorChanReservedValue {
|
|
|
|
reserved = lnwallet.MaxAnchorChanReservedValue
|
|
|
|
}
|
|
|
|
|
|
|
|
return reserved
|
|
|
|
}
|
|
|
|
|
2021-02-20 02:42:02 +01:00
|
|
|
// ImportAccount imports an account backed by an account extended public key.
|
|
|
|
// The master key fingerprint denotes the fingerprint of the root key
|
|
|
|
// corresponding to the account public key (also known as the key with
|
|
|
|
// derivation path m/). This may be required by some hardware wallets for proper
|
|
|
|
// identification and signing.
|
|
|
|
//
|
|
|
|
// The address type can usually be inferred from the key's version, but may be
|
|
|
|
// required for certain keys to map them into the proper scope.
|
|
|
|
//
|
|
|
|
// For BIP-0044 keys, an address type must be specified as we intend to not
|
|
|
|
// support importing BIP-0044 keys into the wallet using the legacy
|
|
|
|
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
|
|
|
|
// the standard BIP-0049 derivation scheme, while a witness address type will
|
|
|
|
// force the standard BIP-0084 derivation scheme.
|
|
|
|
//
|
|
|
|
// For BIP-0049 keys, an address type must also be specified to make a
|
|
|
|
// distinction between the standard BIP-0049 address schema (nested witness
|
|
|
|
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
|
|
|
|
// externally, witness pubkeys internally).
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
|
2021-05-05 00:56:26 +02:00
|
|
|
masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
|
|
|
|
dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
|
|
|
|
[]btcutil.Address, error) {
|
2021-02-20 02:42:02 +01:00
|
|
|
|
2021-05-05 00:56:26 +02:00
|
|
|
if !dryRun {
|
|
|
|
accountProps, err := b.wallet.ImportAccount(
|
|
|
|
name, accountPubKey, masterKeyFingerprint, addrType,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
return accountProps, nil, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Derive addresses from both the external and internal branches of the
|
|
|
|
// account. There's no risk of address inflation as this is only done
|
|
|
|
// for dry runs.
|
|
|
|
accountProps, extAddrs, intAddrs, err := b.wallet.ImportAccountDryRun(
|
2021-02-20 02:42:02 +01:00
|
|
|
name, accountPubKey, masterKeyFingerprint, addrType,
|
2021-05-05 00:56:26 +02:00
|
|
|
dryRunImportAccountNumAddrs,
|
2021-02-20 02:42:02 +01:00
|
|
|
)
|
2021-05-05 00:56:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
externalAddrs := make([]btcutil.Address, len(extAddrs))
|
|
|
|
for i := 0; i < len(extAddrs); i++ {
|
|
|
|
externalAddrs[i] = extAddrs[i].Address()
|
|
|
|
}
|
|
|
|
|
|
|
|
internalAddrs := make([]btcutil.Address, len(intAddrs))
|
|
|
|
for i := 0; i < len(intAddrs); i++ {
|
|
|
|
internalAddrs[i] = intAddrs[i].Address()
|
|
|
|
}
|
|
|
|
|
|
|
|
return accountProps, externalAddrs, internalAddrs, nil
|
2021-02-20 02:42:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// ImportPublicKey imports a single derived public key into the wallet. The
|
|
|
|
// address type can usually be inferred from the key's version, but in the case
|
|
|
|
// of legacy versions (xpub, tpub), an address type must be specified as we
|
|
|
|
// intend to not support importing BIP-44 keys into the wallet using the legacy
|
|
|
|
// pay-to-pubkey-hash (P2PKH) scheme.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey,
|
|
|
|
addrType waddrmgr.AddressType) error {
|
|
|
|
|
|
|
|
return b.wallet.ImportPublicKey(pubKey, addrType)
|
|
|
|
}
|
|
|
|
|
2022-03-18 18:37:47 +01:00
|
|
|
// ImportTaprootScript imports a user-provided taproot script into the address
|
|
|
|
// manager. The imported script will act as a pay-to-taproot address.
|
|
|
|
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
|
|
|
|
tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
|
|
|
|
|
|
|
|
// We want to be able to import script addresses into a watch-only
|
|
|
|
// wallet, which is only possible if we don't encrypt the script with
|
|
|
|
// the private key encryption key. By specifying the script as being
|
|
|
|
// "not secret", we can also decrypt the script in a watch-only wallet.
|
|
|
|
const isSecretScript = false
|
|
|
|
|
|
|
|
// Currently, only v1 (Taproot) scripts are supported. We don't even
|
|
|
|
// know what a v2 witness version would look like at this point.
|
|
|
|
const witnessVersionTaproot byte = 1
|
|
|
|
|
|
|
|
return b.wallet.ImportTaprootScript(
|
|
|
|
scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
|
|
|
|
// the specified outputs. In the case the wallet has insufficient funds, or the
|
2018-04-18 04:03:27 +02:00
|
|
|
// outputs are non-standard, a non-nil error will be returned.
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
2020-06-12 20:22:00 +02:00
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
|
|
//
|
2016-08-13 00:29:38 +02:00
|
|
|
// This is a part of the WalletController interface.
|
2017-11-23 07:29:32 +01:00
|
|
|
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
|
2021-10-14 15:42:52 +02:00
|
|
|
feeRate chainfee.SatPerKWeight, minConfs int32,
|
|
|
|
label string) (*wire.MsgTx, error) {
|
2017-11-23 07:29:32 +01:00
|
|
|
|
2018-07-28 03:20:58 +02:00
|
|
|
// Convert our fee rate from sat/kw to sat/kb since it's required by
|
|
|
|
// SendOutputs.
|
|
|
|
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
2017-11-23 07:29:32 +01:00
|
|
|
|
2019-03-05 14:22:30 +01:00
|
|
|
// Sanity check outputs.
|
|
|
|
if len(outputs) < 1 {
|
|
|
|
return nil, lnwallet.ErrNoOutputs
|
|
|
|
}
|
2020-05-18 14:13:24 +02:00
|
|
|
|
2021-04-22 19:04:00 +02:00
|
|
|
// Sanity check minConfs.
|
|
|
|
if minConfs < 0 {
|
2020-09-27 17:48:15 +02:00
|
|
|
return nil, lnwallet.ErrInvalidMinconf
|
|
|
|
}
|
|
|
|
|
2020-05-18 14:13:24 +02:00
|
|
|
return b.wallet.SendOutputs(
|
2021-03-29 15:23:46 +02:00
|
|
|
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
|
|
|
|
b.cfg.CoinSelectionStrategy, label,
|
2020-05-18 14:13:24 +02:00
|
|
|
)
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
|
2019-03-05 14:22:30 +01:00
|
|
|
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
|
|
|
|
// outputs. The transaction is not broadcasted to the network, but a new change
|
|
|
|
// address might be created in the wallet database. In the case the wallet has
|
|
|
|
// insufficient funds, or the outputs are non-standard, an error should be
|
|
|
|
// returned. This method also takes the target fee expressed in sat/kw that
|
|
|
|
// should be used when crafting the transaction.
|
|
|
|
//
|
|
|
|
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
|
|
|
|
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
|
|
|
|
//
|
2020-06-12 20:22:00 +02:00
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
|
|
//
|
2019-03-05 14:22:30 +01:00
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
|
2021-04-22 19:25:50 +02:00
|
|
|
feeRate chainfee.SatPerKWeight, minConfs int32,
|
|
|
|
dryRun bool) (*txauthor.AuthoredTx, error) {
|
2019-03-05 14:22:30 +01:00
|
|
|
|
|
|
|
// The fee rate is passed in using units of sat/kw, so we'll convert
|
|
|
|
// this to sat/KB as the CreateSimpleTx method requires this unit.
|
|
|
|
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
|
|
|
|
|
|
|
// Sanity check outputs.
|
2019-03-05 14:22:30 +01:00
|
|
|
if len(outputs) < 1 {
|
|
|
|
return nil, lnwallet.ErrNoOutputs
|
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
|
|
|
|
// Sanity check minConfs.
|
|
|
|
if minConfs < 0 {
|
|
|
|
return nil, lnwallet.ErrInvalidMinconf
|
|
|
|
}
|
|
|
|
|
2019-03-05 14:22:30 +01:00
|
|
|
for _, output := range outputs {
|
2019-06-19 02:04:59 +02:00
|
|
|
// When checking an output for things like dusty-ness, we'll
|
|
|
|
// use the default mempool relay fee rather than the target
|
|
|
|
// effective fee rate to ensure accuracy. Otherwise, we may
|
|
|
|
// mistakenly mark small-ish, but not quite dust output as
|
|
|
|
// dust.
|
|
|
|
err := txrules.CheckOutput(
|
|
|
|
output, txrules.DefaultRelayFeePerKb,
|
|
|
|
)
|
2019-03-05 14:22:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 23:12:26 +01:00
|
|
|
return b.wallet.CreateSimpleTx(
|
2021-03-29 15:23:46 +02:00
|
|
|
nil, defaultAccount, outputs, minConfs, feeSatPerKB,
|
|
|
|
b.cfg.CoinSelectionStrategy, dryRun,
|
2021-03-09 23:12:26 +01:00
|
|
|
)
|
2019-03-05 14:22:30 +01:00
|
|
|
}
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
|
|
|
|
// as eligible for coin selection. Locking outputs are utilized in order to
|
|
|
|
// avoid race conditions when selecting inputs for usage when funding a
|
|
|
|
// channel.
|
|
|
|
//
|
2020-06-12 20:22:00 +02:00
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
|
|
//
|
2016-08-13 00:29:38 +02:00
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) LockOutpoint(o wire.OutPoint) {
|
|
|
|
b.wallet.LockOutpoint(o)
|
|
|
|
}
|
|
|
|
|
2018-04-18 04:02:04 +02:00
|
|
|
// UnlockOutpoint unlocks a previously locked output, marking it eligible for
|
2017-12-18 03:40:05 +01:00
|
|
|
// coin selection.
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
2020-06-12 20:22:00 +02:00
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
|
|
//
|
2016-08-13 00:29:38 +02:00
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) UnlockOutpoint(o wire.OutPoint) {
|
|
|
|
b.wallet.UnlockOutpoint(o)
|
|
|
|
}
|
|
|
|
|
2020-05-21 00:33:56 +02:00
|
|
|
// LeaseOutput locks an output to the given ID, preventing it from being
|
|
|
|
// available for any future coin selection attempts. The absolute time of the
|
|
|
|
// lock's expiration is returned. The expiration of the lock can be extended by
|
|
|
|
// successive invocations of this call. Outputs can be unlocked before their
|
|
|
|
// expiration through `ReleaseOutput`.
|
|
|
|
//
|
|
|
|
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
|
|
|
|
// output has already been locked to a different ID, then
|
|
|
|
// wtxmgr.ErrOutputAlreadyLocked is returned.
|
2020-06-12 20:22:00 +02:00
|
|
|
//
|
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
2021-03-12 08:45:12 +01:00
|
|
|
func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
|
2022-05-12 10:24:40 +02:00
|
|
|
duration time.Duration) (time.Time, []byte, btcutil.Amount, error) {
|
2020-06-10 22:15:38 +02:00
|
|
|
|
|
|
|
// Make sure we don't attempt to double lock an output that's been
|
|
|
|
// locked by the in-memory implementation.
|
|
|
|
if b.wallet.LockedOutpoint(op) {
|
2022-05-12 10:24:40 +02:00
|
|
|
return time.Time{}, nil, 0, wtxmgr.ErrOutputAlreadyLocked
|
2020-06-10 22:15:38 +02:00
|
|
|
}
|
|
|
|
|
2022-05-12 10:24:40 +02:00
|
|
|
lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
|
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, nil, 0, err
|
|
|
|
}
|
2020-05-21 00:33:56 +02:00
|
|
|
|
2022-05-12 10:24:40 +02:00
|
|
|
// Get the pkScript and value for this lock from the list of all leased
|
|
|
|
// outputs.
|
|
|
|
allLeases, err := b.wallet.ListLeasedOutputs()
|
2022-04-28 15:31:59 +02:00
|
|
|
if err != nil {
|
2022-05-12 10:24:40 +02:00
|
|
|
return time.Time{}, nil, 0, err
|
2022-04-28 15:31:59 +02:00
|
|
|
}
|
|
|
|
|
2022-05-12 10:24:40 +02:00
|
|
|
for _, lease := range allLeases {
|
|
|
|
if lease.Outpoint == op {
|
|
|
|
return lockedUntil, lease.PkScript,
|
|
|
|
btcutil.Amount(lease.Value), nil
|
|
|
|
}
|
2022-04-28 15:31:59 +02:00
|
|
|
}
|
|
|
|
|
2022-05-12 10:24:40 +02:00
|
|
|
// We MUST find the leased output in the loop above, otherwise something
|
|
|
|
// is seriously wrong.
|
|
|
|
return time.Time{}, nil, 0, wtxmgr.ErrUnknownOutput
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListLeasedOutputs returns a list of all currently locked outputs.
|
|
|
|
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
|
|
|
|
error) {
|
|
|
|
|
|
|
|
return b.wallet.ListLeasedOutputs()
|
2021-03-12 09:26:24 +01:00
|
|
|
}
|
|
|
|
|
2020-05-21 00:33:56 +02:00
|
|
|
// ReleaseOutput unlocks an output, allowing it to be available for coin
|
|
|
|
// selection if it remains unspent. The ID should match the one used to
|
|
|
|
// originally lock the output.
|
2020-06-12 20:22:00 +02:00
|
|
|
//
|
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
2020-05-21 00:33:56 +02:00
|
|
|
func (b *BtcWallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error {
|
|
|
|
return b.wallet.ReleaseOutput(id, op)
|
|
|
|
}
|
|
|
|
|
2021-02-20 02:41:50 +01:00
|
|
|
// ListUnspentWitness returns all unspent outputs which are version 0 witness
|
2021-04-22 19:04:00 +02:00
|
|
|
// programs. The 'minConfs' and 'maxConfs' parameters indicate the minimum
|
2021-02-20 02:41:50 +01:00
|
|
|
// and maximum number of confirmations an output needs in order to be returned
|
2021-04-22 19:04:00 +02:00
|
|
|
// by this method. Passing -1 as 'minConfs' indicates that even unconfirmed
|
|
|
|
// outputs should be returned. Using MaxInt32 as 'maxConfs' implies returning
|
|
|
|
// all outputs with at least 'minConfs'. The account parameter serves as a
|
2021-02-20 02:41:50 +01:00
|
|
|
// filter to retrieve the unspent outputs for a specific account. When empty,
|
|
|
|
// the unspent outputs of all wallet accounts are returned.
|
2016-08-13 00:29:38 +02:00
|
|
|
//
|
2020-06-12 20:22:00 +02:00
|
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
|
|
//
|
2016-08-13 00:29:38 +02:00
|
|
|
// This is a part of the WalletController interface.
|
2021-02-20 02:41:50 +01:00
|
|
|
func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32,
|
|
|
|
accountFilter string) ([]*lnwallet.Utxo, error) {
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
// First, grab all the unfiltered currently unspent outputs.
|
2021-02-20 02:41:50 +01:00
|
|
|
unspentOutputs, err := b.wallet.ListUnspent(
|
|
|
|
minConfs, maxConfs, accountFilter,
|
|
|
|
)
|
2016-08-13 00:29:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, we'll run through all the regular outputs, only saving those
|
|
|
|
// which are p2wkh outputs or a p2wsh output nested within a p2sh output.
|
|
|
|
witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
|
|
|
|
for _, output := range unspentOutputs {
|
|
|
|
pkScript, err := hex.DecodeString(output.ScriptPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-10 14:22:17 +02:00
|
|
|
addressType := lnwallet.UnknownAddressType
|
2017-10-03 03:52:45 +02:00
|
|
|
if txscript.IsPayToWitnessPubKeyHash(pkScript) {
|
|
|
|
addressType = lnwallet.WitnessPubKey
|
|
|
|
} else if txscript.IsPayToScriptHash(pkScript) {
|
|
|
|
// TODO(roasbeef): This assumes all p2sh outputs returned by the
|
|
|
|
// wallet are nested p2pkh. We can't check the redeem script because
|
|
|
|
// the btcwallet service does not include it.
|
|
|
|
addressType = lnwallet.NestedWitnessPubKey
|
2022-03-18 18:37:46 +01:00
|
|
|
} else if txscript.IsPayToTaproot(pkScript) {
|
|
|
|
addressType = lnwallet.TaprootPubkey
|
2017-10-03 03:52:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if addressType == lnwallet.WitnessPubKey ||
|
2022-03-18 18:37:46 +01:00
|
|
|
addressType == lnwallet.NestedWitnessPubKey ||
|
|
|
|
addressType == lnwallet.TaprootPubkey {
|
2017-10-03 03:52:45 +02:00
|
|
|
|
2017-01-05 22:56:27 +01:00
|
|
|
txid, err := chainhash.NewHashFromStr(output.TxID)
|
2016-08-13 00:29:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-03-26 04:15:05 +02:00
|
|
|
// We'll ensure we properly convert the amount given in
|
|
|
|
// BTC to satoshis.
|
|
|
|
amt, err := btcutil.NewAmount(output.Amount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-13 00:29:38 +02:00
|
|
|
utxo := &lnwallet.Utxo{
|
2017-10-03 03:52:45 +02:00
|
|
|
AddressType: addressType,
|
2018-03-26 04:15:05 +02:00
|
|
|
Value: amt,
|
2017-10-03 03:52:45 +02:00
|
|
|
PkScript: pkScript,
|
2016-08-13 00:29:38 +02:00
|
|
|
OutPoint: wire.OutPoint{
|
|
|
|
Hash: *txid,
|
|
|
|
Index: output.Vout,
|
|
|
|
},
|
2018-09-27 15:49:44 +02:00
|
|
|
Confirmations: output.Confirmations,
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
|
|
|
witnessOutputs = append(witnessOutputs, utxo)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return witnessOutputs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PublishTransaction performs cursory validation (dust checks, etc), then
|
2018-01-15 23:27:27 +01:00
|
|
|
// finally broadcasts the passed transaction to the Bitcoin network. If
|
2019-02-21 00:06:21 +01:00
|
|
|
// publishing the transaction fails, an error describing the reason is returned
|
|
|
|
// (currently ErrDoubleSpend). If the transaction is already published to the
|
|
|
|
// network (either in the mempool or chain) no error will be returned.
|
2020-05-18 14:13:23 +02:00
|
|
|
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
|
|
|
|
if err := b.wallet.PublishTransaction(tx, label); err != nil {
|
2019-09-19 14:59:07 +02:00
|
|
|
// If we failed to publish the transaction, check whether we
|
|
|
|
// got an error of known type.
|
|
|
|
switch err.(type) {
|
|
|
|
// If the wallet reports a double spend, convert it to our
|
|
|
|
// internal ErrDoubleSpend and return.
|
|
|
|
case *base.ErrDoubleSpend:
|
|
|
|
return lnwallet.ErrDoubleSpend
|
|
|
|
|
|
|
|
// If the wallet reports a replacement error, return
|
|
|
|
// ErrDoubleSpend, as we currently are never attempting to
|
|
|
|
// replace transactions.
|
|
|
|
case *base.ErrReplacement:
|
|
|
|
return lnwallet.ErrDoubleSpend
|
2018-01-15 23:27:27 +01:00
|
|
|
|
|
|
|
default:
|
2019-09-19 14:59:07 +02:00
|
|
|
return err
|
2018-01-15 23:27:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2016-08-13 00:29:38 +02:00
|
|
|
}
|
2016-10-15 05:15:50 +02:00
|
|
|
|
2020-05-25 08:38:05 +02:00
|
|
|
// LabelTransaction adds a label to a transaction. If the tx already
|
|
|
|
// has a label, this call will fail unless the overwrite parameter
|
|
|
|
// is set. Labels must not be empty, and they are limited to 500 chars.
|
|
|
|
//
|
|
|
|
// Note: it is part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) LabelTransaction(hash chainhash.Hash, label string,
|
|
|
|
overwrite bool) error {
|
|
|
|
|
|
|
|
return b.wallet.LabelTransaction(hash, label, overwrite)
|
|
|
|
}
|
|
|
|
|
2016-10-15 05:15:50 +02:00
|
|
|
// extractBalanceDelta extracts the net balance delta from the PoV of the
|
|
|
|
// wallet given a TransactionSummary.
|
2017-12-06 18:19:37 +01:00
|
|
|
func extractBalanceDelta(
|
|
|
|
txSummary base.TransactionSummary,
|
|
|
|
tx *wire.MsgTx,
|
|
|
|
) (btcutil.Amount, error) {
|
2016-10-15 05:15:50 +02:00
|
|
|
// For each input we debit the wallet's outflow for this transaction,
|
|
|
|
// and for each output we credit the wallet's inflow for this
|
|
|
|
// transaction.
|
|
|
|
var balanceDelta btcutil.Amount
|
|
|
|
for _, input := range txSummary.MyInputs {
|
|
|
|
balanceDelta -= input.PreviousAmount
|
|
|
|
}
|
|
|
|
for _, output := range txSummary.MyOutputs {
|
|
|
|
balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
return balanceDelta, nil
|
|
|
|
}
|
|
|
|
|
2022-06-27 12:15:49 +02:00
|
|
|
// getPreviousOutpoints is a helper function which gets the previous
|
|
|
|
// outpoints of a transaction.
|
|
|
|
func getPreviousOutpoints(wireTx *wire.MsgTx,
|
|
|
|
myInputs []wallet.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
|
|
|
|
|
|
|
|
// isOurOutput is a map containing the output indices
|
|
|
|
// controlled by the wallet.
|
|
|
|
// Note: We make use of the information in `myInputs` provided
|
|
|
|
// by the `wallet.TransactionSummary` structure that holds
|
|
|
|
// information only if the input/previous_output is controlled by the wallet.
|
|
|
|
isOurOutput := make(map[uint32]bool, len(myInputs))
|
|
|
|
for _, myInput := range myInputs {
|
|
|
|
isOurOutput[myInput.Index] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
|
|
|
|
for idx, txIn := range wireTx.TxIn {
|
|
|
|
previousOutpoints[idx] = lnwallet.PreviousOutPoint{
|
|
|
|
OutPoint: txIn.PreviousOutPoint.String(),
|
|
|
|
IsOurOutput: isOurOutput[uint32(idx)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return previousOutpoints
|
|
|
|
}
|
|
|
|
|
2016-10-15 05:15:50 +02:00
|
|
|
// minedTransactionsToDetails is a helper function which converts a summary
|
|
|
|
// information about mined transactions to a TransactionDetail.
|
2017-12-06 18:19:37 +01:00
|
|
|
func minedTransactionsToDetails(
|
|
|
|
currentHeight int32,
|
|
|
|
block base.Block,
|
|
|
|
chainParams *chaincfg.Params,
|
|
|
|
) ([]*lnwallet.TransactionDetail, error) {
|
2016-10-15 05:15:50 +02:00
|
|
|
|
|
|
|
details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
|
|
|
|
for _, tx := range block.Transactions {
|
2017-12-06 18:19:37 +01:00
|
|
|
wireTx := &wire.MsgTx{}
|
|
|
|
txReader := bytes.NewReader(tx.Transaction)
|
|
|
|
|
|
|
|
if err := wireTx.Deserialize(txReader); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 17:57:53 +02:00
|
|
|
// isOurAddress is a map containing the output indices
|
|
|
|
// controlled by the wallet.
|
|
|
|
// Note: We make use of the information in `MyOutputs` provided
|
|
|
|
// by the `wallet.TransactionSummary` structure that holds
|
|
|
|
// information only if the output is controlled by the wallet.
|
|
|
|
isOurAddress := make(map[int]bool, len(tx.MyOutputs))
|
|
|
|
for _, o := range tx.MyOutputs {
|
|
|
|
isOurAddress[int(o.Index)] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
var outputDetails []lnwallet.OutputDetail
|
|
|
|
for i, txOut := range wireTx.TxOut {
|
|
|
|
var addresses []btcutil.Address
|
|
|
|
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
|
2019-06-07 16:36:20 +02:00
|
|
|
txOut.PkScript, chainParams,
|
|
|
|
)
|
2021-07-09 17:57:53 +02:00
|
|
|
if err == nil {
|
|
|
|
// Add supported addresses.
|
|
|
|
addresses = outAddresses
|
2017-12-06 18:19:37 +01:00
|
|
|
}
|
|
|
|
|
2021-07-09 17:57:53 +02:00
|
|
|
outputDetails = append(outputDetails, lnwallet.OutputDetail{
|
|
|
|
OutputType: sc,
|
|
|
|
Addresses: addresses,
|
|
|
|
PkScript: txOut.PkScript,
|
|
|
|
OutputIndex: i,
|
|
|
|
Value: btcutil.Amount(txOut.Value),
|
|
|
|
IsOurAddress: isOurAddress[i],
|
|
|
|
})
|
2017-12-06 18:19:37 +01:00
|
|
|
}
|
|
|
|
|
2022-06-27 12:15:49 +02:00
|
|
|
previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
|
|
|
|
|
2016-10-15 05:15:50 +02:00
|
|
|
txDetail := &lnwallet.TransactionDetail{
|
2022-06-27 12:15:49 +02:00
|
|
|
Hash: *tx.Hash,
|
|
|
|
NumConfirmations: currentHeight - block.Height + 1,
|
|
|
|
BlockHash: block.Hash,
|
|
|
|
BlockHeight: block.Height,
|
|
|
|
Timestamp: block.Timestamp,
|
|
|
|
TotalFees: int64(tx.Fee),
|
|
|
|
OutputDetails: outputDetails,
|
|
|
|
RawTx: tx.Transaction,
|
|
|
|
Label: tx.Label,
|
|
|
|
PreviousOutpoints: previousOutpoints,
|
2016-10-15 05:15:50 +02:00
|
|
|
}
|
|
|
|
|
2017-12-06 18:19:37 +01:00
|
|
|
balanceDelta, err := extractBalanceDelta(tx, wireTx)
|
2016-10-15 05:15:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txDetail.Value = balanceDelta
|
|
|
|
|
|
|
|
details = append(details, txDetail)
|
|
|
|
}
|
|
|
|
|
|
|
|
return details, nil
|
|
|
|
}
|
|
|
|
|
2017-12-06 18:19:37 +01:00
|
|
|
// unminedTransactionsToDetail is a helper function which converts a summary
|
2018-04-18 04:02:04 +02:00
|
|
|
// for an unconfirmed transaction to a transaction detail.
|
2017-12-06 18:19:37 +01:00
|
|
|
func unminedTransactionsToDetail(
|
|
|
|
summary base.TransactionSummary,
|
2019-07-10 08:38:27 +02:00
|
|
|
chainParams *chaincfg.Params,
|
2017-12-06 18:19:37 +01:00
|
|
|
) (*lnwallet.TransactionDetail, error) {
|
2019-06-13 22:54:33 +02:00
|
|
|
|
2017-12-06 18:19:37 +01:00
|
|
|
wireTx := &wire.MsgTx{}
|
|
|
|
txReader := bytes.NewReader(summary.Transaction)
|
|
|
|
|
|
|
|
if err := wireTx.Deserialize(txReader); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-09 17:57:53 +02:00
|
|
|
// isOurAddress is a map containing the output indices controlled by
|
|
|
|
// the wallet.
|
|
|
|
// Note: We make use of the information in `MyOutputs` provided
|
|
|
|
// by the `wallet.TransactionSummary` structure that holds information
|
|
|
|
// only if the output is controlled by the wallet.
|
|
|
|
isOurAddress := make(map[int]bool, len(summary.MyOutputs))
|
|
|
|
for _, o := range summary.MyOutputs {
|
|
|
|
isOurAddress[int(o.Index)] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
var outputDetails []lnwallet.OutputDetail
|
|
|
|
for i, txOut := range wireTx.TxOut {
|
|
|
|
var addresses []btcutil.Address
|
|
|
|
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
|
|
|
|
txOut.PkScript, chainParams,
|
|
|
|
)
|
|
|
|
if err == nil {
|
|
|
|
// Add supported addresses.
|
|
|
|
addresses = outAddresses
|
2019-07-10 08:38:27 +02:00
|
|
|
}
|
|
|
|
|
2021-07-09 17:57:53 +02:00
|
|
|
outputDetails = append(outputDetails, lnwallet.OutputDetail{
|
|
|
|
OutputType: sc,
|
|
|
|
Addresses: addresses,
|
|
|
|
PkScript: txOut.PkScript,
|
|
|
|
OutputIndex: i,
|
|
|
|
Value: btcutil.Amount(txOut.Value),
|
|
|
|
IsOurAddress: isOurAddress[i],
|
|
|
|
})
|
2019-07-10 08:38:27 +02:00
|
|
|
}
|
|
|
|
|
2022-06-27 12:15:49 +02:00
|
|
|
previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
|
|
|
|
|
2016-10-15 05:15:50 +02:00
|
|
|
txDetail := &lnwallet.TransactionDetail{
|
2022-06-27 12:15:49 +02:00
|
|
|
Hash: *summary.Hash,
|
|
|
|
TotalFees: int64(summary.Fee),
|
|
|
|
Timestamp: summary.Timestamp,
|
|
|
|
OutputDetails: outputDetails,
|
|
|
|
RawTx: summary.Transaction,
|
|
|
|
Label: summary.Label,
|
|
|
|
PreviousOutpoints: previousOutpoints,
|
2016-10-15 05:15:50 +02:00
|
|
|
}
|
|
|
|
|
2017-12-06 18:19:37 +01:00
|
|
|
balanceDelta, err := extractBalanceDelta(summary, wireTx)
|
2016-10-15 05:15:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txDetail.Value = balanceDelta
|
|
|
|
|
|
|
|
return txDetail, nil
|
|
|
|
}
|
|
|
|
|
2021-02-20 02:41:50 +01:00
|
|
|
// ListTransactionDetails returns a list of all transactions which are relevant
|
|
|
|
// to the wallet over [startHeight;endHeight]. If start height is greater than
|
|
|
|
// end height, the transactions will be retrieved in reverse order. To include
|
|
|
|
// unconfirmed transactions, endHeight should be set to the special value -1.
|
|
|
|
// This will return transactions from the tip of the chain until the start
|
|
|
|
// height (inclusive) and unconfirmed transactions. The account parameter serves
|
|
|
|
// as a filter to retrieve the transactions relevant to a specific account. When
|
|
|
|
// empty, transactions of all wallet accounts are returned.
|
2016-10-15 05:15:50 +02:00
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
2021-02-20 02:41:50 +01:00
|
|
|
func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
|
|
|
|
accountFilter string) ([]*lnwallet.TransactionDetail, error) {
|
2020-05-05 21:10:06 +02:00
|
|
|
|
2016-10-15 05:15:50 +02:00
|
|
|
// Grab the best block the wallet knows of, we'll use this to calculate
|
|
|
|
// # of confirmations shortly below.
|
|
|
|
bestBlock := b.wallet.Manager.SyncedTo()
|
|
|
|
currentHeight := bestBlock.Height
|
|
|
|
|
2020-05-05 21:10:06 +02:00
|
|
|
// We'll attempt to find all transactions from start to end height.
|
|
|
|
start := base.NewBlockIdentifierFromHeight(startHeight)
|
|
|
|
stop := base.NewBlockIdentifierFromHeight(endHeight)
|
2021-02-20 02:41:50 +01:00
|
|
|
txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
|
2016-10-15 05:15:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails := make([]*lnwallet.TransactionDetail, 0,
|
|
|
|
len(txns.MinedTransactions)+len(txns.UnminedTransactions))
|
|
|
|
|
2017-06-08 02:01:17 +02:00
|
|
|
// For both confirmed and unconfirmed transactions, create a
|
2016-10-15 05:15:50 +02:00
|
|
|
// TransactionDetail which re-packages the data returned by the base
|
|
|
|
// wallet.
|
|
|
|
for _, blockPackage := range txns.MinedTransactions {
|
2019-06-07 16:36:20 +02:00
|
|
|
details, err := minedTransactionsToDetails(
|
|
|
|
currentHeight, blockPackage, b.netParams,
|
|
|
|
)
|
2016-10-15 05:15:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails = append(txDetails, details...)
|
|
|
|
}
|
|
|
|
for _, tx := range txns.UnminedTransactions {
|
2019-07-10 08:38:27 +02:00
|
|
|
detail, err := unminedTransactionsToDetail(tx, b.netParams)
|
2016-10-15 05:15:50 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txDetails = append(txDetails, detail)
|
|
|
|
}
|
|
|
|
|
|
|
|
return txDetails, nil
|
|
|
|
}
|
2016-10-15 23:11:18 +02:00
|
|
|
|
|
|
|
// txSubscriptionClient encapsulates the transaction notification client from
|
|
|
|
// the base wallet. Notifications received from the client will be proxied over
|
|
|
|
// two distinct channels.
|
|
|
|
type txSubscriptionClient struct {
|
|
|
|
txClient base.TransactionNotificationsClient
|
|
|
|
|
|
|
|
confirmed chan *lnwallet.TransactionDetail
|
|
|
|
unconfirmed chan *lnwallet.TransactionDetail
|
|
|
|
|
|
|
|
w *base.Wallet
|
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
quit chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfirmedTransactions returns a channel which will be sent on as new
|
|
|
|
// relevant transactions are confirmed.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
|
|
|
|
return t.confirmed
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnconfirmedTransactions returns a channel which will be sent on as
|
|
|
|
// new relevant transactions are seen within the network.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
|
|
|
|
return t.unconfirmed
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel finalizes the subscription, cleaning up any resources allocated.
|
|
|
|
//
|
|
|
|
// This is part of the TransactionSubscription interface.
|
|
|
|
func (t *txSubscriptionClient) Cancel() {
|
|
|
|
close(t.quit)
|
|
|
|
t.wg.Wait()
|
|
|
|
|
|
|
|
t.txClient.Done()
|
|
|
|
}
|
|
|
|
|
|
|
|
// notificationProxier proxies the notifications received by the underlying
|
|
|
|
// wallet's notification client to a higher-level TransactionSubscription
|
|
|
|
// client.
|
|
|
|
func (t *txSubscriptionClient) notificationProxier() {
|
2019-07-09 17:26:27 +02:00
|
|
|
defer t.wg.Done()
|
|
|
|
|
2016-10-15 23:11:18 +02:00
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case txNtfn := <-t.txClient.C:
|
|
|
|
// TODO(roasbeef): handle detached blocks
|
|
|
|
currentHeight := t.w.Manager.SyncedTo().Height
|
|
|
|
|
|
|
|
// Launch a goroutine to re-package and send
|
|
|
|
// notifications for any newly confirmed transactions.
|
|
|
|
go func() {
|
|
|
|
for _, block := range txNtfn.AttachedBlocks {
|
2017-12-06 18:19:37 +01:00
|
|
|
details, err := minedTransactionsToDetails(currentHeight, block, t.w.ChainParams())
|
2016-10-15 23:11:18 +02:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range details {
|
|
|
|
select {
|
|
|
|
case t.confirmed <- d:
|
|
|
|
case <-t.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Launch a goroutine to re-package and send
|
|
|
|
// notifications for any newly unconfirmed transactions.
|
|
|
|
go func() {
|
|
|
|
for _, tx := range txNtfn.UnminedTransactions {
|
2019-07-10 08:38:27 +02:00
|
|
|
detail, err := unminedTransactionsToDetail(
|
|
|
|
tx, t.w.ChainParams(),
|
|
|
|
)
|
2016-10-15 23:11:18 +02:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case t.unconfirmed <- detail:
|
|
|
|
case <-t.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
case <-t.quit:
|
|
|
|
break out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SubscribeTransactions returns a TransactionSubscription client which
|
|
|
|
// is capable of receiving async notifications as new transactions
|
|
|
|
// related to the wallet are seen within the network, or found in
|
|
|
|
// blocks.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
|
|
|
|
walletClient := b.wallet.NtfnServer.TransactionNotifications()
|
|
|
|
|
|
|
|
txClient := &txSubscriptionClient{
|
|
|
|
txClient: walletClient,
|
|
|
|
confirmed: make(chan *lnwallet.TransactionDetail),
|
|
|
|
unconfirmed: make(chan *lnwallet.TransactionDetail),
|
|
|
|
w: b.wallet,
|
|
|
|
quit: make(chan struct{}),
|
|
|
|
}
|
|
|
|
txClient.wg.Add(1)
|
|
|
|
go txClient.notificationProxier()
|
|
|
|
|
|
|
|
return txClient, nil
|
|
|
|
}
|
2016-12-09 03:29:55 +01:00
|
|
|
|
2019-06-13 22:54:33 +02:00
|
|
|
// IsSynced returns a boolean indicating if from the PoV of the wallet, it has
|
|
|
|
// fully synced to the current best block in the main chain.
|
2016-12-09 03:29:55 +01:00
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
2017-12-10 08:42:46 +01:00
|
|
|
func (b *BtcWallet) IsSynced() (bool, int64, error) {
|
2018-03-06 19:17:09 +01:00
|
|
|
// Grab the best chain state the wallet is currently aware of.
|
2016-12-09 03:29:55 +01:00
|
|
|
syncState := b.wallet.Manager.SyncedTo()
|
|
|
|
|
2018-03-06 19:17:09 +01:00
|
|
|
// We'll also extract the current best wallet timestamp so the caller
|
|
|
|
// can get an idea of where we are in the sync timeline.
|
|
|
|
bestTimestamp := syncState.Timestamp.Unix()
|
2017-05-25 02:34:31 +02:00
|
|
|
|
|
|
|
// Next, query the chain backend to grab the info about the tip of the
|
|
|
|
// main chain.
|
2018-03-06 19:17:09 +01:00
|
|
|
bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
|
2017-11-10 01:30:20 +01:00
|
|
|
if err != nil {
|
2017-12-10 08:42:46 +01:00
|
|
|
return false, 0, err
|
2016-12-09 03:29:55 +01:00
|
|
|
}
|
|
|
|
|
2020-10-19 22:57:30 +02:00
|
|
|
// Make sure the backing chain has been considered synced first.
|
|
|
|
if !b.wallet.ChainSynced() {
|
|
|
|
bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
|
|
|
|
if err != nil {
|
|
|
|
return false, 0, err
|
|
|
|
}
|
|
|
|
bestTimestamp = bestHeader.Timestamp.Unix()
|
|
|
|
return false, bestTimestamp, nil
|
|
|
|
}
|
|
|
|
|
2016-12-09 03:29:55 +01:00
|
|
|
// If the wallet hasn't yet fully synced to the node's best chain tip,
|
|
|
|
// then we're not yet fully synced.
|
2020-10-19 22:57:30 +02:00
|
|
|
if syncState.Height < bestHeight {
|
2018-03-06 19:17:09 +01:00
|
|
|
return false, bestTimestamp, nil
|
2016-12-09 03:29:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the wallet is on par with the current best chain tip, then we
|
2017-05-25 02:34:31 +02:00
|
|
|
// still may not yet be synced as the chain backend may still be
|
|
|
|
// catching up to the main chain. So we'll grab the block header in
|
|
|
|
// order to make a guess based on the current time stamp.
|
2017-11-10 01:30:20 +01:00
|
|
|
blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
|
|
|
|
if err != nil {
|
2017-12-10 08:42:46 +01:00
|
|
|
return false, 0, err
|
2016-12-09 03:29:55 +01:00
|
|
|
}
|
|
|
|
|
2018-07-26 04:33:46 +02:00
|
|
|
// If the timestamp on the best header is more than 2 hours in the
|
2016-12-09 03:29:55 +01:00
|
|
|
// past, then we're not yet synced.
|
|
|
|
minus24Hours := time.Now().Add(-2 * time.Hour)
|
2018-07-26 04:33:46 +02:00
|
|
|
if blockHeader.Timestamp.Before(minus24Hours) {
|
|
|
|
return false, bestTimestamp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, bestTimestamp, nil
|
2016-12-09 03:29:55 +01:00
|
|
|
}
|
2020-06-09 08:47:17 +02:00
|
|
|
|
|
|
|
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
|
|
|
|
// in recovery mode. It also returns a float64, ranging from 0 to 1,
|
|
|
|
// representing the recovery progress made so far.
|
|
|
|
//
|
|
|
|
// This is a part of the WalletController interface.
|
|
|
|
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
|
|
|
|
isRecoveryMode := true
|
|
|
|
progress := float64(0)
|
|
|
|
|
|
|
|
// A zero value in RecoveryWindow indicates there is no trigger of
|
|
|
|
// recovery mode.
|
|
|
|
if b.cfg.RecoveryWindow == 0 {
|
|
|
|
isRecoveryMode = false
|
|
|
|
return isRecoveryMode, progress, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query the wallet's birthday block height from db.
|
|
|
|
var birthdayBlock waddrmgr.BlockStamp
|
|
|
|
err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
|
|
var err error
|
|
|
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
|
|
|
birthdayBlock, _, err = b.wallet.Manager.BirthdayBlock(addrmgrNs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// The wallet won't start until the backend is synced, thus the birthday
|
|
|
|
// block won't be set and this particular error will be returned. We'll
|
|
|
|
// catch this error and return a progress of 0 instead.
|
|
|
|
if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
|
|
|
|
return isRecoveryMode, progress, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return isRecoveryMode, progress, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab the best chain state the wallet is currently aware of.
|
|
|
|
syncState := b.wallet.Manager.SyncedTo()
|
|
|
|
|
|
|
|
// Next, query the chain backend to grab the info about the tip of the
|
|
|
|
// main chain.
|
|
|
|
//
|
|
|
|
// NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
|
|
|
|
// The process purposefully doesn't update the best height. It might create
|
|
|
|
// a small difference between the height queried here and the height used
|
|
|
|
// in the recovery process, ie, the bestHeight used here might be greater,
|
|
|
|
// showing the recovery being unfinished while it's actually done. However,
|
|
|
|
// during a wallet rescan after the recovery, the wallet's synced height
|
|
|
|
// will catch up and this won't be an issue.
|
|
|
|
_, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
|
|
|
|
if err != nil {
|
|
|
|
return isRecoveryMode, progress, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The birthday block height might be greater than the current synced height
|
|
|
|
// in a newly restored wallet, and might be greater than the chain tip if a
|
|
|
|
// rollback happens. In that case, we will return zero progress here.
|
|
|
|
if syncState.Height < birthdayBlock.Height ||
|
|
|
|
bestHeight < birthdayBlock.Height {
|
2022-02-07 13:58:28 +01:00
|
|
|
|
2020-06-09 08:47:17 +02:00
|
|
|
return isRecoveryMode, progress, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// progress is the ratio of the [number of blocks processed] over the [total
|
|
|
|
// number of blocks] needed in a recovery mode, ranging from 0 to 1, in
|
|
|
|
// which,
|
|
|
|
// - total number of blocks is the current chain's best height minus the
|
|
|
|
// wallet's birthday height plus 1.
|
|
|
|
// - number of blocks processed is the wallet's synced height minus its
|
|
|
|
// birthday height plus 1.
|
|
|
|
// - If the wallet is born very recently, the bestHeight can be equal to
|
|
|
|
// the birthdayBlock.Height, and it will recovery instantly.
|
|
|
|
progress = float64(syncState.Height-birthdayBlock.Height+1) /
|
|
|
|
float64(bestHeight-birthdayBlock.Height+1)
|
|
|
|
|
|
|
|
return isRecoveryMode, progress, nil
|
|
|
|
}
|
sweep: remove all unconfirmed descendant transactions when a sweep conflicts
Before this commit, we we were trying to sweep an anchor output, and
that output was spent by someone else (not the sweeper), then we would
report this back to the original resolver (allowing it to be cleaned
up), and also remove the set of inputs spent by that transaction from
the set we need to sweep.
However, it's possible that if a user is spending unconfirmed outputs,
then the wallet is holding onto an invalid transaction, as the outputs
that were used as inputs have been double spent elsewhere.
In this commit, we fix this issue by recursively removing all descendant
transactions of our past sweeps that have an intersecting input set as
the spending transaction. In cases where a user spent an unconfirmed
output to funding a channel, and that output was a descendant of the now
swept anchor output, the funds will now properly be marked as available.
Fixes #6241
2022-02-18 03:25:30 +01:00
|
|
|
|
|
|
|
// FetchTx attempts to fetch a transaction in the wallet's database identified
|
|
|
|
// by the passed transaction hash. If the transaction can't be found, then a
|
|
|
|
// nil pointer is returned.
|
|
|
|
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
|
|
|
|
var targetTx *wtxmgr.TxDetails
|
|
|
|
err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
|
|
wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
|
|
|
|
txDetails, err := b.wallet.TxStore.TxDetails(wtxmgrNs, &txHash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
targetTx = txDetails
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if targetTx == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &targetTx.TxRecord.MsgTx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveDescendants attempts to remove any transaction from the wallet's tx
|
|
|
|
// store (that may be unconfirmed) that spends outputs created by the passed
|
|
|
|
// transaction. This remove propagates recursively down the chain of descendent
|
|
|
|
// transactions.
|
|
|
|
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
|
|
|
|
txRecord, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
|
|
wtxmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
|
|
|
|
return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
|
|
|
|
})
|
|
|
|
}
|