Skip to content

Commit

Permalink
Merge pull request #945 from yyforyongyu/fetch-input-info
Browse files Browse the repository at this point in the history
wallet: break `FetchInputInfo` into two methods
  • Loading branch information
guggero authored Aug 9, 2024
2 parents db3a4a2 + 91de583 commit 7d3434c
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 45 deletions.
127 changes: 82 additions & 45 deletions wallet/utxos.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,86 +104,123 @@ func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOut

// 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.
// full transaction, the target txout, the derivation info and the number of
// confirmations are returned. Otherwise, a non-nil error value of ErrNotMine
// is returned instead.
//
// NOTE: This method is kept for compatibility.
func (w *Wallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
*wire.TxOut, *psbt.Bip32Derivation, int64, error) {

tx, txOut, confs, err := w.FetchOutpointInfo(prevOut)
if err != nil {
return nil, nil, nil, 0, err
}

derivation, err := w.FetchDerivationInfo(txOut.PkScript)
if err != nil {
return nil, nil, nil, 0, err
}

return tx, txOut, derivation, 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
}

// FetchOutpointInfo queries for the wallet's knowledge of the passed outpoint.
// If the wallet determines this output is under its control, 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) FetchOutpointInfo(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, nil, 0, err
return nil, nil, 0, err
} else if txDetail == nil {
return nil, nil, nil, 0, ErrNotMine
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.
// 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, nil, 0, fmt.Errorf("invalid output index %v for "+
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
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, ErrNotMine
}
keyScope, derivationPath, _ := pubKeyAddr.DerivationInfo()

// Determine the number of confirmations the output currently has.
_, currentHeight, err := w.chainClient.GetBestBlock()
if err != nil {
return nil, nil, nil, 0, fmt.Errorf("unable to retrieve current "+
return nil, nil, 0, fmt.Errorf("unable to retrieve current "+
"height: %w", 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,
}, &psbt.Bip32Derivation{
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
MasterKeyFingerprint: derivationPath.MasterKeyFingerprint,
Bip32Path: []uint32{
keyScope.Purpose + hdkeychain.HardenedKeyStart,
keyScope.Coin + hdkeychain.HardenedKeyStart,
derivationPath.Account,
derivationPath.Branch,
derivationPath.Index,
},
}, confs, nil
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)
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (w *Wallet) FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation,
error) {

addr, err := w.fetchOutputAddr(pkScript)
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
}
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return nil, ErrNotMine
}
keyScope, derivationPath, _ := pubKeyAddr.DerivationInfo()

return nil, ErrNotMine
derivation := &psbt.Bip32Derivation{
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
MasterKeyFingerprint: derivationPath.MasterKeyFingerprint,
Bip32Path: []uint32{
keyScope.Purpose + hdkeychain.HardenedKeyStart,
keyScope.Coin + hdkeychain.HardenedKeyStart,
derivationPath.Account,
derivationPath.Branch,
derivationPath.Index,
},
}

return derivation, nil
}
73 changes: 73 additions & 0 deletions wallet/utxos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/stretchr/testify/require"
)

// TestFetchInputInfo checks that the wallet can gather information about an
Expand Down Expand Up @@ -90,3 +91,75 @@ func TestFetchInputInfo(t *testing.T) {
confirmations, 0-testBlockHeight)
}
}

// TestFetchOutpointInfo checks that the wallet can gather information about an
// output based on the outpoint.
func TestFetchOutpointInfo(t *testing.T) {
t.Parallel()

w, cleanup := testWallet(t)
defer cleanup()

// Create an address we can use to send some coins to.
addr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0084)
require.NoError(t, err)
p2shAddr, err := txscript.PayToAddrScript(addr)
require.NoError(t, 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.FetchOutpointInfo(prevOut)
require.NoError(t, err)

require.Equal(t, utxOut.PkScript, out.PkScript)
require.Equal(t, utxOut.Value, out.Value)
require.Equal(t, utxOut.PkScript, tx.TxOut[prevOut.Index].PkScript)
require.Equal(t, int64(0-testBlockHeight), confirmations)
}

// TestFetchDerivationInfo checks that the wallet can gather the derivation
// info about an output based on the pkScript.
func TestFetchDerivationInfo(t *testing.T) {
t.Parallel()

w, cleanup := testWallet(t)
defer cleanup()

// Create an address we can use to send some coins to.
addr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0084)
require.NoError(t, err)
p2shAddr, err := txscript.PayToAddrScript(addr)
require.NoError(t, 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)

info, err := w.FetchDerivationInfo(utxOut.PkScript)
require.NoError(t, err)

require.Len(t, info.Bip32Path, 5)
require.Equal(t, waddrmgr.KeyScopeBIP0084.Purpose+
hdkeychain.HardenedKeyStart, info.Bip32Path[0])
require.Equal(t, waddrmgr.KeyScopeBIP0084.Coin+
hdkeychain.HardenedKeyStart, info.Bip32Path[1])
require.EqualValues(t, hdkeychain.HardenedKeyStart, info.Bip32Path[2])
require.Equal(t, uint32(0), info.Bip32Path[3])
require.Equal(t, uint32(0), info.Bip32Path[4])
}

0 comments on commit 7d3434c

Please sign in to comment.