wallet: add FetchInputInfo
This commit is contained in:
parent
4aa36af74c
commit
1f2ed87055
2 changed files with 142 additions and 0 deletions
|
@ -6,11 +6,22 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
// OutputSelectionPolicy describes the rules for selecting an output from the
|
||||
// wallet.
|
||||
type OutputSelectionPolicy struct {
|
||||
|
@ -88,3 +99,73 @@ func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOut
|
|||
})
|
||||
return outputResults, err
|
||||
}
|
||||
|
||||
// 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,
|
||||
*wire.TxOut, int64, error) {
|
||||
|
||||
// We manually look up the output within the tx store.
|
||||
txid := &prevOut.Hash
|
||||
txDetail, err := UnstableAPI(w).TxDetails(txid)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
} else if txDetail == nil {
|
||||
return nil, nil, 0, ErrNotMine
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, nil, 0, fmt.Errorf("invalid output index %v for "+
|
||||
"transaction with %v outputs", prevOut.Index,
|
||||
numOutputs)
|
||||
}
|
||||
pkScript := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].PkScript
|
||||
if _, err := w.fetchOutputAddr(pkScript); err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
// Determine the number of confirmations the output currently has.
|
||||
_, currentHeight, err := w.chainClient.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, nil, 0, fmt.Errorf("unable to retrieve current "+
|
||||
"height: %v", err)
|
||||
}
|
||||
confs := int64(0)
|
||||
if txDetail.Block.Height != -1 {
|
||||
confs = int64(currentHeight - txDetail.Block.Height)
|
||||
}
|
||||
|
||||
return &txDetail.TxRecord.MsgTx, &wire.TxOut{
|
||||
Value: txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].Value,
|
||||
PkScript: pkScript,
|
||||
}, confs, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
61
wallet/utxos_test.go
Normal file
61
wallet/utxos_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2020 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
)
|
||||
|
||||
// TestFetchInputInfo checks that the wallet can gather information about an
|
||||
// output based on the address.
|
||||
func TestFetchInputInfo(t *testing.T) {
|
||||
w, cleanup := testWallet(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create an address we can use to send some coins to.
|
||||
addr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0084)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current address: %v", addr)
|
||||
}
|
||||
p2shAddr, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to convert wallet address to p2sh: %v", err)
|
||||
}
|
||||
|
||||
// Add an output paying to the wallet's address to the database.
|
||||
utxOut := wire.NewTxOut(100000, p2shAddr)
|
||||
incomingTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{{}},
|
||||
TxOut: []*wire.TxOut{utxOut},
|
||||
}
|
||||
addUtxo(t, w, incomingTx)
|
||||
|
||||
// Look up the UTXO for the outpoint now and compare it to our
|
||||
// expectations.
|
||||
prevOut := &wire.OutPoint{
|
||||
Hash: incomingTx.TxHash(),
|
||||
Index: 0,
|
||||
}
|
||||
tx, out, confirmations, err := w.FetchInputInfo(prevOut)
|
||||
if err != nil {
|
||||
t.Fatalf("error fetching input info: %v", err)
|
||||
}
|
||||
if !bytes.Equal(out.PkScript, utxOut.PkScript) || out.Value != utxOut.Value {
|
||||
t.Fatalf("unexpected TX out, got %v wanted %v", out, utxOut)
|
||||
}
|
||||
if !bytes.Equal(tx.TxOut[prevOut.Index].PkScript, utxOut.PkScript) {
|
||||
t.Fatalf("unexpected TX out, got %v wanted %v",
|
||||
tx.TxOut[prevOut.Index].PkScript, utxOut)
|
||||
}
|
||||
if confirmations != int64(0-testBlockHeight) {
|
||||
t.Fatalf("unexpected number of confirmations, got %d wanted %d",
|
||||
confirmations, 0-testBlockHeight)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue