Skip to content

Commit 584a33f

Browse files
committed
wallet+wtxmgr: check credit in FetchOutpointInfo
To decide whether an outpoint belongs to the wallet or not, we need to further check that the specified output index is indeed a credit paid to us.
1 parent b26f4ec commit 584a33f

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

wallet/createtx_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,47 @@ func addUtxo(t *testing.T, w *Wallet, incomingTx *wire.MsgTx) {
213213
}
214214
}
215215

216+
// addTxAndCredit adds the given transaction to the wallet's database marked as
217+
// a confirmed UTXO specified by the creditIndex.
218+
func addTxAndCredit(t *testing.T, w *Wallet, tx *wire.MsgTx,
219+
creditIndex uint32) {
220+
221+
var b bytes.Buffer
222+
require.NoError(t, tx.Serialize(&b), "unable to serialize tx")
223+
224+
txBytes := b.Bytes()
225+
226+
rec, err := wtxmgr.NewTxRecord(txBytes, time.Now())
227+
require.NoError(t, err)
228+
229+
// The block meta will be inserted to tell the wallet this is a
230+
// confirmed transaction.
231+
block := &wtxmgr.BlockMeta{
232+
Block: wtxmgr.Block{
233+
Hash: *testBlockHash,
234+
Height: testBlockHeight,
235+
},
236+
Time: time.Unix(1387737310, 0),
237+
}
238+
239+
err = walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error {
240+
ns := dbTx.ReadWriteBucket(wtxmgrNamespaceKey)
241+
err = w.TxStore.InsertTx(ns, rec, block)
242+
if err != nil {
243+
return err
244+
}
245+
246+
// Add the specified output as credit.
247+
err = w.TxStore.AddCredit(ns, rec, block, creditIndex, false)
248+
if err != nil {
249+
return err
250+
}
251+
252+
return nil
253+
})
254+
require.NoError(t, err, "failed inserting tx")
255+
}
256+
216257
// TestInputYield verifies the functioning of the inputYieldsPositively.
217258
func TestInputYield(t *testing.T) {
218259
t.Parallel()

wallet/utxos.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ func (w *Wallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
174174
numOutputs)
175175
}
176176

177+
// Exit early if the output doesn't belong to our wallet. We know it's
178+
// our UTXO iff the `TxDetails` has a credit record on this output.
179+
if !txDetail.HasOutput(prevOut.Index) {
180+
return nil, nil, 0, ErrNotMine
181+
}
182+
177183
pkScript := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].PkScript
178184

179185
// Determine the number of confirmations the output currently has.

wallet/utxos_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010

1111
"github.com/btcsuite/btcd/btcutil/hdkeychain"
12+
"github.com/btcsuite/btcd/chaincfg/chainhash"
1213
"github.com/btcsuite/btcd/txscript"
1314
"github.com/btcsuite/btcd/wire"
1415
"github.com/btcsuite/btcwallet/waddrmgr"
@@ -129,6 +130,82 @@ func TestFetchOutpointInfo(t *testing.T) {
129130
require.Equal(t, int64(0-testBlockHeight), confirmations)
130131
}
131132

133+
// TestFetchOutpointInfoErr checks when the wallet cannot find an output, a
134+
// proper error is returned.
135+
func TestFetchOutpointInfoErr(t *testing.T) {
136+
t.Parallel()
137+
138+
w, cleanup := testWallet(t)
139+
defer cleanup()
140+
141+
// Create an address we can use to send some coins to.
142+
addr, err := w.CurrentAddress(0, waddrmgr.KeyScopeBIP0084)
143+
require.NoError(t, err)
144+
p2shAddr, err := txscript.PayToAddrScript(addr)
145+
require.NoError(t, err)
146+
147+
// Create a tx that has two outputs - output1 belongs to the wallet,
148+
// output2 is external.
149+
output1 := wire.NewTxOut(100000, p2shAddr)
150+
output2 := wire.NewTxOut(100000, p2shAddr)
151+
tx := &wire.MsgTx{
152+
TxIn: []*wire.TxIn{{}},
153+
TxOut: []*wire.TxOut{
154+
output1,
155+
output2,
156+
},
157+
}
158+
159+
// Add the tx and its first output as the credit.
160+
addTxAndCredit(t, w, tx, 0)
161+
162+
testCases := []struct {
163+
name string
164+
prevOut *wire.OutPoint
165+
166+
// TODO(yy): refator `FetchOutpointInfo` to return wrapped
167+
// errors.
168+
errExpected string
169+
}{
170+
{
171+
name: "no tx details",
172+
prevOut: &wire.OutPoint{
173+
Hash: chainhash.Hash{1, 2, 3},
174+
Index: 1000,
175+
},
176+
errExpected: "does not belong to the wallet",
177+
},
178+
{
179+
name: "invalid output index",
180+
prevOut: &wire.OutPoint{
181+
Hash: tx.TxHash(),
182+
Index: 1000,
183+
},
184+
errExpected: "invalid output index",
185+
},
186+
{
187+
name: "no credit found",
188+
prevOut: &wire.OutPoint{
189+
Hash: tx.TxHash(),
190+
Index: 1000,
191+
},
192+
errExpected: "invalid output index",
193+
},
194+
}
195+
196+
for _, tc := range testCases {
197+
t.Run(tc.name, func(t *testing.T) {
198+
// Look up the UTXO for the outpoint now and compare it
199+
// to the expected error.
200+
tx, out, conf, err := w.FetchOutpointInfo(tc.prevOut)
201+
require.ErrorContains(t, err, tc.errExpected)
202+
require.Nil(t, tx)
203+
require.Nil(t, out)
204+
require.Zero(t, conf)
205+
})
206+
}
207+
}
208+
132209
// TestFetchDerivationInfo checks that the wallet can gather the derivation
133210
// info about an output based on the pkScript.
134211
func TestFetchDerivationInfo(t *testing.T) {

wtxmgr/query.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ type TxDetails struct {
4242
Label string
4343
}
4444

45+
// HasOutpoint takes an output identified by its output index and determines
46+
// whether the TxDetails contains this output. If the TxDetails doesn't have
47+
// this output, it means this output doesn't belong to our wallet.
48+
func (t *TxDetails) HasOutput(outputIndex uint32) bool {
49+
for _, cred := range t.Credits {
50+
if outputIndex == cred.Index {
51+
return true
52+
}
53+
}
54+
55+
return false
56+
}
57+
4558
// minedTxDetails fetches the TxDetails for the mined transaction with hash
4659
// txHash and the passed tx record key and value.
4760
func (s *Store) minedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, recKey, recVal []byte) (*TxDetails, error) {

0 commit comments

Comments
 (0)