Skip to content

Commit 78a45de

Browse files
committed
mempool/test: add comprehensive query method tests
In this commit, we add a new test file dedicated to testing the mempool query methods with realistic transaction scenarios. The tests cover FetchTransaction, TxHashes, TxDescs, MiningDescs, RawMempoolVerbose, and CheckSpend. Each test verifies both empty mempool behavior and populated mempool scenarios with parent-child transaction relationships. Key test patterns include verifying that TxHashes returns all transaction hashes with proper deduplication, that TxDescs and MiningDescs return correctly formatted descriptors with accurate fee information, and that RawMempoolVerbose properly tracks dependency relationships between transactions in the mempool. The CheckSpend test verifies the spent output detection works correctly, confirming that the mempool can identify which transaction spends a given outpoint while correctly returning nil for unspent outputs. These tests use the established createTestMempoolV2 harness pattern with mock setup, ensuring consistency with the existing test suite.
1 parent 36584c8 commit 78a45de

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

mempool/mempool_v2_query_test.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// Copyright (c) 2013-2025 The btcsuite developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package mempool
6+
7+
import (
8+
"testing"
9+
10+
"github.com/btcsuite/btcd/chaincfg/chainhash"
11+
"github.com/btcsuite/btcd/wire"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// TestFetchTransaction verifies that FetchTransaction correctly retrieves
16+
// transactions from the mempool.
17+
func TestFetchTransaction(t *testing.T) {
18+
t.Parallel()
19+
20+
h := createTestMempoolV2()
21+
22+
// Test fetching non-existent transaction.
23+
randomHash := chainhash.Hash{0x01}
24+
tx, err := h.mempool.FetchTransaction(&randomHash)
25+
require.Error(t, err, "should error for non-existent transaction")
26+
require.Nil(t, tx, "should return nil for non-existent transaction")
27+
require.Contains(t, err.Error(), "not in the pool")
28+
29+
// Add a transaction to the mempool.
30+
testTx := createTestTx()
31+
view := createDefaultUtxoView(testTx)
32+
h.expectTxAccepted(testTx, view)
33+
34+
_, _, err = h.mempool.MaybeAcceptTransaction(testTx, true, false)
35+
require.NoError(t, err)
36+
37+
// Fetch the transaction and verify it matches.
38+
fetchedTx, err := h.mempool.FetchTransaction(testTx.Hash())
39+
require.NoError(t, err)
40+
require.NotNil(t, fetchedTx)
41+
require.Equal(t, testTx.Hash(), fetchedTx.Hash())
42+
}
43+
44+
// TestTxHashes verifies that TxHashes returns correct hash list.
45+
func TestTxHashes(t *testing.T) {
46+
t.Parallel()
47+
48+
h := createTestMempoolV2()
49+
50+
// Empty mempool should return empty slice.
51+
hashes := h.mempool.TxHashes()
52+
require.NotNil(t, hashes, "should return non-nil slice")
53+
require.Empty(t, hashes, "empty mempool should have no hashes")
54+
55+
// Add several unique transactions.
56+
// Create a parent and two children to ensure unique hashes.
57+
parent := createTestTx()
58+
child1 := createChildTx(parent, 0)
59+
child2 := createChildTx(parent, 1)
60+
61+
// Add parent first.
62+
parentView := createDefaultUtxoView(parent)
63+
h.expectTxAccepted(parent, parentView)
64+
_, _, err := h.mempool.MaybeAcceptTransaction(parent, true, false)
65+
require.NoError(t, err)
66+
67+
// Add child1.
68+
child1View := createDefaultUtxoView(child1)
69+
h.expectTxAccepted(child1, child1View)
70+
_, _, err = h.mempool.MaybeAcceptTransaction(child1, true, false)
71+
require.NoError(t, err)
72+
73+
// Add child2.
74+
child2View := createDefaultUtxoView(child2)
75+
h.expectTxAccepted(child2, child2View)
76+
_, _, err = h.mempool.MaybeAcceptTransaction(child2, true, false)
77+
require.NoError(t, err)
78+
79+
// Get hashes and verify.
80+
hashes = h.mempool.TxHashes()
81+
require.Len(t, hashes, 3)
82+
83+
// Verify all three transaction hashes are present.
84+
hashMap := make(map[chainhash.Hash]bool)
85+
for _, hash := range hashes {
86+
hashMap[*hash] = true
87+
}
88+
require.True(t, hashMap[*parent.Hash()])
89+
require.True(t, hashMap[*child1.Hash()])
90+
require.True(t, hashMap[*child2.Hash()])
91+
}
92+
93+
// TestTxDescs verifies that TxDescs returns correct descriptors.
94+
func TestTxDescs(t *testing.T) {
95+
t.Parallel()
96+
97+
h := createTestMempoolV2()
98+
99+
// Empty mempool should return empty slice.
100+
descs := h.mempool.TxDescs()
101+
require.NotNil(t, descs, "should return non-nil slice")
102+
require.Empty(t, descs, "empty mempool should have no descriptors")
103+
104+
// Add a transaction.
105+
tx := createTestTx()
106+
view := createDefaultUtxoView(tx)
107+
h.expectTxAccepted(tx, view)
108+
109+
_, txDesc, err := h.mempool.MaybeAcceptTransaction(tx, true, false)
110+
require.NoError(t, err)
111+
require.NotNil(t, txDesc)
112+
113+
// Get descriptors and verify.
114+
descs = h.mempool.TxDescs()
115+
require.Len(t, descs, 1)
116+
117+
// Verify descriptor fields.
118+
desc := descs[0]
119+
require.Equal(t, tx.Hash(), desc.Tx.Hash())
120+
require.Equal(t, txDesc.Height, desc.Height)
121+
require.Equal(t, txDesc.Fee, desc.Fee)
122+
require.Equal(t, txDesc.FeePerKB, desc.FeePerKB)
123+
}
124+
125+
// TestMiningDescs verifies that MiningDescs returns correct mining descriptors.
126+
func TestMiningDescs(t *testing.T) {
127+
t.Parallel()
128+
129+
h := createTestMempoolV2()
130+
131+
// Empty mempool should return empty slice.
132+
descs := h.mempool.MiningDescs()
133+
require.NotNil(t, descs, "should return non-nil slice")
134+
require.Empty(t, descs, "empty mempool should have no mining descriptors")
135+
136+
// Add parent and child transactions to test topological ordering.
137+
parent := createTestTx()
138+
child := createChildTx(parent, 0)
139+
140+
// Add parent first.
141+
parentView := createDefaultUtxoView(parent)
142+
h.expectTxAccepted(parent, parentView)
143+
_, _, err := h.mempool.MaybeAcceptTransaction(parent, true, false)
144+
require.NoError(t, err)
145+
146+
// Add child.
147+
childView := createDefaultUtxoView(child)
148+
h.expectTxAccepted(child, childView)
149+
_, _, err = h.mempool.MaybeAcceptTransaction(child, true, false)
150+
require.NoError(t, err)
151+
152+
// Get mining descriptors.
153+
descs = h.mempool.MiningDescs()
154+
require.Len(t, descs, 2)
155+
156+
// Verify both parent and child are present.
157+
hashes := make(map[chainhash.Hash]bool)
158+
for _, desc := range descs {
159+
hashes[*desc.Tx.Hash()] = true
160+
}
161+
require.True(t, hashes[*parent.Hash()], "parent should be in results")
162+
require.True(t, hashes[*child.Hash()], "child should be in results")
163+
164+
// TODO: Add more sophisticated topological ordering verification
165+
// once we have a more complex transaction graph test case.
166+
}
167+
168+
// TestRawMempoolVerbose verifies that RawMempoolVerbose returns correct verbose info.
169+
func TestRawMempoolVerbose(t *testing.T) {
170+
t.Parallel()
171+
172+
h := createTestMempoolV2()
173+
174+
// Empty mempool should return empty map.
175+
verbose := h.mempool.RawMempoolVerbose()
176+
require.NotNil(t, verbose, "should return non-nil map")
177+
require.Empty(t, verbose, "empty mempool should have empty verbose map")
178+
179+
// Add parent and child to test dependency tracking.
180+
parent := createTestTx()
181+
child := createChildTx(parent, 0)
182+
183+
parentView := createDefaultUtxoView(parent)
184+
h.expectTxAccepted(parent, parentView)
185+
_, _, err := h.mempool.MaybeAcceptTransaction(parent, true, false)
186+
require.NoError(t, err)
187+
188+
childView := createDefaultUtxoView(child)
189+
h.expectTxAccepted(child, childView)
190+
_, _, err = h.mempool.MaybeAcceptTransaction(child, true, false)
191+
require.NoError(t, err)
192+
193+
// Get verbose info.
194+
verbose = h.mempool.RawMempoolVerbose()
195+
require.Len(t, verbose, 2)
196+
197+
// Verify child has parent in depends list.
198+
childVerbose, ok := verbose[child.Hash().String()]
199+
require.True(t, ok)
200+
require.Contains(t, childVerbose.Depends, parent.Hash().String())
201+
202+
// Verify parent has no dependencies.
203+
parentVerbose, ok := verbose[parent.Hash().String()]
204+
require.True(t, ok)
205+
require.Empty(t, parentVerbose.Depends)
206+
}
207+
208+
// TestMempoolV2CheckSpend verifies that CheckSpend correctly detects spending transactions.
209+
func TestMempoolV2CheckSpend(t *testing.T) {
210+
t.Parallel()
211+
212+
h := createTestMempoolV2()
213+
214+
// Test checking spend for non-existent outpoint.
215+
op := wire.OutPoint{
216+
Hash: chainhash.Hash{0x01},
217+
Index: 0,
218+
}
219+
spender := h.mempool.CheckSpend(op)
220+
require.Nil(t, spender, "should return nil for unspent outpoint")
221+
222+
// Add parent and child transactions.
223+
parent := createTestTx()
224+
child := createChildTx(parent, 0)
225+
226+
parentView := createDefaultUtxoView(parent)
227+
h.expectTxAccepted(parent, parentView)
228+
_, _, err := h.mempool.MaybeAcceptTransaction(parent, true, false)
229+
require.NoError(t, err)
230+
231+
childView := createDefaultUtxoView(child)
232+
h.expectTxAccepted(child, childView)
233+
_, _, err = h.mempool.MaybeAcceptTransaction(child, true, false)
234+
require.NoError(t, err)
235+
236+
// Check if child spends parent's output.
237+
parentOutpoint := wire.OutPoint{
238+
Hash: *parent.Hash(),
239+
Index: 0,
240+
}
241+
spender = h.mempool.CheckSpend(parentOutpoint)
242+
require.NotNil(t, spender)
243+
require.Equal(t, child.Hash(), spender.Hash())
244+
245+
// Check an unspent output from parent.
246+
unspentOutpoint := wire.OutPoint{
247+
Hash: *parent.Hash(),
248+
Index: 1,
249+
}
250+
spender = h.mempool.CheckSpend(unspentOutpoint)
251+
require.Nil(t, spender)
252+
}
253+
254+
// TestQueryMethodsConcurrentAccess verifies that query methods are safe for
255+
// concurrent access.
256+
func TestQueryMethodsConcurrentAccess(t *testing.T) {
257+
t.Parallel()
258+
259+
mp := newTestMempoolV2(t)
260+
261+
// Launch multiple goroutines performing concurrent query operations.
262+
const numReaders = 10
263+
const numReads = 100
264+
265+
done := make(chan bool)
266+
267+
for i := 0; i < numReaders; i++ {
268+
go func() {
269+
for j := 0; j < numReads; j++ {
270+
// Perform various query operations concurrently.
271+
_ = mp.TxHashes()
272+
_ = mp.TxDescs()
273+
_ = mp.MiningDescs()
274+
_ = mp.RawMempoolVerbose()
275+
276+
randomHash := chainhash.Hash{byte(j)}
277+
_, _ = mp.FetchTransaction(&randomHash)
278+
_ = mp.CheckSpend(wire.OutPoint{Hash: randomHash})
279+
}
280+
done <- true
281+
}()
282+
}
283+
284+
// Wait for all goroutines to complete.
285+
for i := 0; i < numReaders; i++ {
286+
<-done
287+
}
288+
289+
// No panics or races should occur (verified by race detector).
290+
}

0 commit comments

Comments
 (0)