tx_test.go raw
1 package wtxmgr
2
3 import (
4 "bytes"
5 "encoding/hex"
6 "github.com/p9c/p9/pkg/amt"
7 "io/ioutil"
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "github.com/p9c/p9/pkg/chaincfg"
14 "github.com/p9c/p9/pkg/chainhash"
15 "github.com/p9c/p9/pkg/util"
16 "github.com/p9c/p9/pkg/walletdb"
17 _ "github.com/p9c/p9/pkg/walletdb/bdb"
18 "github.com/p9c/p9/pkg/wire"
19 )
20
21 // Received transaction output for mainnet outpoint
22 // 61d3696de4c888730cbe06b0ad8ecb6d72d6108e893895aa9bc067bd7eba3fad:0
23 var (
24 TstRecvSerializedTx, _ = hex.DecodeString("010000000114d9ff358894c486b4ae11c2a8cf7851b1df64c53d2e511278eff17c22fb7373000000008c493046022100995447baec31ee9f6d4ec0e05cb2a44f6b817a99d5f6de167d1c75354a946410022100c9ffc23b64d770b0e01e7ff4d25fbc2f1ca8091053078a247905c39fce3760b601410458b8e267add3c1e374cf40f1de02b59213a82e1d84c2b94096e22e2f09387009c96debe1d0bcb2356ffdcf65d2a83d4b34e72c62eccd8490dbf2110167783b2bffffffff0280969800000000001976a914479ed307831d0ac19ebc5f63de7d5f1a430ddb9d88ac38bfaa00000000001976a914dadf9e3484f28b385ddeaa6c575c0c0d18e9788a88ac00000000")
25 TstRecvTx, _ = util.NewTxFromBytes(TstRecvSerializedTx)
26 TstRecvTxSpendingTxBlockHash, _ = chainhash.NewHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4")
27 // TstRecvAmt = int64(10000000)
28 TstRecvTxBlockDetails = &BlockMeta{
29 Block: Block{Hash: *TstRecvTxSpendingTxBlockHash, Height: 276425},
30 Time: time.Unix(1387737310, 0),
31 }
32 TstRecvCurrentHeight = int32(284498) // mainnet blockchain height at time of writing
33 // TstRecvTxOutConfirms = 8074 // hardcoded number of confirmations given the above block height
34 TstSpendingSerializedTx, _ = hex.DecodeString("0100000003ad3fba7ebd67c09baa9538898e10d6726dcb8eadb006be0c7388c8e46d69d361000000006b4830450220702c4fbde5532575fed44f8d6e8c3432a2a9bd8cff2f966c3a79b2245a7c88db02210095d6505a57e350720cb52b89a9b56243c15ddfcea0596aedc1ba55d9fb7d5aa0012103cccb5c48a699d3efcca6dae277fee6b82e0229ed754b742659c3acdfed2651f9ffffffffdbd36173f5610e34de5c00ed092174603761595d90190f790e79cda3e5b45bc2010000006b483045022000fa20735e5875e64d05bed43d81b867f3bd8745008d3ff4331ef1617eac7c44022100ad82261fc57faac67fc482a37b6bf18158da0971e300abf5fe2f9fd39e107f58012102d4e1caf3e022757512c204bf09ff56a9981df483aba3c74bb60d3612077c9206ffffffff65536c9d964b6f89b8ef17e83c6666641bc495cb27bab60052f76cd4556ccd0d040000006a473044022068e3886e0299ffa69a1c3ee40f8b6700f5f6d463a9cf9dbf22c055a131fc4abc02202b58957fe19ff1be7a84c458d08016c53fbddec7184ac5e633f2b282ae3420ae012103b4e411b81d32a69fb81178a8ea1abaa12f613336923ee920ffbb1b313af1f4d2ffffffff02ab233200000000001976a91418808b2fbd8d2c6d022aed5cd61f0ce6c0a4cbb688ac4741f011000000001976a914f081088a300c80ce36b717a9914ab5ec8a7d283988ac00000000")
35 TstSpendingTx, _ = util.NewTxFromBytes(TstSpendingSerializedTx)
36 TstSpendingTxBlockHeight = int32(279143)
37 TstSignedTxBlockHash, _ = chainhash.NewHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4")
38 TstSignedTxBlockDetails = &BlockMeta{
39 Block: Block{Hash: *TstSignedTxBlockHash, Height: TstSpendingTxBlockHeight},
40 Time: time.Unix(1389114091, 0),
41 }
42 )
43
44 func testDB() (walletdb.DB, func(), error) {
45 tmpDir, e := ioutil.TempDir("", "wtxmgr_test")
46 if e != nil {
47 return nil, func() {
48 }, e
49 }
50 db, e := walletdb.Create("bdb", filepath.Join(tmpDir, "db"))
51 return db, func() {
52 if e = os.RemoveAll(tmpDir); E.Chk(e) {
53 }
54 }, e
55 }
56
57 var namespaceKey = []byte("txstore")
58
59 func testStore() (*Store, walletdb.DB, func(), error) {
60 var e error
61 var tmpDir string
62 tmpDir, e = ioutil.TempDir("", "wtxmgr_test")
63 if e != nil {
64 return nil, nil, func() {
65 }, e
66 }
67 var db walletdb.DB
68 db, e = walletdb.Create("bdb", filepath.Join(tmpDir, "db"))
69 if e != nil {
70 if e = os.RemoveAll(tmpDir); E.Chk(e) {
71 }
72 return nil, nil, nil, e
73 }
74 teardown := func() {
75 if e = db.Close(); E.Chk(e) {
76 }
77 if e = os.RemoveAll(tmpDir); E.Chk(e) {
78 }
79 }
80 var s *Store
81 e = walletdb.Update(db, func(tx walletdb.ReadWriteTx) (e error) {
82 ns, e := tx.CreateTopLevelBucket(namespaceKey)
83 if e != nil {
84 return e
85 }
86 e = Create(ns)
87 if e != nil {
88 return e
89 }
90 s, e = Open(ns, &chaincfg.TestNet3Params)
91 return e
92 },
93 )
94 return s, db, teardown, e
95 }
96 func serializeTx(tx *util.Tx) []byte {
97 var buf bytes.Buffer
98 e := tx.MsgTx().Serialize(&buf)
99 if e != nil {
100 panic(e)
101 }
102 return buf.Bytes()
103 }
104 func TestInsertsCreditsDebitsRollbacks(t *testing.T) {
105 t.Parallel()
106 // Create a double spend of the received blockchain transaction.
107 dupRecvTx, _ := util.NewTxFromBytes(TstRecvSerializedTx)
108 // Switch txout amount to 1 DUO. Transaction store doesn't validate txs, so this is fine for testing a double spend
109 // removal.
110 TstDupRecvAmount := int64(1e8)
111 newDupMsgTx := dupRecvTx.MsgTx()
112 newDupMsgTx.TxOut[0].Value = TstDupRecvAmount
113 TstDoubleSpendTx := util.NewTx(newDupMsgTx)
114 TstDoubleSpendSerializedTx := serializeTx(TstDoubleSpendTx)
115 // Create a "signed" (with invalid sigs) tx that spends output 0 of the double spend.
116 spendingTx := wire.NewMsgTx(wire.TxVersion)
117 spendingTxIn := wire.NewTxIn(wire.NewOutPoint(TstDoubleSpendTx.Hash(), 0), []byte{0, 1, 2, 3, 4}, nil)
118 spendingTx.AddTxIn(spendingTxIn)
119 spendingTxOut1 := wire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9})
120 spendingTxOut2 := wire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14})
121 spendingTx.AddTxOut(spendingTxOut1)
122 spendingTx.AddTxOut(spendingTxOut2)
123 TstSpendingTx = util.NewTx(spendingTx)
124 TstSpendingSerializedTx = serializeTx(TstSpendingTx)
125 var _ = TstSpendingTx
126 tests := []struct {
127 name string
128 f func(*Store, walletdb.ReadWriteBucket) (*Store, error)
129 bal, unc amt.Amount
130 unspents map[wire.OutPoint]struct{}
131 unmined map[chainhash.Hash]struct{}
132 }{
133 {
134 name: "new store",
135 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
136 return s, nil
137 },
138 bal: 0,
139 unc: 0,
140 unspents: map[wire.OutPoint]struct{}{},
141 unmined: map[chainhash.Hash]struct{}{},
142 },
143 {
144 name: "txout insert",
145 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
146 rec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
147 if e != nil {
148 return nil, e
149 }
150 e = s.InsertTx(ns, rec, nil)
151 if e != nil {
152 return nil, e
153 }
154 e = s.AddCredit(ns, rec, nil, 0, false)
155 return s, e
156 },
157 bal: 0,
158 unc: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
159 unspents: map[wire.OutPoint]struct{}{
160 {
161 Hash: *TstRecvTx.Hash(),
162 Index: 0,
163 }: {},
164 },
165 unmined: map[chainhash.Hash]struct{}{
166 *TstRecvTx.Hash(): {},
167 },
168 },
169 {
170 name: "insert duplicate unconfirmed",
171 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
172 rec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
173 if e != nil {
174 return nil, e
175 }
176 e = s.InsertTx(ns, rec, nil)
177 if e != nil {
178 return nil, e
179 }
180 e = s.AddCredit(ns, rec, nil, 0, false)
181 return s, e
182 },
183 bal: 0,
184 unc: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
185 unspents: map[wire.OutPoint]struct{}{
186 {
187 Hash: *TstRecvTx.Hash(),
188 Index: 0,
189 }: {},
190 },
191 unmined: map[chainhash.Hash]struct{}{
192 *TstRecvTx.Hash(): {},
193 },
194 },
195 {
196 name: "confirmed txout insert",
197 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
198 rec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
199 if e != nil {
200 return nil, e
201 }
202 e = s.InsertTx(ns, rec, TstRecvTxBlockDetails)
203 if e != nil {
204 return nil, e
205 }
206 e = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false)
207 return s, e
208 },
209 bal: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
210 unc: 0,
211 unspents: map[wire.OutPoint]struct{}{
212 {
213 Hash: *TstRecvTx.Hash(),
214 Index: 0,
215 }: {},
216 },
217 unmined: map[chainhash.Hash]struct{}{},
218 },
219 {
220 name: "insert duplicate confirmed",
221 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
222 rec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
223 if e != nil {
224 return nil, e
225 }
226 e = s.InsertTx(ns, rec, TstRecvTxBlockDetails)
227 if e != nil {
228 return nil, e
229 }
230 e = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false)
231 return s, e
232 },
233 bal: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
234 unc: 0,
235 unspents: map[wire.OutPoint]struct{}{
236 {
237 Hash: *TstRecvTx.Hash(),
238 Index: 0,
239 }: {},
240 },
241 unmined: map[chainhash.Hash]struct{}{},
242 },
243 {
244 name: "rollback confirmed credit",
245 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
246 e := s.Rollback(ns, TstRecvTxBlockDetails.Height)
247 return s, e
248 },
249 bal: 0,
250 unc: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
251 unspents: map[wire.OutPoint]struct{}{
252 {
253 Hash: *TstRecvTx.Hash(),
254 Index: 0,
255 }: {},
256 },
257 unmined: map[chainhash.Hash]struct{}{
258 *TstRecvTx.Hash(): {},
259 },
260 },
261 {
262 name: "insert confirmed double spend",
263 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
264 rec, e := NewTxRecord(TstDoubleSpendSerializedTx, time.Now())
265 if e != nil {
266 return nil, e
267 }
268 e = s.InsertTx(ns, rec, TstRecvTxBlockDetails)
269 if e != nil {
270 return nil, e
271 }
272 e = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false)
273 return s, e
274 },
275 bal: amt.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value),
276 unc: 0,
277 unspents: map[wire.OutPoint]struct{}{
278 {
279 Hash: *TstDoubleSpendTx.Hash(),
280 Index: 0,
281 }: {},
282 },
283 unmined: map[chainhash.Hash]struct{}{},
284 },
285 {
286 name: "insert unconfirmed debit",
287 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
288 rec, e := NewTxRecord(TstSpendingSerializedTx, time.Now())
289 if e != nil {
290 return nil, e
291 }
292 e = s.InsertTx(ns, rec, nil)
293 return s, e
294 },
295 bal: 0,
296 unc: 0,
297 unspents: map[wire.OutPoint]struct{}{},
298 unmined: map[chainhash.Hash]struct{}{
299 *TstSpendingTx.Hash(): {},
300 },
301 },
302 {
303 name: "insert unconfirmed debit again",
304 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
305 rec, e := NewTxRecord(TstDoubleSpendSerializedTx, time.Now())
306 if e != nil {
307 return nil, e
308 }
309 e = s.InsertTx(ns, rec, TstRecvTxBlockDetails)
310 return s, e
311 },
312 bal: 0,
313 unc: 0,
314 unspents: map[wire.OutPoint]struct{}{},
315 unmined: map[chainhash.Hash]struct{}{
316 *TstSpendingTx.Hash(): {},
317 },
318 },
319 {
320 name: "insert change (index 0)",
321 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
322 rec, e := NewTxRecord(TstSpendingSerializedTx, time.Now())
323 if e != nil {
324 return nil, e
325 }
326 e = s.InsertTx(ns, rec, nil)
327 if e != nil {
328 return nil, e
329 }
330 e = s.AddCredit(ns, rec, nil, 0, true)
331 return s, e
332 },
333 bal: 0,
334 unc: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value),
335 unspents: map[wire.OutPoint]struct{}{
336 {
337 Hash: *TstSpendingTx.Hash(),
338 Index: 0,
339 }: {},
340 },
341 unmined: map[chainhash.Hash]struct{}{
342 *TstSpendingTx.Hash(): {},
343 },
344 },
345 {
346 name: "insert output back to this own wallet (index 1)",
347 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
348 rec, e := NewTxRecord(TstSpendingSerializedTx, time.Now())
349 if e != nil {
350 return nil, e
351 }
352 e = s.InsertTx(ns, rec, nil)
353 if e != nil {
354 return nil, e
355 }
356 e = s.AddCredit(ns, rec, nil, 1, true)
357 return s, e
358 },
359 bal: 0,
360 unc: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
361 unspents: map[wire.OutPoint]struct{}{
362 {
363 Hash: *TstSpendingTx.Hash(),
364 Index: 0,
365 }: {},
366 {
367 Hash: *TstSpendingTx.Hash(),
368 Index: 1,
369 }: {},
370 },
371 unmined: map[chainhash.Hash]struct{}{
372 *TstSpendingTx.Hash(): {},
373 },
374 },
375 {
376 name: "confirm signed tx",
377 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
378 rec, e := NewTxRecord(TstSpendingSerializedTx, time.Now())
379 if e != nil {
380 return nil, e
381 }
382 e = s.InsertTx(ns, rec, TstSignedTxBlockDetails)
383 return s, e
384 },
385 bal: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
386 unc: 0,
387 unspents: map[wire.OutPoint]struct{}{
388 {
389 Hash: *TstSpendingTx.Hash(),
390 Index: 0,
391 }: {},
392 {
393 Hash: *TstSpendingTx.Hash(),
394 Index: 1,
395 }: {},
396 },
397 unmined: map[chainhash.Hash]struct{}{},
398 },
399 {
400 name: "rollback after spending tx",
401 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
402 e := s.Rollback(ns, TstSignedTxBlockDetails.Height+1)
403 return s, e
404 },
405 bal: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
406 unc: 0,
407 unspents: map[wire.OutPoint]struct{}{
408 {
409 Hash: *TstSpendingTx.Hash(),
410 Index: 0,
411 }: {},
412 {
413 Hash: *TstSpendingTx.Hash(),
414 Index: 1,
415 }: {},
416 },
417 unmined: map[chainhash.Hash]struct{}{},
418 },
419 {
420 name: "rollback spending tx block",
421 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
422 e := s.Rollback(ns, TstSignedTxBlockDetails.Height)
423 return s, e
424 },
425 bal: 0,
426 unc: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
427 unspents: map[wire.OutPoint]struct{}{
428 {
429 Hash: *TstSpendingTx.Hash(),
430 Index: 0,
431 }: {},
432 {
433 Hash: *TstSpendingTx.Hash(),
434 Index: 1,
435 }: {},
436 },
437 unmined: map[chainhash.Hash]struct{}{
438 *TstSpendingTx.Hash(): {},
439 },
440 },
441 {
442 name: "rollback double spend tx block",
443 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
444 e := s.Rollback(ns, TstRecvTxBlockDetails.Height)
445 return s, e
446 },
447 bal: 0,
448 unc: amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
449 unspents: map[wire.OutPoint]struct{}{
450 *wire.NewOutPoint(TstSpendingTx.Hash(), 0): {},
451 *wire.NewOutPoint(TstSpendingTx.Hash(), 1): {},
452 },
453 unmined: map[chainhash.Hash]struct{}{
454 *TstDoubleSpendTx.Hash(): {},
455 *TstSpendingTx.Hash(): {},
456 },
457 },
458 {
459 name: "insert original recv txout",
460 f: func(s *Store, ns walletdb.ReadWriteBucket) (*Store, error) {
461 rec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
462 if e != nil {
463 return nil, e
464 }
465 e = s.InsertTx(ns, rec, TstRecvTxBlockDetails)
466 if e != nil {
467 return nil, e
468 }
469 e = s.AddCredit(ns, rec, TstRecvTxBlockDetails, 0, false)
470 return s, e
471 },
472 bal: amt.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
473 unc: 0,
474 unspents: map[wire.OutPoint]struct{}{
475 *wire.NewOutPoint(TstRecvTx.Hash(), 0): {},
476 },
477 unmined: map[chainhash.Hash]struct{}{},
478 },
479 }
480 s, db, teardown, e := testStore()
481 if e != nil {
482 t.Fatal(e)
483 }
484 defer teardown()
485 for _, test := range tests {
486 e := walletdb.Update(db, func(tx walletdb.ReadWriteTx) (e error) {
487 ns := tx.ReadWriteBucket(namespaceKey)
488 tmpStore, e := test.f(s, ns)
489 if e != nil {
490 t.Fatalf("%s: got error: %v", test.name, e)
491 }
492 s = tmpStore
493 bal, e := s.Balance(ns, 1, TstRecvCurrentHeight)
494 if e != nil {
495 t.Fatalf("%s: Confirmed Balance failed: %v", test.name, e)
496 }
497 if bal != test.bal {
498 t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal)
499 }
500 unc, e := s.Balance(ns, 0, TstRecvCurrentHeight)
501 if e != nil {
502 t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, e)
503 }
504 unc -= bal
505 if unc != test.unc {
506 t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc)
507 }
508 // Chk that unspent outputs match expected.
509 unspent, e := s.UnspentOutputs(ns)
510 if e != nil {
511 t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, e)
512 }
513 for _, cred := range unspent {
514 if _, ok := test.unspents[cred.OutPoint]; !ok {
515 t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint)
516 }
517 delete(test.unspents, cred.OutPoint)
518 }
519 if len(test.unspents) != 0 {
520 t.Fatalf("%s: missing expected unspent output(s)", test.name)
521 }
522 // Chk that unmined txs match expected.
523 unmined, e := s.UnminedTxs(ns)
524 if e != nil {
525 t.Fatalf("%s: cannot load unmined transactions: %v", test.name, e)
526 }
527 for _, tx := range unmined {
528 txHash := tx.TxHash()
529 if _, ok := test.unmined[txHash]; !ok {
530 t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash)
531 }
532 delete(test.unmined, txHash)
533 }
534 if len(test.unmined) != 0 {
535 t.Fatalf("%s: missing expected unmined tx(s)", test.name)
536 }
537 return nil
538 },
539 )
540 if e != nil {
541 t.Fatal(e)
542 }
543 }
544 }
545 func TestFindingSpentCredits(t *testing.T) {
546 t.Parallel()
547 s, db, teardown, e := testStore()
548 if e != nil {
549 t.Fatal(e)
550 }
551 defer teardown()
552 dbtx, e := db.BeginReadWriteTx()
553 if e != nil {
554 t.Fatal(e)
555 }
556 defer func() {
557 e = dbtx.Commit()
558 if e != nil {
559 t.Log(e)
560 }
561 }()
562 ns := dbtx.ReadWriteBucket(namespaceKey)
563 // Insert transaction and credit which will be spent.
564 recvRec, e := NewTxRecord(TstRecvSerializedTx, time.Now())
565 if e != nil {
566 t.Fatal(e)
567 }
568 e = s.InsertTx(ns, recvRec, TstRecvTxBlockDetails)
569 if e != nil {
570 t.Fatal(e)
571 }
572 e = s.AddCredit(ns, recvRec, TstRecvTxBlockDetails, 0, false)
573 if e != nil {
574 t.Fatal(e)
575 }
576 // Insert confirmed transaction which spends the above credit.
577 spendingRec, e := NewTxRecord(TstSpendingSerializedTx, time.Now())
578 if e != nil {
579 t.Fatal(e)
580 }
581 e = s.InsertTx(ns, spendingRec, TstSignedTxBlockDetails)
582 if e != nil {
583 t.Fatal(e)
584 }
585 e = s.AddCredit(ns, spendingRec, TstSignedTxBlockDetails, 0, false)
586 if e != nil {
587 t.Fatal(e)
588 }
589 bal, e := s.Balance(ns, 1, TstSignedTxBlockDetails.Height)
590 if e != nil {
591 t.Fatal(e)
592 }
593 expectedBal := amt.Amount(TstSpendingTx.MsgTx().TxOut[0].Value)
594 if bal != expectedBal {
595 t.Fatalf("bad balance: %v != %v", bal, expectedBal)
596 }
597 unspents, e := s.UnspentOutputs(ns)
598 if e != nil {
599 t.Fatal(e)
600 }
601 op := wire.NewOutPoint(TstSpendingTx.Hash(), 0)
602 if unspents[0].OutPoint != *op {
603 t.Fatal("unspent outpoint doesn't match expected")
604 }
605 if len(unspents) > 1 {
606 t.Fatal("has more than one unspent credit")
607 }
608 }
609 func newCoinBase(outputValues ...int64) *wire.MsgTx {
610 tx := wire.MsgTx{
611 TxIn: []*wire.TxIn{
612 {
613 PreviousOutPoint: wire.OutPoint{Index: ^uint32(0)},
614 },
615 },
616 }
617 for _, val := range outputValues {
618 tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val})
619 }
620 return &tx
621 }
622 func spendOutput(txHash *chainhash.Hash, index uint32, outputValues ...int64) *wire.MsgTx {
623 tx := wire.MsgTx{
624 TxIn: []*wire.TxIn{
625 {
626 PreviousOutPoint: wire.OutPoint{Hash: *txHash, Index: index},
627 },
628 },
629 }
630 for _, val := range outputValues {
631 tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val})
632 }
633 return &tx
634 }
635 func spendOutputs(outputs []wire.OutPoint, outputValues ...int64) *wire.MsgTx {
636 tx := &wire.MsgTx{}
637 for _, output := range outputs {
638 tx.TxIn = append(tx.TxIn, &wire.TxIn{PreviousOutPoint: output})
639 }
640 for _, value := range outputValues {
641 tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: value})
642 }
643 return tx
644 }
645 func TestCoinbases(t *testing.T) {
646 t.Parallel()
647 s, db, teardown, e := testStore()
648 if e != nil {
649 t.Fatal(e)
650 }
651 defer teardown()
652 dbtx, e := db.BeginReadWriteTx()
653 if e != nil {
654 t.Fatal(e)
655 }
656 defer func() {
657 e = dbtx.Commit()
658 if e != nil {
659 t.Log(e)
660 }
661 }()
662 ns := dbtx.ReadWriteBucket(namespaceKey)
663 b100 := BlockMeta{
664 Block: Block{Height: 100},
665 Time: time.Now(),
666 }
667 cb := newCoinBase(20e8, 10e8, 30e8)
668 cbRec, e := NewTxRecordFromMsgTx(cb, b100.Time)
669 if e != nil {
670 t.Fatal(e)
671 }
672 // Insert coinbase and mark outputs 0 and 2 as credits.
673 e = s.InsertTx(ns, cbRec, &b100)
674 if e != nil {
675 t.Fatal(e)
676 }
677 e = s.AddCredit(ns, cbRec, &b100, 0, false)
678 if e != nil {
679 t.Fatal(e)
680 }
681 e = s.AddCredit(ns, cbRec, &b100, 2, false)
682 if e != nil {
683 t.Fatal(e)
684 }
685 coinbaseMaturity := int32(chaincfg.TestNet3Params.CoinbaseMaturity)
686 // Balance should be 0 if the coinbase is immature, 50 DUO at and beyond
687 // maturity.
688 //
689 // Outputs when depth is below maturity are never included, no matter
690 // the required number of confirmations. Matured outputs which have
691 // greater depth than minConf are still excluded.
692 type balTest struct {
693 height int32
694 minConf int32
695 bal amt.Amount
696 }
697 balTests := []balTest{
698 // Next block it is still immature
699 {
700 height: b100.Height + coinbaseMaturity - 2,
701 minConf: 0,
702 bal: 0,
703 },
704 {
705 height: b100.Height + coinbaseMaturity - 2,
706 minConf: coinbaseMaturity,
707 bal: 0,
708 },
709 // Next block it matures
710 {
711 height: b100.Height + coinbaseMaturity - 1,
712 minConf: 0,
713 bal: 50e8,
714 },
715 {
716 height: b100.Height + coinbaseMaturity - 1,
717 minConf: 1,
718 bal: 50e8,
719 },
720 {
721 height: b100.Height + coinbaseMaturity - 1,
722 minConf: coinbaseMaturity - 1,
723 bal: 50e8,
724 },
725 {
726 height: b100.Height + coinbaseMaturity - 1,
727 minConf: coinbaseMaturity,
728 bal: 50e8,
729 },
730 {
731 height: b100.Height + coinbaseMaturity - 1,
732 minConf: coinbaseMaturity + 1,
733 bal: 0,
734 },
735 // Matures at this block
736 {
737 height: b100.Height + coinbaseMaturity,
738 minConf: 0,
739 bal: 50e8,
740 },
741 {
742 height: b100.Height + coinbaseMaturity,
743 minConf: 1,
744 bal: 50e8,
745 },
746 {
747 height: b100.Height + coinbaseMaturity,
748 minConf: coinbaseMaturity,
749 bal: 50e8,
750 },
751 {
752 height: b100.Height + coinbaseMaturity,
753 minConf: coinbaseMaturity + 1,
754 bal: 50e8,
755 },
756 {
757 height: b100.Height + coinbaseMaturity,
758 minConf: coinbaseMaturity + 2,
759 bal: 0,
760 },
761 }
762 for i, tst := range balTests {
763 var bal amt.Amount
764 bal, e = s.Balance(ns, tst.minConf, tst.height)
765 if e != nil {
766 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
767 }
768 if bal != tst.bal {
769 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
770 }
771 }
772 if t.Failed() {
773 t.Fatal("Failed balance checks after inserting coinbase")
774 }
775 // Spend an output from the coinbase tx in an unmined transaction when
776 // the next block will mature the coinbase.
777 spenderATime := time.Now()
778 spenderA := spendOutput(&cbRec.Hash, 0, 5e8, 15e8)
779 spenderARec, e := NewTxRecordFromMsgTx(spenderA, spenderATime)
780 if e != nil {
781 t.Fatal(e)
782 }
783 e = s.InsertTx(ns, spenderARec, nil)
784 if e != nil {
785 t.Fatal(e)
786 }
787 e = s.AddCredit(ns, spenderARec, nil, 0, false)
788 if e != nil {
789 t.Fatal(e)
790 }
791 balTests = []balTest{
792 // Next block it matures
793 {
794 height: b100.Height + coinbaseMaturity - 1,
795 minConf: 0,
796 bal: 35e8,
797 },
798 {
799 height: b100.Height + coinbaseMaturity - 1,
800 minConf: 1,
801 bal: 30e8,
802 },
803 {
804 height: b100.Height + coinbaseMaturity - 1,
805 minConf: coinbaseMaturity,
806 bal: 30e8,
807 },
808 {
809 height: b100.Height + coinbaseMaturity - 1,
810 minConf: coinbaseMaturity + 1,
811 bal: 0,
812 },
813 // Matures at this block
814 {
815 height: b100.Height + coinbaseMaturity,
816 minConf: 0,
817 bal: 35e8,
818 },
819 {
820 height: b100.Height + coinbaseMaturity,
821 minConf: 1,
822 bal: 30e8,
823 },
824 {
825 height: b100.Height + coinbaseMaturity,
826 minConf: coinbaseMaturity,
827 bal: 30e8,
828 },
829 {
830 height: b100.Height + coinbaseMaturity,
831 minConf: coinbaseMaturity + 1,
832 bal: 30e8,
833 },
834 {
835 height: b100.Height + coinbaseMaturity,
836 minConf: coinbaseMaturity + 2,
837 bal: 0,
838 },
839 }
840 balTestsBeforeMaturity := balTests
841 for i, tst := range balTests {
842 var bal amt.Amount
843 bal, e = s.Balance(ns, tst.minConf, tst.height)
844 if e != nil {
845 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
846 }
847 if bal != tst.bal {
848 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
849 }
850 }
851 if t.Failed() {
852 t.Fatal("Failed balance checks after spending coinbase with unmined transaction")
853 }
854 // Mine the spending transaction in the block the coinbase matures.
855 bMaturity := BlockMeta{
856 Block: Block{Height: b100.Height + coinbaseMaturity},
857 Time: time.Now(),
858 }
859 e = s.InsertTx(ns, spenderARec, &bMaturity)
860 if e != nil {
861 t.Fatal(e)
862 }
863 balTests = []balTest{
864 // Maturity height
865 {
866 height: bMaturity.Height,
867 minConf: 0,
868 bal: 35e8,
869 },
870 {
871 height: bMaturity.Height,
872 minConf: 1,
873 bal: 35e8,
874 },
875 {
876 height: bMaturity.Height,
877 minConf: 2,
878 bal: 30e8,
879 },
880 {
881 height: bMaturity.Height,
882 minConf: coinbaseMaturity,
883 bal: 30e8,
884 },
885 {
886 height: bMaturity.Height,
887 minConf: coinbaseMaturity + 1,
888 bal: 30e8,
889 },
890 {
891 height: bMaturity.Height,
892 minConf: coinbaseMaturity + 2,
893 bal: 0,
894 },
895 // Next block after maturity height
896 {
897 height: bMaturity.Height + 1,
898 minConf: 0,
899 bal: 35e8,
900 },
901 {
902 height: bMaturity.Height + 1,
903 minConf: 2,
904 bal: 35e8,
905 },
906 {
907 height: bMaturity.Height + 1,
908 minConf: 3,
909 bal: 30e8,
910 },
911 {
912 height: bMaturity.Height + 1,
913 minConf: coinbaseMaturity + 2,
914 bal: 30e8,
915 },
916 {
917 height: bMaturity.Height + 1,
918 minConf: coinbaseMaturity + 3,
919 bal: 0,
920 },
921 }
922 var bal amt.Amount
923 for i, tst := range balTests {
924 bal, e = s.Balance(ns, tst.minConf, tst.height)
925 if e != nil {
926 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
927 }
928 if bal != tst.bal {
929 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
930 }
931 }
932 if t.Failed() {
933 t.Fatal("Failed balance checks mining coinbase spending transaction")
934 }
935 // Create another spending transaction which spends the credit from the
936 // first spender. This will be used to test removing the entire
937 // conflict chain when the coinbase is later reorged out.
938 //
939 // Use the same output amount as spender A and mark it as a credit.
940 // This will mean the balance tests should report identical results.
941 spenderBTime := time.Now()
942 spenderB := spendOutput(&spenderARec.Hash, 0, 5e8)
943 spenderBRec, e := NewTxRecordFromMsgTx(spenderB, spenderBTime)
944 if e != nil {
945 t.Fatal(e)
946 }
947 e = s.InsertTx(ns, spenderBRec, &bMaturity)
948 if e != nil {
949 t.Fatal(e)
950 }
951 e = s.AddCredit(ns, spenderBRec, &bMaturity, 0, false)
952 if e != nil {
953 t.Fatal(e)
954 }
955 for i, tst := range balTests {
956 bal, e = s.Balance(ns, tst.minConf, tst.height)
957 if e != nil {
958 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
959 }
960 if bal != tst.bal {
961 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
962 }
963 }
964 if t.Failed() {
965 t.Fatal("Failed balance checks mining second spending transaction")
966 }
967 // Reorg out the block that matured the coinbase and check balances
968 // again.
969 e = s.Rollback(ns, bMaturity.Height)
970 if e != nil {
971 t.Fatal(e)
972 }
973 balTests = balTestsBeforeMaturity
974 for i, tst := range balTests {
975 bal, e = s.Balance(ns, tst.minConf, tst.height)
976 if e != nil {
977 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
978 }
979 if bal != tst.bal {
980 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
981 }
982 }
983 if t.Failed() {
984 t.Fatal("Failed balance checks after reorging maturity block")
985 }
986 // Reorg out the block which contained the coinbase. There should be no
987 // more transactions in the store (since the previous outputs referenced
988 // by the spending tx no longer exist), and the balance will always be
989 // zero.
990 e = s.Rollback(ns, b100.Height)
991 if e != nil {
992 t.Fatal(e)
993 }
994 balTests = []balTest{
995 // Current height
996 {
997 height: b100.Height - 1,
998 minConf: 0,
999 bal: 0,
1000 },
1001 {
1002 height: b100.Height - 1,
1003 minConf: 1,
1004 bal: 0,
1005 },
1006 // Next height
1007 {
1008 height: b100.Height,
1009 minConf: 0,
1010 bal: 0,
1011 },
1012 {
1013 height: b100.Height,
1014 minConf: 1,
1015 bal: 0,
1016 },
1017 }
1018 for i, tst := range balTests {
1019 bal, e = s.Balance(ns, tst.minConf, tst.height)
1020 if e != nil {
1021 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
1022 }
1023 if bal != tst.bal {
1024 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
1025 }
1026 }
1027 if t.Failed() {
1028 t.Fatal("Failed balance checks after reorging coinbase block")
1029 }
1030 unminedTxs, e := s.UnminedTxs(ns)
1031 if e != nil {
1032 t.Fatal(e)
1033 }
1034 if len(unminedTxs) != 0 {
1035 t.Fatalf("Should have no unmined transactions after coinbase reorg, found %d", len(unminedTxs))
1036 }
1037 }
1038
1039 // Test moving multiple transactions from unmined buckets to the same block.
1040 func TestMoveMultipleToSameBlock(t *testing.T) {
1041 t.Parallel()
1042 s, db, teardown, e := testStore()
1043 if e != nil {
1044 t.Fatal(e)
1045 }
1046 defer teardown()
1047 dbtx, e := db.BeginReadWriteTx()
1048 if e != nil {
1049 t.Fatal(e)
1050 }
1051 defer func() {
1052 e = dbtx.Commit()
1053 if e != nil {
1054 t.Log(e)
1055 }
1056 }()
1057 ns := dbtx.ReadWriteBucket(namespaceKey)
1058 b100 := BlockMeta{
1059 Block: Block{Height: 100},
1060 Time: time.Now(),
1061 }
1062 cb := newCoinBase(20e8, 30e8)
1063 cbRec, e := NewTxRecordFromMsgTx(cb, b100.Time)
1064 if e != nil {
1065 t.Fatal(e)
1066 }
1067 // Insert coinbase and mark both outputs as credits.
1068 e = s.InsertTx(ns, cbRec, &b100)
1069 if e != nil {
1070 t.Fatal(e)
1071 }
1072 e = s.AddCredit(ns, cbRec, &b100, 0, false)
1073 if e != nil {
1074 t.Fatal(e)
1075 }
1076 e = s.AddCredit(ns, cbRec, &b100, 1, false)
1077 if e != nil {
1078 t.Fatal(e)
1079 }
1080 // Create and insert two unmined transactions which spend both coinbase
1081 // outputs.
1082 spenderATime := time.Now()
1083 spenderA := spendOutput(&cbRec.Hash, 0, 1e8, 2e8, 18e8)
1084 spenderARec, e := NewTxRecordFromMsgTx(spenderA, spenderATime)
1085 if e != nil {
1086 t.Fatal(e)
1087 }
1088 e = s.InsertTx(ns, spenderARec, nil)
1089 if e != nil {
1090 t.Fatal(e)
1091 }
1092 e = s.AddCredit(ns, spenderARec, nil, 0, false)
1093 if e != nil {
1094 t.Fatal(e)
1095 }
1096 e = s.AddCredit(ns, spenderARec, nil, 1, false)
1097 if e != nil {
1098 t.Fatal(e)
1099 }
1100 spenderBTime := time.Now()
1101 spenderB := spendOutput(&cbRec.Hash, 1, 4e8, 8e8, 18e8)
1102 spenderBRec, e := NewTxRecordFromMsgTx(spenderB, spenderBTime)
1103 if e != nil {
1104 t.Fatal(e)
1105 }
1106 e = s.InsertTx(ns, spenderBRec, nil)
1107 if e != nil {
1108 t.Fatal(e)
1109 }
1110 e = s.AddCredit(ns, spenderBRec, nil, 0, false)
1111 if e != nil {
1112 t.Fatal(e)
1113 }
1114 e = s.AddCredit(ns, spenderBRec, nil, 1, false)
1115 if e != nil {
1116 t.Fatal(e)
1117 }
1118 coinbaseMaturity := int32(chaincfg.TestNet3Params.CoinbaseMaturity)
1119 // Mine both transactions in the block that matures the coinbase.
1120 bMaturity := BlockMeta{
1121 Block: Block{Height: b100.Height + coinbaseMaturity},
1122 Time: time.Now(),
1123 }
1124 e = s.InsertTx(ns, spenderARec, &bMaturity)
1125 if e != nil {
1126 t.Fatal(e)
1127 }
1128 e = s.InsertTx(ns, spenderBRec, &bMaturity)
1129 if e != nil {
1130 t.Fatal(e)
1131 }
1132 // Chk that both transactions can be queried at the maturity block.
1133 detailsA, e := s.UniqueTxDetails(ns, &spenderARec.Hash, &bMaturity.Block)
1134 if e != nil {
1135 t.Fatal(e)
1136 }
1137 if detailsA == nil {
1138 t.Fatal("No details found for first spender")
1139 }
1140 detailsB, e := s.UniqueTxDetails(ns, &spenderBRec.Hash, &bMaturity.Block)
1141 if e != nil {
1142 t.Fatal(e)
1143 }
1144 if detailsB == nil {
1145 t.Fatal("No details found for second spender")
1146 }
1147 // Verify that the balance was correctly updated on the block record
1148 // append and that no unmined transactions remain.
1149 balTests := []struct {
1150 height int32
1151 minConf int32
1152 bal amt.Amount
1153 }{
1154 // Maturity height
1155 {
1156 height: bMaturity.Height,
1157 minConf: 0,
1158 bal: 15e8,
1159 },
1160 {
1161 height: bMaturity.Height,
1162 minConf: 1,
1163 bal: 15e8,
1164 },
1165 {
1166 height: bMaturity.Height,
1167 minConf: 2,
1168 bal: 0,
1169 },
1170 // Next block after maturity height
1171 {
1172 height: bMaturity.Height + 1,
1173 minConf: 0,
1174 bal: 15e8,
1175 },
1176 {
1177 height: bMaturity.Height + 1,
1178 minConf: 2,
1179 bal: 15e8,
1180 },
1181 {
1182 height: bMaturity.Height + 1,
1183 minConf: 3,
1184 bal: 0,
1185 },
1186 }
1187 for i, tst := range balTests {
1188 var bal amt.Amount
1189 bal, e = s.Balance(ns, tst.minConf, tst.height)
1190 if e != nil {
1191 t.Fatalf("Balance test %d: Store.Balance failed: %v", i, e)
1192 }
1193 if bal != tst.bal {
1194 t.Errorf("Balance test %d: Got %v Expected %v", i, bal, tst.bal)
1195 }
1196 }
1197 if t.Failed() {
1198 t.Fatal("Failed balance checks after moving both coinbase spenders")
1199 }
1200 unminedTxs, e := s.UnminedTxs(ns)
1201 if e != nil {
1202 t.Fatal(e)
1203 }
1204 if len(unminedTxs) != 0 {
1205 t.Fatalf("Should have no unmined transactions mining both, found %d", len(unminedTxs))
1206 }
1207 }
1208
1209 // Test the optional-ness of the serialized transaction in a TxRecord.
1210 // NewTxRecord and NewTxRecordFromMsgTx both save the serialized transaction, so
1211 // manually strip it out to test this code path.
1212 func TestInsertUnserializedTx(t *testing.T) {
1213 t.Parallel()
1214 s, db, teardown, e := testStore()
1215 if e != nil {
1216 t.Fatal(e)
1217 }
1218 defer teardown()
1219 dbtx, e := db.BeginReadWriteTx()
1220 if e != nil {
1221 t.Fatal(e)
1222 }
1223 defer func() {
1224 e = dbtx.Commit()
1225 if e != nil {
1226 t.Log(e)
1227 }
1228 }()
1229 ns := dbtx.ReadWriteBucket(namespaceKey)
1230 tx := newCoinBase(50e8)
1231 rec, e := NewTxRecordFromMsgTx(tx, timeNow())
1232 if e != nil {
1233 t.Fatal(e)
1234 }
1235 b100 := makeBlockMeta(100)
1236 e = s.InsertTx(ns, stripSerializedTx(rec), &b100)
1237 if e != nil {
1238 t.Fatalf("Insert for stripped TxRecord failed: %v", e)
1239 }
1240 // Ensure it can be retreived successfully.
1241 details, e := s.UniqueTxDetails(ns, &rec.Hash, &b100.Block)
1242 if e != nil {
1243 t.Fatal(e)
1244 }
1245 rec2, e := NewTxRecordFromMsgTx(&details.MsgTx, rec.Received)
1246 if e != nil {
1247 t.Fatal(e)
1248 }
1249 if !bytes.Equal(rec.SerializedTx, rec2.SerializedTx) {
1250 t.Fatal("Serialized txs for coinbase do not match")
1251 }
1252 // Now test that path with an unmined transaction.
1253 tx = spendOutput(&rec.Hash, 0, 50e8)
1254 rec, e = NewTxRecordFromMsgTx(tx, timeNow())
1255 if e != nil {
1256 t.Fatal(e)
1257 }
1258 e = s.InsertTx(ns, rec, nil)
1259 if e != nil {
1260 t.Fatal(e)
1261 }
1262 details, e = s.UniqueTxDetails(ns, &rec.Hash, nil)
1263 if e != nil {
1264 t.Fatal(e)
1265 }
1266 rec2, e = NewTxRecordFromMsgTx(&details.MsgTx, rec.Received)
1267 if e != nil {
1268 t.Fatal(e)
1269 }
1270 if !bytes.Equal(rec.SerializedTx, rec2.SerializedTx) {
1271 t.Fatal("Serialized txs for coinbase spender do not match")
1272 }
1273 }
1274
1275 // TestRemoveUnminedTx tests that if we add an umined transaction, then we're
1276 // able to remove that unmined transaction later along with any of its
1277 // descendants. Any balance modifications due to the unmined transaction should
1278 // be revered.
1279 func TestRemoveUnminedTx(t *testing.T) {
1280 t.Parallel()
1281 store, db, teardown, e := testStore()
1282 if e != nil {
1283 t.Fatal(e)
1284 }
1285 defer teardown()
1286 // In order to reproduce real-world scenarios, we'll use a new database transaction for each interaction with the
1287 // wallet.
1288 //
1289 // We'll start off the test by creating a new coinbase output at height 100 and inserting it into the store.
1290 b100 := &BlockMeta{
1291 Block: Block{Height: 100},
1292 Time: time.Now(),
1293 }
1294 initialBalance := int64(1e8)
1295 cb := newCoinBase(initialBalance)
1296 cbRec, e := NewTxRecordFromMsgTx(cb, b100.Time)
1297 if e != nil {
1298 t.Fatal(e)
1299 }
1300 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1301 if e = store.InsertTx(ns, cbRec, b100); E.Chk(e) {
1302 t.Fatal(e)
1303 }
1304 e = store.AddCredit(ns, cbRec, b100, 0, false)
1305 if e != nil {
1306 t.Fatal(e)
1307 }
1308 },
1309 )
1310 // Determine the maturity height for the coinbase output created.
1311 coinbaseMaturity := int32(chaincfg.TestNet3Params.CoinbaseMaturity)
1312 maturityHeight := b100.Block.Height + coinbaseMaturity
1313 // checkBalance is a helper function that compares the balance of the store with the expected value. The
1314 // includeUnconfirmed boolean can be used to include the unconfirmed balance as a part of the total balance.
1315 checkBalance := func(expectedBalance amt.Amount,
1316 includeUnconfirmed bool,
1317 ) {
1318 t.Helper()
1319 minConfs := int32(1)
1320 if includeUnconfirmed {
1321 minConfs = 0
1322 }
1323 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1324 t.Helper()
1325 var b amt.Amount
1326 b, e = store.Balance(ns, minConfs, maturityHeight)
1327 if e != nil {
1328 t.Fatalf("unable to retrieve balance: %v", e)
1329 }
1330 if b != expectedBalance {
1331 t.Fatalf("expected balance of %d, got %d",
1332 expectedBalance, b,
1333 )
1334 }
1335 },
1336 )
1337 }
1338 // Since we don't have any unconfirmed transactions within the store, the total balance reflecting confirmed and
1339 // unconfirmed outputs should match the initial balance.
1340 checkBalance(amt.Amount(initialBalance), false)
1341 checkBalance(amt.Amount(initialBalance), true)
1342 // Then, we'll create an unconfirmed spend for the coinbase output and insert it into the store.
1343 b101 := &BlockMeta{
1344 Block: Block{Height: 201},
1345 Time: time.Now(),
1346 }
1347 changeAmount := int64(4e7)
1348 spendTx := spendOutput(&cbRec.Hash, 0, 5e7, changeAmount)
1349 spendTxRec, e := NewTxRecordFromMsgTx(spendTx, b101.Time)
1350 if e != nil {
1351 t.Fatal(e)
1352 }
1353 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1354 if e := store.InsertTx(ns, spendTxRec, nil); E.Chk(e) {
1355 t.Fatal(e)
1356 }
1357 e := store.AddCredit(ns, spendTxRec, nil, 1, true)
1358 if e != nil {
1359 t.Fatal(e)
1360 }
1361 },
1362 )
1363 // With the unconfirmed spend inserted into the store, we'll query it for its unconfirmed tranasctions to ensure it
1364 // was properly added.
1365 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1366 unminedTxs, e := store.UnminedTxs(ns)
1367 if e != nil {
1368 t.Fatalf("unable to query for unmined txs: %v", e)
1369 }
1370 if len(unminedTxs) != 1 {
1371 t.Fatalf("expected 1 mined tx, instead got %v",
1372 len(unminedTxs),
1373 )
1374 }
1375 unminedTxHash := unminedTxs[0].TxHash()
1376 spendTxHash := spendTx.TxHash()
1377 if !unminedTxHash.IsEqual(&spendTxHash) {
1378 t.Fatalf("mismatch tx hashes: expected %v, got %v",
1379 spendTxHash, unminedTxHash,
1380 )
1381 }
1382 },
1383 )
1384 // Now that an unconfirmed spend exists, there should no longer be any confirmed balance. The total balance should
1385 // now all be unconfirmed and it should match the change amount of the unconfirmed spend tranasction.
1386 checkBalance(0, false)
1387 checkBalance(amt.Amount(changeAmount), true)
1388 // Now, we'll remove the unconfirmed spend tranaction from the store.
1389 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1390 if e := store.RemoveUnminedTx(ns, spendTxRec); E.Chk(e) {
1391 t.Fatal(e)
1392 }
1393 },
1394 )
1395 // We'll query the store one last time for its unconfirmed transactions to ensure the unconfirmed spend was properly
1396 // removed above.
1397 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1398 unminedTxs, e := store.UnminedTxs(ns)
1399 if e != nil {
1400 t.Fatalf("unable to query for unmined txs: %v", e)
1401 }
1402 if len(unminedTxs) != 0 {
1403 t.Fatalf("expected 0 mined txs, instead got %v",
1404 len(unminedTxs),
1405 )
1406 }
1407 },
1408 )
1409 // Finally, the total balance (including confirmed and unconfirmed) should once again match the initial balance, as
1410 // the uncofirmed spend has already been removed.
1411 checkBalance(amt.Amount(initialBalance), false)
1412 checkBalance(amt.Amount(initialBalance), true)
1413 }
1414
1415 // commitDBTx is a helper function that allows us to perform multiple operations on a specific database's bucket as a
1416 // single atomic operation.
1417 func commitDBTx(t *testing.T, store *Store, db walletdb.DB,
1418 f func(walletdb.ReadWriteBucket),
1419 ) {
1420 t.Helper()
1421 dbTx, e := db.BeginReadWriteTx()
1422 if e != nil {
1423 t.Fatal(e)
1424 }
1425 defer func() {
1426 e := dbTx.Commit()
1427 if e != nil {
1428 t.Log(e)
1429 }
1430 }()
1431 ns := dbTx.ReadWriteBucket(namespaceKey)
1432 f(ns)
1433 }
1434
1435 // testInsertDoubleSpendTx is a helper test which double spends an output. The boolean parameter indicates whether the
1436 // first spending transaction should be the one confirmed. This test ensures that when a double spend occurs and both
1437 // spending transactions are present in the mempool, if one of them confirms, then the remaining conflicting
1438 // transactions within the mempool should be removed from the wallet's store.
1439 func testInsertMempoolDoubleSpendTx(t *testing.T, first bool) {
1440 store, db, teardown, e := testStore()
1441 if e != nil {
1442 t.Fatal(e)
1443 }
1444 defer teardown()
1445 // In order to reproduce real-world scenarios, we'll use a new database transaction for each interaction with the
1446 // wallet.
1447 //
1448 // We'll start off the test by creating a new coinbase output at height 100 and inserting it into the store.
1449 b100 := BlockMeta{
1450 Block: Block{Height: 100},
1451 Time: time.Now(),
1452 }
1453 cb := newCoinBase(1e8)
1454 cbRec, e := NewTxRecordFromMsgTx(cb, b100.Time)
1455 if e != nil {
1456 t.Fatal(e)
1457 }
1458 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1459 if e = store.InsertTx(ns, cbRec, &b100); E.Chk(e) {
1460 t.Fatal(e)
1461 }
1462 e = store.AddCredit(ns, cbRec, &b100, 0, false)
1463 if e != nil {
1464 t.Fatal(e)
1465 }
1466 },
1467 )
1468 // Then, we'll create two spends from the same coinbase output, in order to replicate a double spend scenario.
1469 firstSpend := spendOutput(&cbRec.Hash, 0, 5e7, 5e7)
1470 firstSpendRec, e := NewTxRecordFromMsgTx(firstSpend, time.Now())
1471 if e != nil {
1472 t.Fatal(e)
1473 }
1474 secondSpend := spendOutput(&cbRec.Hash, 0, 4e7, 6e7)
1475 secondSpendRec, e := NewTxRecordFromMsgTx(secondSpend, time.Now())
1476 if e != nil {
1477 t.Fatal(e)
1478 }
1479 // We'll insert both of them into the store without confirming them.
1480 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1481 if e := store.InsertTx(ns, firstSpendRec, nil); E.Chk(e) {
1482 t.Fatal(e)
1483 }
1484 e := store.AddCredit(ns, firstSpendRec, nil, 0, false)
1485 if e != nil {
1486 t.Fatal(e)
1487 }
1488 },
1489 )
1490 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1491 if e := store.InsertTx(ns, secondSpendRec, nil); E.Chk(e) {
1492 t.Fatal(e)
1493 }
1494 e := store.AddCredit(ns, secondSpendRec, nil, 0, false)
1495 if e != nil {
1496 t.Fatal(e)
1497 }
1498 },
1499 )
1500 // Ensure that both spends are found within the unconfirmed transactions in the wallet's store.
1501 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1502 unminedTxs, e := store.UnminedTxs(ns)
1503 if e != nil {
1504 t.Fatal(e)
1505 }
1506 if len(unminedTxs) != 2 {
1507 t.Fatalf("expected 2 unmined txs, got %v",
1508 len(unminedTxs),
1509 )
1510 }
1511 },
1512 )
1513 // Then, we'll confirm either the first or second spend, depending on the boolean passed, with a height deep enough
1514 // that allows us to succesfully spend the coinbase output.
1515 coinbaseMaturity := int32(chaincfg.TestNet3Params.CoinbaseMaturity)
1516 bMaturity := BlockMeta{
1517 Block: Block{Height: b100.Height + coinbaseMaturity},
1518 Time: time.Now(),
1519 }
1520 var confirmedSpendRec *TxRecord
1521 if first {
1522 confirmedSpendRec = firstSpendRec
1523 } else {
1524 confirmedSpendRec = secondSpendRec
1525 }
1526 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1527 e := store.InsertTx(ns, confirmedSpendRec, &bMaturity)
1528 if e != nil {
1529 t.Fatal(e)
1530 }
1531 e = store.AddCredit(
1532 ns, confirmedSpendRec, &bMaturity, 0, false,
1533 )
1534 if e != nil {
1535 t.Fatal(e)
1536 }
1537 },
1538 )
1539 // This should now trigger the store to remove any other pending double spends for this coinbase output, as we've
1540 // already seen the confirmed one. Therefore, we shouldn't see any other unconfirmed transactions within it. We also
1541 // ensure that the transaction that confirmed and is now listed as a UTXO within the wallet is the correct one.
1542 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1543 unminedTxs, e := store.UnminedTxs(ns)
1544 if e != nil {
1545 t.Fatal(e)
1546 }
1547 if len(unminedTxs) != 0 {
1548 t.Fatalf("expected 0 unmined txs, got %v",
1549 len(unminedTxs),
1550 )
1551 }
1552 minedTxs, e := store.UnspentOutputs(ns)
1553 if e != nil {
1554 t.Fatal(e)
1555 }
1556 if len(minedTxs) != 1 {
1557 t.Fatalf("expected 1 mined tx, got %v", len(minedTxs))
1558 }
1559 if !minedTxs[0].Hash.IsEqual(&confirmedSpendRec.Hash) {
1560 t.Fatalf("expected confirmed tx hash %v, got %v",
1561 confirmedSpendRec.Hash, minedTxs[0].Hash,
1562 )
1563 }
1564 },
1565 )
1566 }
1567
1568 // TestInsertMempoolDoubleSpendConfirmedFirstTx ensures that when a double spend occurs and both spending transactions
1569 // are present in the mempool, if the first spend seen is confirmed, then the second spend transaction within the
1570 // mempool should be removed from the wallet's store.
1571 func TestInsertMempoolDoubleSpendConfirmedFirstTx(t *testing.T) {
1572 t.Parallel()
1573 testInsertMempoolDoubleSpendTx(t, true)
1574 }
1575
1576 // TestInsertMempoolDoubleSpendConfirmedFirstTx ensures that when a double spend occurs and both spending transactions
1577 // are present in the mempool, if the second spend seen is confirmed, then the first spend transaction within the
1578 // mempool should be removed from the wallet's store.
1579 func TestInsertMempoolDoubleSpendConfirmSecondTx(t *testing.T) {
1580 t.Parallel()
1581 testInsertMempoolDoubleSpendTx(t, false)
1582 }
1583
1584 // TestInsertConfirmedDoubleSpendTx tests that when one or more double spends occur and a spending transaction confirms
1585 // that was not known to the wallet, then the unconfirmed double spends within the mempool should be removed from the
1586 // wallet's store.
1587 func TestInsertConfirmedDoubleSpendTx(t *testing.T) {
1588 t.Parallel()
1589 store, db, teardown, e := testStore()
1590 if e != nil {
1591 t.Fatal(e)
1592 }
1593 defer teardown()
1594 // In order to reproduce real-world scenarios, we'll use a new database transaction for each interaction with the
1595 // wallet.
1596 //
1597 // We'll start off the test by creating a new coinbase output at height 100 and inserting it into the store.
1598 b100 := BlockMeta{
1599 Block: Block{Height: 100},
1600 Time: time.Now(),
1601 }
1602 cb1 := newCoinBase(1e8)
1603 cbRec1, e := NewTxRecordFromMsgTx(cb1, b100.Time)
1604 if e != nil {
1605 t.Fatal(e)
1606 }
1607 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1608 if e = store.InsertTx(ns, cbRec1, &b100); E.Chk(e) {
1609 t.Fatal(e)
1610 }
1611 e = store.AddCredit(ns, cbRec1, &b100, 0, false)
1612 if e != nil {
1613 t.Fatal(e)
1614 }
1615 },
1616 )
1617 // Then, we'll create three spends from the same coinbase output. The first two will remain unconfirmed, while the
1618 // last should confirm and remove the remaining unconfirmed from the wallet's store.
1619 firstSpend1 := spendOutput(&cbRec1.Hash, 0, 5e7)
1620 firstSpendRec1, e := NewTxRecordFromMsgTx(firstSpend1, time.Now())
1621 if e != nil {
1622 t.Fatal(e)
1623 }
1624 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1625 if e = store.InsertTx(ns, firstSpendRec1, nil); E.Chk(e) {
1626 t.Fatal(e)
1627 }
1628 e = store.AddCredit(ns, firstSpendRec1, nil, 0, false)
1629 if e != nil {
1630 t.Fatal(e)
1631 }
1632 },
1633 )
1634 secondSpend1 := spendOutput(&cbRec1.Hash, 0, 4e7)
1635 secondSpendRec1, e := NewTxRecordFromMsgTx(secondSpend1, time.Now())
1636 if e != nil {
1637 t.Fatal(e)
1638 }
1639 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1640 if e = store.InsertTx(ns, secondSpendRec1, nil); E.Chk(e) {
1641 t.Fatal(e)
1642 }
1643 e = store.AddCredit(ns, secondSpendRec1, nil, 0, false)
1644 if e != nil {
1645 t.Fatal(e)
1646 }
1647 },
1648 )
1649 // We'll also create another output and have one unconfirmed and one confirmed spending transaction also spend it.
1650 cb2 := newCoinBase(2e8)
1651 cbRec2, e := NewTxRecordFromMsgTx(cb2, b100.Time)
1652 if e != nil {
1653 t.Fatal(e)
1654 }
1655 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1656 if e = store.InsertTx(ns, cbRec2, &b100); E.Chk(e) {
1657 t.Fatal(e)
1658 }
1659 e = store.AddCredit(ns, cbRec2, &b100, 0, false)
1660 if e != nil {
1661 t.Fatal(e)
1662 }
1663 },
1664 )
1665 firstSpend2 := spendOutput(&cbRec2.Hash, 0, 5e7)
1666 firstSpendRec2, e := NewTxRecordFromMsgTx(firstSpend2, time.Now())
1667 if e != nil {
1668 t.Fatal(e)
1669 }
1670 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1671 if e = store.InsertTx(ns, firstSpendRec2, nil); E.Chk(e) {
1672 t.Fatal(e)
1673 }
1674 e = store.AddCredit(ns, firstSpendRec2, nil, 0, false)
1675 if e != nil {
1676 t.Fatal(e)
1677 }
1678 },
1679 )
1680 // At this point, we should see all unconfirmed transactions within the store.
1681 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1682 var unminedTxs []*wire.MsgTx
1683 unminedTxs, e = store.UnminedTxs(ns)
1684 if e != nil {
1685 t.Fatal(e)
1686 }
1687 if len(unminedTxs) != 3 {
1688 t.Fatalf("expected 3 unmined txs, got %d",
1689 len(unminedTxs),
1690 )
1691 }
1692 },
1693 )
1694 // Then, we'll insert the confirmed spend at a height deep enough that allows us to successfully spend the coinbase
1695 // outputs.
1696 coinbaseMaturity := int32(chaincfg.TestNet3Params.CoinbaseMaturity)
1697 bMaturity := BlockMeta{
1698 Block: Block{Height: b100.Height + coinbaseMaturity},
1699 Time: time.Now(),
1700 }
1701 outputsToSpend := []wire.OutPoint{
1702 {Hash: cbRec1.Hash, Index: 0},
1703 {Hash: cbRec2.Hash, Index: 0},
1704 }
1705 confirmedSpend := spendOutputs(outputsToSpend, 3e7)
1706 confirmedSpendRec, e := NewTxRecordFromMsgTx(
1707 confirmedSpend, bMaturity.Time,
1708 )
1709 if e != nil {
1710 t.Fatal(e)
1711 }
1712 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1713 e := store.InsertTx(ns, confirmedSpendRec, &bMaturity)
1714 if e != nil {
1715 t.Fatal(e)
1716 }
1717 e = store.AddCredit(
1718 ns, confirmedSpendRec, &bMaturity, 0, false,
1719 )
1720 if e != nil {
1721 t.Fatal(e)
1722 }
1723 },
1724 )
1725 // Now that the confirmed spend exists within the store, we should no longer see the unconfirmed spends within it.
1726 // We also ensure that the transaction that confirmed and is now listed as a UTXO within the wallet is the correct
1727 // one.
1728 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1729 unminedTxs, e := store.UnminedTxs(ns)
1730 if e != nil {
1731 t.Fatal(e)
1732 }
1733 if len(unminedTxs) != 0 {
1734 t.Fatalf("expected 0 unmined txs, got %v",
1735 len(unminedTxs),
1736 )
1737 }
1738 minedTxs, e := store.UnspentOutputs(ns)
1739 if e != nil {
1740 t.Fatal(e)
1741 }
1742 if len(minedTxs) != 1 {
1743 t.Fatalf("expected 1 mined tx, got %v", len(minedTxs))
1744 }
1745 if !minedTxs[0].Hash.IsEqual(&confirmedSpendRec.Hash) {
1746 t.Fatalf("expected confirmed tx hash %v, got %v",
1747 confirmedSpend, minedTxs[0].Hash,
1748 )
1749 }
1750 },
1751 )
1752 }
1753
1754 // TestAddDuplicateCreditAfterConfirm aims to test the case where a duplicate unconfirmed credit is added to the store
1755 // after the intial credit has already confirmed. This can lead to outputs being duplicated in the store, which can lead
1756 // to creating double spends when querying the wallet's UTXO set.
1757 func TestAddDuplicateCreditAfterConfirm(t *testing.T) {
1758 t.Parallel()
1759 store, db, teardown, e := testStore()
1760 if e != nil {
1761 t.Fatal(e)
1762 }
1763 defer teardown()
1764 // In order to reproduce real-world scenarios, we'll use a new database transaction for each interaction with the
1765 // wallet.
1766 //
1767 // We'll start off the test by creating a new coinbase output at height 100 and inserting it into the store.
1768 b100 := &BlockMeta{
1769 Block: Block{Height: 100},
1770 Time: time.Now(),
1771 }
1772 cb := newCoinBase(1e8)
1773 cbRec, e := NewTxRecordFromMsgTx(cb, b100.Time)
1774 if e != nil {
1775 t.Fatal(e)
1776 }
1777 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1778 if e = store.InsertTx(ns, cbRec, b100); E.Chk(e) {
1779 t.Fatal(e)
1780 }
1781 e = store.AddCredit(ns, cbRec, b100, 0, false)
1782 if e != nil {
1783 t.Fatal(e)
1784 }
1785 },
1786 )
1787 // We'll confirm that there is one unspent output in the store, which should be the coinbase output created above.
1788 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1789 var minedTxs []Credit
1790 minedTxs, e = store.UnspentOutputs(ns)
1791 if e != nil {
1792 t.Fatal(e)
1793 }
1794 if len(minedTxs) != 1 {
1795 t.Fatalf("expected 1 mined tx, got %v", len(minedTxs))
1796 }
1797 if !minedTxs[0].Hash.IsEqual(&cbRec.Hash) {
1798 t.Fatalf("expected tx hash %v, got %v", cbRec.Hash,
1799 minedTxs[0].Hash,
1800 )
1801 }
1802 },
1803 )
1804 // Then, we'll create an unconfirmed spend for the coinbase output.
1805 b101 := &BlockMeta{
1806 Block: Block{Height: 101},
1807 Time: time.Now(),
1808 }
1809 spendTx := spendOutput(&cbRec.Hash, 0, 5e7, 4e7)
1810 spendTxRec, e := NewTxRecordFromMsgTx(spendTx, b101.Time)
1811 if e != nil {
1812 t.Fatal(e)
1813 }
1814 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1815 if e := store.InsertTx(ns, spendTxRec, nil); E.Chk(e) {
1816 t.Fatal(e)
1817 }
1818 e := store.AddCredit(ns, spendTxRec, nil, 1, true)
1819 if e != nil {
1820 t.Fatal(e)
1821 }
1822 },
1823 )
1824 // Confirm the spending transaction at the next height.
1825 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1826 if e := store.InsertTx(ns, spendTxRec, b101); E.Chk(e) {
1827 t.Fatal(e)
1828 }
1829 e := store.AddCredit(ns, spendTxRec, b101, 1, true)
1830 if e != nil {
1831 t.Fatal(e)
1832 }
1833 },
1834 )
1835 // We should see one unspent output within the store once again, this time being the change output of the spending
1836 // transaction.
1837 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1838 minedTxs, e := store.UnspentOutputs(ns)
1839 if e != nil {
1840 t.Fatal(e)
1841 }
1842 if len(minedTxs) != 1 {
1843 t.Fatalf("expected 1 mined txs, got %v", len(minedTxs))
1844 }
1845 if !minedTxs[0].Hash.IsEqual(&spendTxRec.Hash) {
1846 t.Fatalf("expected tx hash %v, got %v", spendTxRec.Hash,
1847 minedTxs[0].Hash,
1848 )
1849 }
1850 },
1851 )
1852 // Now, we'll insert the spending transaction once again, this time as unconfirmed. This can happen if the backend
1853 // happens to forward an unconfirmed chain.RelevantTx notification to the client even after it has confirmed, which
1854 // results in us adding it to the store once again.
1855 //
1856 // TODO(wilmer): ideally this shouldn't happen, so we should identify the real reason for this.
1857 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1858 if e := store.InsertTx(ns, spendTxRec, nil); E.Chk(e) {
1859 t.Fatal(e)
1860 }
1861 e := store.AddCredit(ns, spendTxRec, nil, 1, true)
1862 if e != nil {
1863 t.Fatal(e)
1864 }
1865 },
1866 )
1867 // Finally, we'll ensure the change output is still the only unspent output within the store.
1868 commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
1869 minedTxs, e := store.UnspentOutputs(ns)
1870 if e != nil {
1871 t.Fatal(e)
1872 }
1873 if len(minedTxs) != 1 {
1874 t.Fatalf("expected 1 mined txs, got %v", len(minedTxs))
1875 }
1876 if !minedTxs[0].Hash.IsEqual(&spendTxRec.Hash) {
1877 t.Fatalf("expected tx hash %v, got %v", spendTxRec.Hash,
1878 minedTxs[0].Hash,
1879 )
1880 }
1881 },
1882 )
1883 }
1884