2017-01-17 01:19:02 +01:00
|
|
|
// Copyright (c) 2016 The Decred developers
|
|
|
|
// Copyright (c) 2017 The btcsuite developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package wallet
|
|
|
|
|
|
|
|
import (
|
2020-08-27 21:14:57 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
2021-08-26 00:58:28 +02:00
|
|
|
"github.com/lbryio/lbcd/txscript"
|
|
|
|
"github.com/lbryio/lbcd/wire"
|
|
|
|
"github.com/lbryio/lbcutil/hdkeychain"
|
|
|
|
"github.com/lbryio/lbcutil/psbt"
|
|
|
|
"github.com/lbryio/lbcwallet/waddrmgr"
|
|
|
|
"github.com/lbryio/lbcwallet/walletdb"
|
2017-01-17 01:19:02 +01:00
|
|
|
)
|
|
|
|
|
2020-08-27 21:14:57 +02:00
|
|
|
var (
|
|
|
|
// ErrNotMine is an error denoting that a Wallet instance is unable to
|
|
|
|
// spend a specified output.
|
|
|
|
ErrNotMine = errors.New("the passed output does not belong to the " +
|
|
|
|
"wallet")
|
|
|
|
)
|
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
// OutputSelectionPolicy describes the rules for selecting an output from the
|
|
|
|
// wallet.
|
|
|
|
type OutputSelectionPolicy struct {
|
|
|
|
Account uint32
|
|
|
|
RequiredConfirmations int32
|
2021-11-23 20:04:18 +01:00
|
|
|
IncludeStakes bool
|
2017-01-17 01:19:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *OutputSelectionPolicy) meetsRequiredConfs(txHeight, curHeight int32) bool {
|
|
|
|
return confirmed(p.RequiredConfirmations, txHeight, curHeight)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnspentOutputs fetches all unspent outputs from the wallet that match rules
|
|
|
|
// described in the passed policy.
|
|
|
|
func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) {
|
|
|
|
var outputResults []*TransactionOutput
|
|
|
|
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
|
|
|
|
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
|
|
|
txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
|
|
|
|
|
|
|
|
syncBlock := w.Manager.SyncedTo()
|
|
|
|
|
|
|
|
// TODO: actually stream outputs from the db instead of fetching
|
|
|
|
// all of them at once.
|
|
|
|
outputs, err := w.TxStore.UnspentOutputs(txmgrNs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, output := range outputs {
|
|
|
|
// Ignore outputs that haven't reached the required
|
|
|
|
// number of confirmations.
|
|
|
|
if !policy.meetsRequiredConfs(output.Height, syncBlock.Height) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore outputs that are not controlled by the account.
|
|
|
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript,
|
|
|
|
w.chainParams)
|
|
|
|
if err != nil || len(addrs) == 0 {
|
|
|
|
// Cannot determine which account this belongs
|
|
|
|
// to without a valid address. TODO: Fix this
|
|
|
|
// by saving outputs per account, or accounts
|
|
|
|
// per output.
|
|
|
|
continue
|
|
|
|
}
|
2018-02-14 06:36:39 +01:00
|
|
|
_, outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0])
|
2017-01-17 01:19:02 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if outputAcct != policy.Account {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stakebase isn't exposed by wtxmgr so those will be
|
|
|
|
// OutputKindNormal for now.
|
|
|
|
outputSource := OutputKindNormal
|
|
|
|
if output.FromCoinBase {
|
|
|
|
outputSource = OutputKindCoinbase
|
|
|
|
}
|
|
|
|
|
2021-11-23 20:04:18 +01:00
|
|
|
if isStake(output.PkScript) {
|
|
|
|
if !policy.IncludeStakes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
outputSource = OutputKindStake
|
|
|
|
}
|
|
|
|
|
2017-01-17 01:19:02 +01:00
|
|
|
result := &TransactionOutput{
|
|
|
|
OutPoint: output.OutPoint,
|
|
|
|
Output: wire.TxOut{
|
|
|
|
Value: int64(output.Amount),
|
|
|
|
PkScript: output.PkScript,
|
|
|
|
},
|
|
|
|
OutputKind: outputSource,
|
|
|
|
ContainingBlock: BlockIdentity(output.Block),
|
|
|
|
ReceiveTime: output.Received,
|
|
|
|
}
|
|
|
|
outputResults = append(outputResults, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return outputResults, err
|
|
|
|
}
|
2020-08-27 21:14:57 +02:00
|
|
|
|
|
|
|
// FetchInputInfo queries for the wallet's knowledge of the passed outpoint. If
|
|
|
|
// the wallet determines this output is under its control, then the original
|
|
|
|
// full transaction, the target txout and the number of confirmations are
|
|
|
|
// returned. Otherwise, a non-nil error value of ErrNotMine is returned instead.
|
|
|
|
func (w *Wallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
|
2021-02-18 01:24:08 +01:00
|
|
|
*wire.TxOut, *psbt.Bip32Derivation, int64, error) {
|
2020-08-27 21:14:57 +02:00
|
|
|
|
|
|
|
// We manually look up the output within the tx store.
|
|
|
|
txid := &prevOut.Hash
|
|
|
|
txDetail, err := UnstableAPI(w).TxDetails(txid)
|
|
|
|
if err != nil {
|
2021-02-18 01:24:08 +01:00
|
|
|
return nil, nil, nil, 0, err
|
2020-08-27 21:14:57 +02:00
|
|
|
} else if txDetail == nil {
|
2021-02-18 01:24:08 +01:00
|
|
|
return nil, nil, nil, 0, ErrNotMine
|
2020-08-27 21:14:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// With the output retrieved, we'll make an additional check to ensure
|
|
|
|
// we actually have control of this output. We do this because the check
|
|
|
|
// above only guarantees that the transaction is somehow relevant to us,
|
|
|
|
// like in the event of us being the sender of the transaction.
|
|
|
|
numOutputs := uint32(len(txDetail.TxRecord.MsgTx.TxOut))
|
|
|
|
if prevOut.Index >= numOutputs {
|
2021-02-18 01:24:08 +01:00
|
|
|
return nil, nil, nil, 0, fmt.Errorf("invalid output index %v for "+
|
2020-08-27 21:14:57 +02:00
|
|
|
"transaction with %v outputs", prevOut.Index,
|
|
|
|
numOutputs)
|
|
|
|
}
|
|
|
|
pkScript := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].PkScript
|
2021-02-18 01:24:08 +01:00
|
|
|
addr, err := w.fetchOutputAddr(pkScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, 0, err
|
|
|
|
}
|
|
|
|
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, 0, err
|
2020-08-27 21:14:57 +02:00
|
|
|
}
|
2021-02-18 01:24:08 +01:00
|
|
|
keyScope, derivationPath, _ := pubKeyAddr.DerivationInfo()
|
2020-08-27 21:14:57 +02:00
|
|
|
|
|
|
|
// Determine the number of confirmations the output currently has.
|
|
|
|
_, currentHeight, err := w.chainClient.GetBestBlock()
|
|
|
|
if err != nil {
|
2021-02-18 01:24:08 +01:00
|
|
|
return nil, nil, nil, 0, fmt.Errorf("unable to retrieve current "+
|
2020-08-27 21:14:57 +02:00
|
|
|
"height: %v", err)
|
|
|
|
}
|
|
|
|
confs := int64(0)
|
|
|
|
if txDetail.Block.Height != -1 {
|
|
|
|
confs = int64(currentHeight - txDetail.Block.Height)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &txDetail.TxRecord.MsgTx, &wire.TxOut{
|
2021-02-18 01:24:08 +01:00
|
|
|
Value: txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].Value,
|
|
|
|
PkScript: pkScript,
|
|
|
|
}, &psbt.Bip32Derivation{
|
|
|
|
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
|
2021-02-18 01:24:11 +01:00
|
|
|
MasterKeyFingerprint: derivationPath.MasterKeyFingerprint,
|
2021-02-18 01:24:08 +01:00
|
|
|
Bip32Path: []uint32{
|
|
|
|
keyScope.Purpose + hdkeychain.HardenedKeyStart,
|
|
|
|
keyScope.Coin + hdkeychain.HardenedKeyStart,
|
|
|
|
derivationPath.Account,
|
|
|
|
derivationPath.Branch,
|
|
|
|
derivationPath.Index,
|
|
|
|
},
|
|
|
|
}, confs, nil
|
2020-08-27 21:14:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// fetchOutputAddr attempts to fetch the managed address corresponding to the
|
|
|
|
// passed output script. This function is used to look up the proper key which
|
|
|
|
// should be used to sign a specified input.
|
|
|
|
func (w *Wallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
|
|
|
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, w.chainParams)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the case of a multi-sig output, several address may be extracted.
|
|
|
|
// Therefore, we simply select the key for the first address we know
|
|
|
|
// of.
|
|
|
|
for _, addr := range addrs {
|
|
|
|
addr, err := w.AddressInfo(addr)
|
|
|
|
if err == nil {
|
|
|
|
return addr, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, ErrNotMine
|
|
|
|
}
|