query_test.go raw
1 // Copyright (c) 2015-2017 The btcsuite developers
2 package wtxmgr
3
4 import (
5 "bytes"
6 "encoding/binary"
7 "errors"
8 "fmt"
9 "github.com/p9c/p9/pkg/amt"
10 "testing"
11 "time"
12
13 "github.com/p9c/p9/pkg/chainhash"
14 "github.com/p9c/p9/pkg/walletdb"
15 "github.com/p9c/p9/pkg/wire"
16 )
17
18 type queryState struct {
19 // slice items are ordered by height, mempool comes last.
20 blocks [][]TxDetails
21 txDetails map[chainhash.Hash][]TxDetails
22 }
23
24 func newQueryState() *queryState {
25 return &queryState{
26 txDetails: make(map[chainhash.Hash][]TxDetails),
27 }
28 }
29 func (q *queryState) deepCopy() *queryState {
30 cpy := newQueryState()
31 for _, blockDetails := range q.blocks {
32 var cpyDetails []TxDetails
33 for _, detail := range blockDetails {
34 cpyDetails = append(cpyDetails, *deepCopyTxDetails(&detail))
35 }
36 cpy.blocks = append(cpy.blocks, cpyDetails)
37 }
38 cpy.txDetails = make(map[chainhash.Hash][]TxDetails)
39 for txHash, details := range q.txDetails {
40 detailsSlice := make([]TxDetails, len(details))
41 for i, detail := range details {
42 detailsSlice[i] = *deepCopyTxDetails(&detail)
43 }
44 cpy.txDetails[txHash] = detailsSlice
45 }
46 return cpy
47 }
48 func deepCopyTxDetails(d *TxDetails) *TxDetails {
49 cpy := *d
50 cpy.MsgTx = *d.MsgTx.Copy()
51 if cpy.SerializedTx != nil {
52 cpy.SerializedTx = make([]byte, len(cpy.SerializedTx))
53 copy(cpy.SerializedTx, d.SerializedTx)
54 }
55 cpy.Credits = make([]CreditRecord, len(d.Credits))
56 copy(cpy.Credits, d.Credits)
57 cpy.Debits = make([]DebitRecord, len(d.Debits))
58 copy(cpy.Debits, d.Debits)
59 return &cpy
60 }
61 func (q *queryState) compare(
62 s *Store, ns walletdb.ReadBucket,
63 changeDesc string,
64 ) (e error) {
65 fwdBlocks := q.blocks
66 revBlocks := make([][]TxDetails, len(q.blocks))
67 copy(revBlocks, q.blocks)
68 for i := 0; i < len(revBlocks)/2; i++ {
69 revBlocks[i], revBlocks[len(revBlocks)-1-i] = revBlocks[len(revBlocks)-1-i], revBlocks[i]
70 }
71 checkBlock := func(blocks [][]TxDetails) func([]TxDetails) (bool, error) {
72 return func(got []TxDetails) (bool, error) {
73 if len(fwdBlocks) == 0 {
74 return false, errors.New(
75 "entered range " +
76 "when no more details expected",
77 )
78 }
79 exp := blocks[0]
80 if len(got) != len(exp) {
81 return false, fmt.Errorf(
82 "got len(details)=%d "+
83 "in transaction range, expected %d",
84 len(got), len(exp),
85 )
86 }
87 for i := range got {
88 e = equalTxDetails(&got[i], &exp[i])
89 if e != nil {
90 return false, fmt.Errorf(
91 "failed "+
92 "comparing range of "+
93 "transaction details: %v", e,
94 )
95 }
96 }
97 blocks = blocks[1:]
98 return false, nil
99 }
100 }
101 e = s.RangeTransactions(ns, 0, -1, checkBlock(fwdBlocks))
102 if e != nil {
103 return fmt.Errorf(
104 "%s: failed in RangeTransactions (forwards "+
105 "iteration): %v", changeDesc, e,
106 )
107 }
108 e = s.RangeTransactions(ns, -1, 0, checkBlock(revBlocks))
109 if e != nil {
110 return fmt.Errorf(
111 "%s: failed in RangeTransactions (reverse "+
112 "iteration): %v", changeDesc, e,
113 )
114 }
115 for txHash, details := range q.txDetails {
116 for _, detail := range details {
117 blk := &detail.Block.Block
118 if blk.Height == -1 {
119 blk = nil
120 }
121 d, e := s.UniqueTxDetails(ns, &txHash, blk)
122 if e != nil {
123 return e
124 }
125 if d == nil {
126 return fmt.Errorf(
127 "found no matching "+
128 "transaction at height %d",
129 detail.Block.Height,
130 )
131 }
132 if e := equalTxDetails(d, &detail); E.Chk(e) {
133 return fmt.Errorf(
134 "%s: failed querying latest "+
135 "details regarding transaction %v",
136 changeDesc, txHash,
137 )
138 }
139 }
140 // For the most recent tx with this hash, check that TxDetails (not looking up a tx at any particular height)
141 // matches the last.
142 detail := &details[len(details)-1]
143 d, e := s.TxDetails(ns, &txHash)
144 if e != nil {
145 return e
146 }
147 if e := equalTxDetails(d, detail); E.Chk(e) {
148 return fmt.Errorf(
149 "%s: failed querying latest details "+
150 "regarding transaction %v", changeDesc, txHash,
151 )
152 }
153 }
154 return nil
155 }
156 func equalTxDetails(got, exp *TxDetails) (e error) {
157 // Need to avoid using reflect.DeepEqual against slices, since it returns false for nil vs non-nil zero length
158 // slices.
159 if e := equalTxs(&got.MsgTx, &exp.MsgTx); E.Chk(e) {
160 return e
161 }
162 if got.Hash != exp.Hash {
163 return fmt.Errorf(
164 "found mismatched hashes: got %v, expected %v",
165 got.Hash, exp.Hash,
166 )
167 }
168 if got.Received != exp.Received {
169 return fmt.Errorf(
170 "found mismatched receive time: got %v, "+
171 "expected %v", got.Received, exp.Received,
172 )
173 }
174 if !bytes.Equal(got.SerializedTx, exp.SerializedTx) {
175 return fmt.Errorf(
176 "found mismatched serialized txs: got %v, "+
177 "expected %v", got.SerializedTx, exp.SerializedTx,
178 )
179 }
180 if got.Block != exp.Block {
181 return fmt.Errorf(
182 "found mismatched block meta: got %v, "+
183 "expected %v", got.Block, exp.Block,
184 )
185 }
186 if len(got.Credits) != len(exp.Credits) {
187 return fmt.Errorf(
188 "credit slice lengths differ: got %d, "+
189 "expected %d", len(got.Credits), len(exp.Credits),
190 )
191 }
192 for i := range got.Credits {
193 if got.Credits[i] != exp.Credits[i] {
194 return fmt.Errorf(
195 "found mismatched credit[%d]: got %v, "+
196 "expected %v", i, got.Credits[i], exp.Credits[i],
197 )
198 }
199 }
200 if len(got.Debits) != len(exp.Debits) {
201 return fmt.Errorf(
202 "debit slice lengths differ: got %d, "+
203 "expected %d", len(got.Debits), len(exp.Debits),
204 )
205 }
206 for i := range got.Debits {
207 if got.Debits[i] != exp.Debits[i] {
208 return fmt.Errorf(
209 "found mismatched debit[%d]: got %v, "+
210 "expected %v", i, got.Debits[i], exp.Debits[i],
211 )
212 }
213 }
214 return nil
215 }
216 func equalTxs(got, exp *wire.MsgTx) (e error) {
217 var bufGot, bufExp bytes.Buffer
218 e = got.Serialize(&bufGot)
219 if e != nil {
220 return e
221 }
222 e = exp.Serialize(&bufExp)
223 if e != nil {
224 return e
225 }
226 if !bytes.Equal(bufGot.Bytes(), bufExp.Bytes()) {
227 return fmt.Errorf(
228 "found unexpected wire.MsgTx: got: %v, "+
229 "expected %v", got, exp,
230 )
231 }
232 return nil
233 }
234
235 // Returns time.Now() with seconds resolution, this is what Store saves.
236 func timeNow() time.Time {
237 return time.Unix(time.Now().Unix(), 0)
238 }
239
240 // Returns a copy of a TxRecord without the serialized tx.
241 func stripSerializedTx(rec *TxRecord) *TxRecord {
242 ret := *rec
243 ret.SerializedTx = nil
244 return &ret
245 }
246 func makeBlockMeta(height int32) BlockMeta {
247 if height == -1 {
248 return BlockMeta{Block: Block{Height: -1}}
249 }
250 b := BlockMeta{
251 Block: Block{Height: height},
252 Time: timeNow(),
253 }
254 // Give it a fake block hash created from the height and time.
255 binary.LittleEndian.PutUint32(b.Hash[0:4], uint32(height))
256 binary.LittleEndian.PutUint64(b.Hash[4:12], uint64(b.Time.Unix()))
257 return b
258 }
259 func TestStoreQueries(t *testing.T) {
260 t.Parallel()
261 type queryTest struct {
262 desc string
263 updates func(ns walletdb.ReadWriteBucket) error
264 state *queryState
265 }
266 var tests []queryTest
267 // Create the store and test initial state.
268 var s *Store
269 var db walletdb.DB
270 var teardown func()
271 var e error
272 if s, db, teardown, e = testStore(); !E.Chk(e) {
273 defer teardown()
274 } else {
275 t.Fatal(e)
276 }
277 lastState := newQueryState()
278 tests = append(
279 tests, queryTest{
280 desc: "initial store",
281 updates: func(walletdb.ReadWriteBucket) (e error) { return nil },
282 state: lastState,
283 },
284 )
285 // Insert an unmined transaction. Mark no credits yet.
286 txA := spendOutput(&chainhash.Hash{}, 0, 100e8)
287 recA, e := NewTxRecordFromMsgTx(txA, timeNow())
288 if e != nil {
289 t.Fatal(e)
290 }
291 newState := lastState.deepCopy()
292 newState.blocks = [][]TxDetails{
293 {
294 {
295 TxRecord: *stripSerializedTx(recA),
296 Block: BlockMeta{Block: Block{Height: -1}},
297 },
298 },
299 }
300 newState.txDetails[recA.Hash] = []TxDetails{
301 newState.blocks[0][0],
302 }
303 lastState = newState
304 tests = append(
305 tests, queryTest{
306 desc: "insert tx A unmined",
307 updates: func(ns walletdb.ReadWriteBucket) (e error) {
308 return s.InsertTx(ns, recA, nil)
309 },
310 state: newState,
311 },
312 )
313 // Add txA:0 as a change credit.
314 newState = lastState.deepCopy()
315 newState.blocks[0][0].Credits = []CreditRecord{
316 {
317 Index: 0,
318 Amount: amt.Amount(recA.MsgTx.TxOut[0].Value),
319 Spent: false,
320 Change: true,
321 },
322 }
323 newState.txDetails[recA.Hash][0].Credits = newState.blocks[0][0].Credits
324 lastState = newState
325 tests = append(
326 tests, queryTest{
327 desc: "mark unconfirmed txA:0 as credit",
328 updates: func(ns walletdb.ReadWriteBucket) (e error) {
329 return s.AddCredit(ns, recA, nil, 0, true)
330 },
331 state: newState,
332 },
333 )
334 // Insert another unmined transaction which spends txA:0, splitting the amount into outputs of 40 and 60 DUO.
335 txB := spendOutput(&recA.Hash, 0, 40e8, 60e8)
336 recB, e := NewTxRecordFromMsgTx(txB, timeNow())
337 if e != nil {
338 t.Fatal(e)
339 }
340 newState = lastState.deepCopy()
341 newState.blocks[0][0].Credits[0].Spent = true
342 newState.blocks[0] = append(
343 newState.blocks[0], TxDetails{
344 TxRecord: *stripSerializedTx(recB),
345 Block: BlockMeta{Block: Block{Height: -1}},
346 Debits: []DebitRecord{
347 {
348 Amount: amt.Amount(recA.MsgTx.TxOut[0].Value),
349 Index: 0, // recB.MsgTx.TxIn index
350 },
351 },
352 },
353 )
354 newState.txDetails[recA.Hash][0].Credits[0].Spent = true
355 newState.txDetails[recB.Hash] = []TxDetails{newState.blocks[0][1]}
356 lastState = newState
357 tests = append(
358 tests, queryTest{
359 desc: "insert tx B unmined",
360 updates: func(ns walletdb.ReadWriteBucket) (e error) {
361 return s.InsertTx(ns, recB, nil)
362 },
363 state: newState,
364 },
365 )
366 newState = lastState.deepCopy()
367 newState.blocks[0][1].Credits = []CreditRecord{
368 {
369 Index: 0,
370 Amount: amt.Amount(recB.MsgTx.TxOut[0].Value),
371 Spent: false,
372 Change: false,
373 },
374 }
375 newState.txDetails[recB.Hash][0].Credits = newState.blocks[0][1].Credits
376 lastState = newState
377 tests = append(
378 tests, queryTest{
379 desc: "mark txB:0 as non-change credit",
380 updates: func(ns walletdb.ReadWriteBucket) (e error) {
381 return s.AddCredit(ns, recB, nil, 0, false)
382 },
383 state: newState,
384 },
385 )
386 // Mine tx A at block 100. Leave tx B unmined.
387 b100 := makeBlockMeta(100)
388 newState = lastState.deepCopy()
389 newState.blocks[0] = newState.blocks[0][:1]
390 newState.blocks[0][0].Block = b100
391 newState.blocks = append(newState.blocks, lastState.blocks[0][1:])
392 newState.txDetails[recA.Hash][0].Block = b100
393 lastState = newState
394 tests = append(
395 tests, queryTest{
396 desc: "mine tx A",
397 updates: func(ns walletdb.ReadWriteBucket) (e error) {
398 return s.InsertTx(ns, recA, &b100)
399 },
400 state: newState,
401 },
402 )
403 // Mine tx B at block 101.
404 b101 := makeBlockMeta(101)
405 newState = lastState.deepCopy()
406 newState.blocks[1][0].Block = b101
407 newState.txDetails[recB.Hash][0].Block = b101
408 lastState = newState
409 tests = append(
410 tests, queryTest{
411 desc: "mine tx B",
412 updates: func(ns walletdb.ReadWriteBucket) (e error) {
413 return s.InsertTx(ns, recB, &b101)
414 },
415 state: newState,
416 },
417 )
418 for _, tst := range tests {
419 e = walletdb.Update(
420 db, func(tx walletdb.ReadWriteTx) (e error) {
421 ns := tx.ReadWriteBucket(namespaceKey)
422 if e := tst.updates(ns); E.Chk(e) {
423 return e
424 }
425 return tst.state.compare(s, ns, tst.desc)
426 },
427 )
428 if e != nil {
429 t.Fatal(e)
430 }
431 }
432 // Run some additional query tests with the current store's state:
433 //
434 // - Verify that querying for a transaction not in the store returns nil without failure.
435 // - Verify that querying for a unique transaction at the wrong block returns nil without failure.
436 // - Verify that breaking early on RangeTransactions stops further iteration.
437 e = walletdb.Update(
438 db, func(tx walletdb.ReadWriteTx) (e error) {
439 ns := tx.ReadWriteBucket(namespaceKey)
440 missingTx := spendOutput(&recB.Hash, 0, 40e8)
441 missingRec, e := NewTxRecordFromMsgTx(missingTx, timeNow())
442 if e != nil {
443 return e
444 }
445 missingBlock := makeBlockMeta(102)
446 missingDetails, e := s.TxDetails(ns, &missingRec.Hash)
447 if e != nil {
448 return e
449 }
450 if missingDetails != nil {
451 return fmt.Errorf("expected no details, found details for tx %v", missingDetails.Hash)
452 }
453 missingUniqueTests := []struct {
454 hash *chainhash.Hash
455 block *Block
456 }{
457 {&missingRec.Hash, &b100.Block},
458 {&missingRec.Hash, &missingBlock.Block},
459 {&missingRec.Hash, nil},
460 {&recB.Hash, &b100.Block},
461 {&recB.Hash, &missingBlock.Block},
462 {&recB.Hash, nil},
463 }
464 for _, tst := range missingUniqueTests {
465 missingDetails, e = s.UniqueTxDetails(ns, tst.hash, tst.block)
466 if e != nil {
467 t.Fatal(e)
468 }
469 if missingDetails != nil {
470 t.Errorf("Expected no details, found details for tx %v", missingDetails.Hash)
471 }
472 }
473 iterations := 0
474 e = s.RangeTransactions(
475 ns, 0, -1, func([]TxDetails) (bool, error) {
476 iterations++
477 return true, nil
478 },
479 )
480 if e != nil {
481 t.Log(e)
482 }
483 if iterations != 1 {
484 t.Errorf("RangeTransactions (forwards) ran func %d times", iterations)
485 }
486 iterations = 0
487 e = s.RangeTransactions(
488 ns, -1, 0, func([]TxDetails) (bool, error) {
489 iterations++
490 return true, nil
491 },
492 )
493 if e != nil {
494 t.Log(e)
495 }
496 if iterations != 1 {
497 t.Errorf("RangeTransactions (reverse) ran func %d times", iterations)
498 }
499 // Make sure it also breaks early after one iteration through unmined transactions.
500 if e = s.Rollback(ns, b101.Height); E.Chk(e) {
501 return e
502 }
503 iterations = 0
504 e = s.RangeTransactions(
505 ns, -1, 0, func([]TxDetails) (bool, error) {
506 iterations++
507 return true, nil
508 },
509 )
510 if e != nil {
511 t.Log(e)
512 }
513 if iterations != 1 {
514 t.Errorf("RangeTransactions (reverse) ran func %d times", iterations)
515 }
516 return nil
517 },
518 )
519 if e != nil {
520 t.Fatal(e)
521 }
522 // None of the above tests have tested RangeTransactions with multiple txs per block, so do that now. Start by
523 // moving tx B to block 100 (same block as tx A), and then rollback from block 100 onwards so both are unmined.
524 newState = lastState.deepCopy()
525 newState.blocks[0] = append(newState.blocks[0], newState.blocks[1]...)
526 newState.blocks[0][1].Block = b100
527 newState.blocks = newState.blocks[:1]
528 newState.txDetails[recB.Hash][0].Block = b100
529 lastState = newState
530 tests = append(
531 tests[:0:0], queryTest{
532 desc: "move tx B to block 100",
533 updates: func(ns walletdb.ReadWriteBucket) (e error) {
534 return s.InsertTx(ns, recB, &b100)
535 },
536 state: newState,
537 },
538 )
539 newState = lastState.deepCopy()
540 newState.blocks[0][0].Block = makeBlockMeta(-1)
541 newState.blocks[0][1].Block = makeBlockMeta(-1)
542 newState.txDetails[recA.Hash][0].Block = makeBlockMeta(-1)
543 newState.txDetails[recB.Hash][0].Block = makeBlockMeta(-1)
544 lastState = newState
545 tests = append(
546 tests, queryTest{
547 desc: "rollback block 100",
548 updates: func(ns walletdb.ReadWriteBucket) (e error) {
549 return s.Rollback(ns, b100.Height)
550 },
551 state: newState,
552 },
553 )
554 for _, tst := range tests {
555 e := walletdb.Update(
556 db, func(tx walletdb.ReadWriteTx) (e error) {
557 ns := tx.ReadWriteBucket(namespaceKey)
558 if e := tst.updates(ns); E.Chk(e) {
559 return e
560 }
561 return tst.state.compare(s, ns, tst.desc)
562 },
563 )
564 if e != nil {
565 t.Fatal(e)
566 }
567 }
568 }
569 func TestPreviousPkScripts(t *testing.T) {
570 t.Parallel()
571 var e error
572 var teardown func()
573 var db walletdb.DB
574 var s *Store
575 if s, db, teardown, e = testStore(); !E.Chk(e) {
576 defer teardown()
577 } else {
578 t.Fatal(e)
579 }
580 // Invalid scripts but sufficient for testing.
581 var (
582 scriptA0 = []byte("tx A output 0")
583 scriptA1 = []byte("tx A output 1")
584 scriptB0 = []byte("tx B output 0")
585 scriptB1 = []byte("tx B output 1")
586 scriptC0 = []byte("tx C output 0")
587 scriptC1 = []byte("tx C output 1")
588 )
589 // Create a transaction spending two prevous outputs and generating two new outputs the passed pkScipts. Spends
590 // outputs 0 and 1 from prevHash.
591 buildTx := func(prevHash *chainhash.Hash, script0, script1 []byte) *wire.MsgTx {
592 return &wire.MsgTx{
593 TxIn: []*wire.TxIn{
594 {
595 PreviousOutPoint: wire.OutPoint{
596 Hash: *prevHash,
597 Index: 0,
598 },
599 },
600 {
601 PreviousOutPoint: wire.OutPoint{
602 Hash: *prevHash, Index: 1,
603 },
604 },
605 },
606 TxOut: []*wire.TxOut{
607 {Value: 1e8, PkScript: script0},
608 {Value: 1e8, PkScript: script1},
609 },
610 }
611 }
612 newTxRecordFromMsgTx := func(tx *wire.MsgTx) *TxRecord {
613 var rec *TxRecord
614 rec, e = NewTxRecordFromMsgTx(tx, timeNow())
615 if e != nil {
616 t.Fatal(e)
617 }
618 return rec
619 }
620 // Create transactions with the fake output scripts.
621 var (
622 txA = buildTx(&chainhash.Hash{}, scriptA0, scriptA1)
623 recA = newTxRecordFromMsgTx(txA)
624 txB = buildTx(&recA.Hash, scriptB0, scriptB1)
625 recB = newTxRecordFromMsgTx(txB)
626 txC = buildTx(&recB.Hash, scriptC0, scriptC1)
627 recC = newTxRecordFromMsgTx(txC)
628 txD = buildTx(&recC.Hash, nil, nil)
629 recD = newTxRecordFromMsgTx(txD)
630 )
631 insertTx := func(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) {
632 e = s.InsertTx(ns, rec, block)
633 if e != nil {
634 t.Fatal(e)
635 }
636 }
637 addCredit := func(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32) {
638 e = s.AddCredit(ns, rec, block, index, false)
639 if e != nil {
640 t.Fatal(e)
641 }
642 }
643 type scriptTest struct {
644 rec *TxRecord
645 block *Block
646 scripts [][]byte
647 }
648 runTest := func(ns walletdb.ReadWriteBucket, tst *scriptTest) {
649 var scripts [][]byte
650 scripts, e = s.PreviousPkScripts(ns, tst.rec, tst.block)
651 if e != nil {
652 t.Fatal(e)
653 }
654 height := int32(-1)
655 if tst.block != nil {
656 height = tst.block.Height
657 }
658 if len(scripts) != len(tst.scripts) {
659 t.Errorf(
660 "Transaction %v height %d: got len(scripts)=%d, expected %d",
661 tst.rec.Hash, height, len(scripts), len(tst.scripts),
662 )
663 return
664 }
665 for i := range scripts {
666 if !bytes.Equal(scripts[i], tst.scripts[i]) {
667 // Format scripts with %s since they are (should be) ascii.
668 t.Errorf(
669 "Transaction %v height %d script %d: got '%s' expected '%s'",
670 tst.rec.Hash, height, i, scripts[i], tst.scripts[i],
671 )
672 }
673 }
674 }
675 dbtx, e := db.BeginReadWriteTx()
676 if e != nil {
677 t.Fatal(e)
678 }
679 defer func() {
680 e := dbtx.Commit()
681 if e != nil {
682 t.Log(e)
683 }
684 }()
685 ns := dbtx.ReadWriteBucket(namespaceKey)
686 // Insert transactions A-C unmined, but mark no credits yet. Until these are marked as credits, PreviousPkScripts
687 // should not return them.
688 insertTx(ns, recA, nil)
689 insertTx(ns, recB, nil)
690 insertTx(ns, recC, nil)
691 b100 := makeBlockMeta(100)
692 b101 := makeBlockMeta(101)
693 tests := []scriptTest{
694 {recA, nil, nil},
695 {recA, &b100.Block, nil},
696 {recB, nil, nil},
697 {recB, &b100.Block, nil},
698 {recC, nil, nil},
699 {recC, &b100.Block, nil},
700 }
701 for _, tst := range tests {
702 runTest(ns, &tst)
703 }
704 if t.Failed() {
705 t.Fatal("Failed after unmined tx inserts")
706 }
707 // Mark credits. Tx C output 1 not marked as a credit: tx D will spend both later but when C is mined, output 1's
708 // script should not be returned.
709 addCredit(ns, recA, nil, 0)
710 addCredit(ns, recA, nil, 1)
711 addCredit(ns, recB, nil, 0)
712 addCredit(ns, recB, nil, 1)
713 addCredit(ns, recC, nil, 0)
714 tests = []scriptTest{
715 {recA, nil, nil},
716 {recA, &b100.Block, nil},
717 {recB, nil, [][]byte{scriptA0, scriptA1}},
718 {recB, &b100.Block, nil},
719 {recC, nil, [][]byte{scriptB0, scriptB1}},
720 {recC, &b100.Block, nil},
721 }
722 for _, tst := range tests {
723 runTest(ns, &tst)
724 }
725 if t.Failed() {
726 t.Fatal("Failed after marking unmined credits")
727 }
728 // Mine tx A in block 100. Test results should be identical.
729 insertTx(ns, recA, &b100)
730 for _, tst := range tests {
731 runTest(ns, &tst)
732 }
733 if t.Failed() {
734 t.Fatal("Failed after mining tx A")
735 }
736 // Mine tx B in block 101.
737 insertTx(ns, recB, &b101)
738 tests = []scriptTest{
739 {recA, nil, nil},
740 {recA, &b100.Block, nil},
741 {recB, nil, nil},
742 {recB, &b101.Block, [][]byte{scriptA0, scriptA1}},
743 {recC, nil, [][]byte{scriptB0, scriptB1}},
744 {recC, &b101.Block, nil},
745 }
746 for _, tst := range tests {
747 runTest(ns, &tst)
748 }
749 if t.Failed() {
750 t.Fatal("Failed after mining tx B")
751 }
752 // Mine tx C in block 101 (same block as tx B) to test debits from the same block.
753 insertTx(ns, recC, &b101)
754 tests = []scriptTest{
755 {recA, nil, nil},
756 {recA, &b100.Block, nil},
757 {recB, nil, nil},
758 {recB, &b101.Block, [][]byte{scriptA0, scriptA1}},
759 {recC, nil, nil},
760 {recC, &b101.Block, [][]byte{scriptB0, scriptB1}},
761 }
762 for _, tst := range tests {
763 runTest(ns, &tst)
764 }
765 if t.Failed() {
766 t.Fatal("Failed after mining tx C")
767 }
768 // Insert tx D, which spends C:0 and C:1. However, only C:0 is marked as a credit, and only that output script
769 // should be returned.
770 insertTx(ns, recD, nil)
771 tests = append(tests, scriptTest{recD, nil, [][]byte{scriptC0}})
772 tests = append(tests, scriptTest{recD, &b101.Block, nil})
773 for _, tst := range tests {
774 runTest(ns, &tst)
775 }
776 if t.Failed() {
777 t.Fatal("Failed after inserting tx D")
778 }
779 }
780