wallet: add FetchInputInfo

This commit is contained in:
Oliver Gugger 2020-08-27 21:14:57 +02:00
parent 4aa36af74c
commit 1f2ed87055
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 142 additions and 0 deletions

View file

@ -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
View 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)
}
}