decred.org/dcrwallet/v3@v3.1.0/wallet/udb/tx_test.go (about)

     1  // Copyright (c) 2013-2015 The btcsuite developers
     2  // Copyright (c) 2019 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package udb
     7  
     8  import (
     9  	"context"
    10  	"testing"
    11  	"time"
    12  
    13  	_ "decred.org/dcrwallet/v3/wallet/drivers/bdb"
    14  	"decred.org/dcrwallet/v3/wallet/walletdb"
    15  	"github.com/decred/dcrd/chaincfg/chainhash"
    16  	"github.com/decred/dcrd/dcrutil/v4"
    17  	"github.com/decred/dcrd/wire"
    18  )
    19  
    20  func TestInsertsCreditsDebitsRollbacks(t *testing.T) {
    21  	ctx := context.Background()
    22  	db, _, s, _, teardown, err := cloneDB(ctx, "inserts_credits_debits_rollbacks.kv")
    23  	defer teardown()
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  
    28  	g := makeBlockGenerator()
    29  	b1H := g.generate(dcrutil.BlockValid)
    30  	b1Hash := b1H.BlockHash()
    31  	b2H := g.generate(dcrutil.BlockValid)
    32  	b2Hash := b2H.BlockHash()
    33  	b3H := g.generate(dcrutil.BlockValid)
    34  	headerData := makeHeaderDataSlice(b1H, b2H, b3H)
    35  	filters := emptyFilters(3)
    36  
    37  	tx1 := wire.MsgTx{TxOut: []*wire.TxOut{{Value: 2e8}}}
    38  	tx1Rec, err := NewTxRecordFromMsgTx(&tx1, time.Time{})
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  
    43  	sTx1 := wire.MsgTx{
    44  		TxIn: []*wire.TxIn{{
    45  			PreviousOutPoint: wire.OutPoint{
    46  				Hash:  tx1.TxHash(),
    47  				Index: 0,
    48  				Tree:  wire.TxTreeRegular,
    49  			},
    50  			ValueIn:     tx1Rec.MsgTx.TxOut[0].Value,
    51  			BlockHeight: b2H.Height,
    52  			BlockIndex:  0,
    53  		}},
    54  		TxOut: []*wire.TxOut{{Value: tx1Rec.MsgTx.TxOut[0].Value}},
    55  	}
    56  	sTx1Rec, err := NewTxRecordFromMsgTx(&sTx1, time.Time{})
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  
    61  	tx2 := wire.MsgTx{TxOut: []*wire.TxOut{{Value: 3e8}}}
    62  	tx2Rec, err := NewTxRecordFromMsgTx(&tx2, time.Time{})
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	sTx2 := wire.MsgTx{
    68  		TxIn: []*wire.TxIn{{
    69  			PreviousOutPoint: wire.OutPoint{
    70  				Hash:  tx2.TxHash(),
    71  				Index: 0,
    72  				Tree:  wire.TxTreeRegular,
    73  			},
    74  			ValueIn:     tx2Rec.MsgTx.TxOut[0].Value,
    75  			BlockHeight: b3H.Height,
    76  			BlockIndex:  0,
    77  		}},
    78  		TxOut: []*wire.TxOut{{Value: tx2Rec.MsgTx.TxOut[0].Value}},
    79  	}
    80  	sTx2Rec, err := NewTxRecordFromMsgTx(&sTx2, time.Time{})
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	err = walletdb.Update(ctx, db, func(dbtx walletdb.ReadWriteTx) error {
    86  		err = insertMainChainHeaders(s, dbtx, headerData, filters)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		return nil
    91  	})
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	defaultAccount := uint32(0)
    97  	tests := []struct {
    98  		name     string
    99  		f        func(*Store, walletdb.ReadWriteTx) (*Store, error)
   100  		bal, unc dcrutil.Amount
   101  		unspents map[wire.OutPoint]struct{}
   102  		unmined  map[chainhash.Hash]struct{}
   103  	}{
   104  		{
   105  			name: "new store",
   106  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   107  				return s, nil
   108  			},
   109  			bal:      0,
   110  			unc:      0,
   111  			unspents: map[wire.OutPoint]struct{}{},
   112  			unmined:  map[chainhash.Hash]struct{}{},
   113  		},
   114  		{
   115  			name: "txout insert",
   116  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   117  				err = s.InsertMemPoolTx(dbtx, tx1Rec)
   118  				if err != nil {
   119  					return nil, err
   120  				}
   121  
   122  				err = s.AddCredit(dbtx, tx1Rec, nil, 0, false, defaultAccount)
   123  				return s, err
   124  			},
   125  			bal: 0,
   126  			unc: dcrutil.Amount(tx1Rec.MsgTx.TxOut[0].Value),
   127  			unspents: map[wire.OutPoint]struct{}{
   128  				{
   129  					Hash:  tx1Rec.Hash,
   130  					Index: 0,
   131  					Tree:  wire.TxTreeRegular,
   132  				}: {},
   133  			},
   134  			unmined: map[chainhash.Hash]struct{}{
   135  				tx1Rec.Hash: {},
   136  			},
   137  		},
   138  		{
   139  			name: "insert duplicate unconfirmed",
   140  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   141  				err = s.InsertMemPoolTx(dbtx, tx1Rec)
   142  				return s, err
   143  			},
   144  			bal: 0,
   145  			unc: dcrutil.Amount(tx1Rec.MsgTx.TxOut[0].Value),
   146  			unspents: map[wire.OutPoint]struct{}{
   147  				{
   148  					Hash:  tx1Rec.Hash,
   149  					Index: 0,
   150  					Tree:  wire.TxTreeRegular,
   151  				}: {},
   152  			},
   153  			unmined: map[chainhash.Hash]struct{}{
   154  				tx1Rec.Hash: {},
   155  			},
   156  		},
   157  		{
   158  			name: "confirmed txout insert",
   159  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   160  				err = s.InsertMinedTx(dbtx, tx1Rec, &b1Hash)
   161  				return s, err
   162  			},
   163  			bal: dcrutil.Amount(tx1Rec.MsgTx.TxOut[0].Value),
   164  			unc: 0,
   165  			unspents: map[wire.OutPoint]struct{}{
   166  				{
   167  					Hash:  tx1Rec.Hash,
   168  					Index: 0,
   169  					Tree:  wire.TxTreeRegular,
   170  				}: {},
   171  			},
   172  			unmined: map[chainhash.Hash]struct{}{},
   173  		},
   174  		{
   175  			name: "rollback confirmed credit",
   176  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   177  				err := s.Rollback(dbtx, int32(b1H.Height))
   178  				return s, err
   179  			},
   180  			bal: 0,
   181  			unc: dcrutil.Amount(tx1Rec.MsgTx.TxOut[0].Value),
   182  			unspents: map[wire.OutPoint]struct{}{
   183  				{
   184  					Hash:  tx1Rec.Hash,
   185  					Index: 0,
   186  					Tree:  wire.TxTreeRegular,
   187  				}: {},
   188  			},
   189  			unmined: map[chainhash.Hash]struct{}{
   190  				tx1Rec.Hash: {},
   191  			},
   192  		},
   193  		{
   194  			name: "insert duplicate confirmed",
   195  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   196  				err = insertMainChainHeaders(s, dbtx, headerData, filters)
   197  				if err != nil {
   198  					return nil, err
   199  				}
   200  
   201  				err = s.InsertMinedTx(dbtx, tx1Rec, &b1Hash)
   202  				return s, nil
   203  			},
   204  			bal: dcrutil.Amount(tx1Rec.MsgTx.TxOut[0].Value),
   205  			unc: 0,
   206  			unspents: map[wire.OutPoint]struct{}{
   207  				{
   208  					Hash:  tx1Rec.Hash,
   209  					Index: 0,
   210  					Tree:  wire.TxTreeRegular,
   211  				}: {},
   212  			},
   213  			unmined: map[chainhash.Hash]struct{}{},
   214  		},
   215  		{
   216  			name: "insert confirmed double spend",
   217  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   218  				err = s.InsertMinedTx(dbtx, sTx1Rec, &b2Hash)
   219  				if err != nil {
   220  					return nil, err
   221  				}
   222  
   223  				err = s.InsertMinedTx(dbtx, sTx1Rec, &b2Hash)
   224  				return s, err
   225  			},
   226  			bal:      0,
   227  			unc:      0,
   228  			unspents: map[wire.OutPoint]struct{}{},
   229  			unmined:  map[chainhash.Hash]struct{}{},
   230  		},
   231  		{
   232  			name: "rollback after spending tx",
   233  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   234  				err := s.Rollback(dbtx, int32(b2H.Height))
   235  				return s, err
   236  			},
   237  			bal:      0,
   238  			unc:      0,
   239  			unspents: map[wire.OutPoint]struct{}{},
   240  			unmined: map[chainhash.Hash]struct{}{
   241  				sTx1Rec.Hash: {},
   242  			},
   243  		},
   244  		{
   245  			name: "insert unconfirmed debit",
   246  			f: func(s *Store, dbtx walletdb.ReadWriteTx) (*Store, error) {
   247  				err = s.InsertMemPoolTx(dbtx, sTx2Rec)
   248  				return s, err
   249  			},
   250  			bal:      0,
   251  			unc:      0,
   252  			unspents: map[wire.OutPoint]struct{}{},
   253  			unmined: map[chainhash.Hash]struct{}{
   254  				sTx1Rec.Hash: {},
   255  				sTx2Rec.Hash: {},
   256  			},
   257  		},
   258  	}
   259  
   260  	for _, test := range tests {
   261  		err := walletdb.Update(ctx, db, func(dbtx walletdb.ReadWriteTx) error {
   262  			tmpStore, err := test.f(s, dbtx)
   263  			if err != nil {
   264  				t.Fatalf("%s: got error: %v", test.name, err)
   265  			}
   266  
   267  			s := tmpStore
   268  			bal, err := s.AccountBalance(dbtx, 1, defaultAccount)
   269  			if err != nil {
   270  				t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err)
   271  			}
   272  			if bal.Spendable != test.bal {
   273  				t.Fatalf("%s: balance mismatch: expected: %d, got: %v",
   274  					test.name, test.bal, bal.Spendable)
   275  			}
   276  			unc, err := s.AccountBalance(dbtx, 1, defaultAccount)
   277  			if err != nil {
   278  				t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err)
   279  			}
   280  			if unc.Unconfirmed != test.unc {
   281  				t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d",
   282  					test.name, test.unc, unc)
   283  			}
   284  
   285  			// Check that unspent outputs match expected.
   286  			unspent, err := s.UnspentOutputs(dbtx)
   287  			if err != nil {
   288  				t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err)
   289  			}
   290  			for _, cred := range unspent {
   291  				if _, ok := test.unspents[cred.OutPoint]; !ok {
   292  					t.Errorf("%s: unexpected unspent output: %v",
   293  						test.name, cred.OutPoint)
   294  				}
   295  				delete(test.unspents, cred.OutPoint)
   296  			}
   297  			if len(test.unspents) != 0 {
   298  				t.Fatalf("%s: missing expected unspent output(s)", test.name)
   299  			}
   300  
   301  			// Check that unmined txs match expected.
   302  			unmined, err := s.UnminedTxs(dbtx)
   303  			if err != nil {
   304  				t.Fatalf("%s: cannot load unmined transactions: %v",
   305  					test.name, err)
   306  			}
   307  			for _, tx := range unmined {
   308  				if _, ok := test.unmined[tx.Hash]; !ok {
   309  					t.Fatalf("%s: unexpected unmined tx: %v",
   310  						test.name, tx.Hash)
   311  				}
   312  				delete(test.unmined, tx.Hash)
   313  			}
   314  			if len(test.unmined) != 0 {
   315  				t.Fatalf("%s: missing expected unmined tx(s)", test.name)
   316  			}
   317  
   318  			return nil
   319  		})
   320  		if err != nil {
   321  			t.Fatal(err)
   322  		}
   323  	}
   324  }
   325  
   326  func newCoinBase(outputValues ...int64) *wire.MsgTx {
   327  	tx := wire.MsgTx{
   328  		TxIn: []*wire.TxIn{
   329  			{
   330  				PreviousOutPoint: wire.OutPoint{Index: ^uint32(0)},
   331  			},
   332  		},
   333  	}
   334  	for _, val := range outputValues {
   335  		tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val})
   336  	}
   337  	return &tx
   338  }
   339  
   340  func spendOutput(txHash *chainhash.Hash, index uint32, tree int8, outputValues ...int64) *wire.MsgTx {
   341  	tx := wire.MsgTx{
   342  		TxIn: []*wire.TxIn{
   343  			{
   344  				PreviousOutPoint: wire.OutPoint{Hash: *txHash, Index: index, Tree: tree},
   345  			},
   346  		},
   347  	}
   348  	for _, val := range outputValues {
   349  		tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val})
   350  	}
   351  	return &tx
   352  }
   353  
   354  func TestCoinbases(t *testing.T) {
   355  	ctx := context.Background()
   356  	db, _, s, _, teardown, err := cloneDB(ctx, "coinbases.kv")
   357  	defer teardown()
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	cb := newCoinBase(20e8, 10e8, 30e8)
   363  	cbRec, err := NewTxRecordFromMsgTx(cb, time.Time{})
   364  	if err != nil {
   365  		t.Fatal(err)
   366  	}
   367  
   368  	defaultAccount := uint32(0)
   369  	g := makeBlockGenerator()
   370  	b1H := g.generate(dcrutil.BlockValid)
   371  	b1Hash := b1H.BlockHash()
   372  	b1Meta := makeBlockMeta(b1H)
   373  	headers := []*wire.BlockHeader{b1H}
   374  
   375  	// Generate enough blocks for tests.
   376  	for idx := 0; idx < 18; idx++ {
   377  		bh := g.generate(dcrutil.BlockValid)
   378  		headers = append(headers, bh)
   379  	}
   380  
   381  	headerData := makeHeaderDataSlice(headers...)
   382  	filters := emptyFilters(18)
   383  
   384  	err = walletdb.Update(ctx, db, func(dbtx walletdb.ReadWriteTx) error {
   385  		err = insertMainChainHeaders(s, dbtx, headerData[0:1], filters[0:1])
   386  		if err != nil {
   387  			t.Fatal(err)
   388  		}
   389  
   390  		// Insert coinbase and mark outputs 0 and 2 as credits.
   391  		err = s.InsertMinedTx(dbtx, cbRec, &b1Hash)
   392  		if err != nil {
   393  			t.Fatal(err)
   394  		}
   395  
   396  		err = s.AddCredit(dbtx, cbRec, b1Meta, 0, false, defaultAccount)
   397  		if err != nil {
   398  			t.Fatal(err)
   399  		}
   400  
   401  		err = s.AddCredit(dbtx, cbRec, b1Meta, 2, false, defaultAccount)
   402  		if err != nil {
   403  			t.Fatal(err)
   404  		}
   405  
   406  		type coinbaseTest struct {
   407  			immature  dcrutil.Amount
   408  			spendable dcrutil.Amount
   409  		}
   410  
   411  		testMaturity := func(tests []coinbaseTest) error {
   412  			for i, tst := range tests {
   413  				bal, err := s.AccountBalance(dbtx, 0, defaultAccount)
   414  				if err != nil {
   415  					t.Fatalf("Coinbase test %d: Store.Balance failed: %v", i, err)
   416  				}
   417  
   418  				if bal.ImmatureCoinbaseRewards != tst.immature {
   419  					t.Fatalf("Coinbase test %d: Got %v immature coinbase, Expected %v",
   420  						i, bal.ImmatureCoinbaseRewards, tst.immature)
   421  				}
   422  
   423  				if bal.ImmatureCoinbaseRewards != tst.immature {
   424  					t.Fatalf("Coinbase test %d: Got %v spendable balance, Expected %v",
   425  						i, bal.Spendable, tst.spendable)
   426  				}
   427  			}
   428  
   429  			return nil
   430  		}
   431  
   432  		expectedImmature := []coinbaseTest{
   433  			{
   434  				immature:  dcrutil.Amount(50e8),
   435  				spendable: dcrutil.Amount(0),
   436  			},
   437  		}
   438  
   439  		// At Block 1, 16 blocks from testnet coinbase maturity .
   440  		err := testMaturity(expectedImmature)
   441  		if err != nil {
   442  			t.Fatal(err)
   443  		}
   444  
   445  		// Extend chain by 6 blocks.
   446  		err = insertMainChainHeaders(s, dbtx, headerData[1:7], filters[1:7])
   447  		if err != nil {
   448  			t.Fatal(err)
   449  		}
   450  
   451  		// At Block 7, 10 blocks from testnet coinbase maturity.
   452  		err = testMaturity(expectedImmature)
   453  		if err != nil {
   454  			t.Fatal(err)
   455  		}
   456  
   457  		// Extend chain by 6 blocks.
   458  		err = insertMainChainHeaders(s, dbtx, headerData[7:13], filters[7:13])
   459  		if err != nil {
   460  			t.Fatal(err)
   461  		}
   462  
   463  		// At Block 13, 4 blocks from testnet coinbase maturity.
   464  		err = testMaturity(expectedImmature)
   465  		if err != nil {
   466  			t.Fatal(err)
   467  		}
   468  
   469  		expectedMature := []coinbaseTest{
   470  			{
   471  				immature:  dcrutil.Amount(0),
   472  				spendable: dcrutil.Amount(50e8),
   473  			},
   474  		}
   475  
   476  		// Extend chain by 3 blocks. The coinbase should still be immature since
   477  		// it is still a block away from maturity.
   478  		err = insertMainChainHeaders(s, dbtx,
   479  			headerData[13:16], filters[13:16])
   480  		if err != nil {
   481  			t.Fatal(err)
   482  		}
   483  
   484  		// At Block 16, 1 block from testnet coinbase maturity.
   485  		err = testMaturity(expectedImmature)
   486  		if err != nil {
   487  			t.Fatal(err)
   488  		}
   489  
   490  		// Extend chain by 1 block.
   491  		err = insertMainChainHeaders(s, dbtx,
   492  			headerData[16:17], filters[16:17])
   493  		if err != nil {
   494  			t.Fatal(err)
   495  		}
   496  
   497  		// At Block 17, testnet coinbase maturity reached. The coinbase should
   498  		// be available to spend.
   499  		err = testMaturity(expectedMature)
   500  		if err != nil {
   501  			t.Fatal(err)
   502  		}
   503  
   504  		// Spend an output from the coinbase. This should deduct the amount
   505  		// spent by the tx from the matured coinbase amount.
   506  		spenderA := spendOutput(&cbRec.Hash, 0, 0, 5e8, 15e8)
   507  		spenderARec, err := NewTxRecordFromMsgTx(spenderA, time.Now())
   508  		if err != nil {
   509  			t.Fatal(err)
   510  		}
   511  
   512  		b17H := headers[16]
   513  		b17Hash := b17H.BlockHash()
   514  		err = s.InsertMinedTx(dbtx, spenderARec, &b17Hash)
   515  		if err != nil {
   516  			t.Fatal(err)
   517  		}
   518  
   519  		expectedMatureRemainder := []coinbaseTest{
   520  			{
   521  				immature:  dcrutil.Amount(0),
   522  				spendable: dcrutil.Amount(30e8),
   523  			},
   524  		}
   525  
   526  		err = testMaturity(expectedMatureRemainder)
   527  		if err != nil {
   528  			t.Fatal(err)
   529  		}
   530  
   531  		// Reorg out the block that matured the coinbase and spends part of the
   532  		// coinbase. The immature coinbase should be deducted by the amount
   533  		// being spent by the tx.
   534  		err = s.Rollback(dbtx, int32(b17H.Height))
   535  		if err != nil {
   536  			t.Fatal(err)
   537  		}
   538  
   539  		expectedReorgImmature := []coinbaseTest{
   540  			{
   541  				immature:  dcrutil.Amount(30e8),
   542  				spendable: dcrutil.Amount(0),
   543  			},
   544  		}
   545  
   546  		err = testMaturity(expectedReorgImmature)
   547  		if err != nil {
   548  			t.Fatal(err)
   549  		}
   550  
   551  		// Reorg out the block that contained the coinbase. Since the block
   552  		// with the coinbase is no longer part of the chain there should not be
   553  		// any mature or immature amounts reported.
   554  		err = s.Rollback(dbtx, int32(b1H.Height))
   555  		if err != nil {
   556  			t.Fatal(err)
   557  		}
   558  
   559  		expectedReorgToFirstBlock := []coinbaseTest{
   560  			{
   561  				immature:  dcrutil.Amount(0),
   562  				spendable: dcrutil.Amount(0),
   563  			},
   564  		}
   565  
   566  		err = testMaturity(expectedReorgToFirstBlock)
   567  		if err != nil {
   568  			t.Fatal(err)
   569  		}
   570  
   571  		return nil
   572  	})
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  }