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