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
|
package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
"github.com/btcsuite/btcwallet/walletdb"
|
"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
|
// OutputSelectionPolicy describes the rules for selecting an output from the
|
||||||
// wallet.
|
// wallet.
|
||||||
type OutputSelectionPolicy struct {
|
type OutputSelectionPolicy struct {
|
||||||
|
@ -88,3 +99,73 @@ func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOut
|
||||||
})
|
})
|
||||||
return outputResults, err
|
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