Skip to content

Commit 4d523a6

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 4d523a6

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/btcsuite/btcd/wire"
1616
"github.com/btcsuite/btcwallet/waddrmgr"
1717
"github.com/btcsuite/btcwallet/walletdb"
18+
"github.com/btcsuite/btcwallet/wtxmgr"
1819
)
1920

2021
var (
@@ -174,6 +175,12 @@ func (w *Wallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*wire.MsgTx,
174175
numOutputs)
175176
}
176177

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

179186
// Determine the number of confirmations the output currently has.
@@ -224,3 +231,19 @@ func (w *Wallet) FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation,
224231

225232
return derivation, nil
226233
}
234+
235+
// hasOutpoint takes an output identified by its output index and determines
236+
// whether the TxDetails contains this output. If the TxDetails doesn't have
237+
// this output, it means this output doesn't belong to our wallet.
238+
//
239+
// TODO(yy): implement this method on `TxDetails` and update the package
240+
// `wtxmgr` instead.
241+
func hasOutput(t *wtxmgr.TxDetails, outputIndex uint32) bool {
242+
for _, cred := range t.Credits {
243+
if outputIndex == cred.Index {
244+
return true
245+
}
246+
}
247+
248+
return false
249+
}

wallet/utxos_test.go

Lines changed: 79 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,84 @@ 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: 0,
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: 1,
191+
},
192+
errExpected: "does not belong to the wallet",
193+
},
194+
}
195+
196+
for _, tc := range testCases {
197+
t.Run(tc.name, func(t *testing.T) {
198+
t.Parallel()
199+
200+
// Look up the UTXO for the outpoint now and compare it
201+
// to the expected error.
202+
tx, out, conf, err := w.FetchOutpointInfo(tc.prevOut)
203+
require.ErrorContains(t, err, tc.errExpected)
204+
require.Nil(t, tx)
205+
require.Nil(t, out)
206+
require.Zero(t, conf)
207+
})
208+
}
209+
}
210+
132211
// TestFetchDerivationInfo checks that the wallet can gather the derivation
133212
// info about an output based on the pkScript.
134213
func TestFetchDerivationInfo(t *testing.T) {

0 commit comments

Comments
 (0)