gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/transactionpool/update_test.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  	"time"
     7  
     8  	"gitlab.com/SiaPrime/SiaPrime/modules"
     9  	"gitlab.com/SiaPrime/SiaPrime/types"
    10  )
    11  
    12  // TestFindSets checks that the findSets functions is properly parsing and
    13  // combining transactions into their minimal sets.
    14  func TestFindSets(t *testing.T) {
    15  	// Graph a graph which is a chain. Graph will be invalid, but we don't need
    16  	// the consensus set, so no worries.
    17  	graph1Size := 5
    18  	edges := make([]types.TransactionGraphEdge, 0, graph1Size)
    19  	for i := 0; i < graph1Size; i++ {
    20  		edges = append(edges, types.TransactionGraphEdge{
    21  			Dest:   i + 1,
    22  			Fee:    types.NewCurrency64(5),
    23  			Source: i,
    24  			Value:  types.NewCurrency64(100),
    25  		})
    26  	}
    27  	graph1, err := types.TransactionGraph(types.SiacoinOutputID{}, edges)
    28  	if err != nil {
    29  		t.Fatal(err)
    30  	}
    31  
    32  	// Split the graph using findSets. Result should be a single set with 5
    33  	// transactions.
    34  	sets := findSets(graph1)
    35  	if len(sets) != 1 {
    36  		t.Fatal("there should be only one set")
    37  	}
    38  	if len(sets[0]) != graph1Size {
    39  		t.Error("findSets is not grouping the transactions correctly")
    40  	}
    41  
    42  	// Create a second graph to check it can handle two graphs.
    43  	graph2Size := 6
    44  	edges = make([]types.TransactionGraphEdge, 0, graph2Size)
    45  	for i := 0; i < graph2Size; i++ {
    46  		edges = append(edges, types.TransactionGraphEdge{
    47  			Dest:   i + 1,
    48  			Fee:    types.NewCurrency64(5),
    49  			Source: i,
    50  			Value:  types.NewCurrency64(100),
    51  		})
    52  	}
    53  	graph2, err := types.TransactionGraph(types.SiacoinOutputID{1}, edges)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	sets = findSets(append(graph1, graph2...))
    58  	if len(sets) != 2 {
    59  		t.Fatal("there should be two sets")
    60  	}
    61  	lens := []int{len(sets[0]), len(sets[1])}
    62  	sort.Ints(lens)
    63  	expected := []int{graph1Size, graph2Size}
    64  	sort.Ints(expected)
    65  	if lens[0] != expected[0] || lens[1] != expected[1] {
    66  		t.Error("Resulting sets do not have the right lengths")
    67  	}
    68  
    69  	// Create a diamond graph to make sure it can handle diamond graph.
    70  	edges = make([]types.TransactionGraphEdge, 0, 5)
    71  	sources := []int{0, 0, 1, 2, 3}
    72  	dests := []int{1, 2, 3, 3, 4}
    73  	for i := 0; i < 5; i++ {
    74  		edges = append(edges, types.TransactionGraphEdge{
    75  			Dest:   dests[i],
    76  			Fee:    types.NewCurrency64(5),
    77  			Source: sources[i],
    78  			Value:  types.NewCurrency64(100),
    79  		})
    80  	}
    81  	graph3, err := types.TransactionGraph(types.SiacoinOutputID{2}, edges)
    82  	graph3Size := len(graph3)
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	sets = findSets(append(graph1, append(graph2, graph3...)...))
    87  	if len(sets) != 3 {
    88  		t.Fatal("there should be two sets")
    89  	}
    90  	lens = []int{len(sets[0]), len(sets[1]), len(sets[2])}
    91  	sort.Ints(lens)
    92  	expected = []int{graph1Size, graph2Size, graph3Size}
    93  	sort.Ints(expected)
    94  	if lens[0] != expected[0] || lens[1] != expected[1] || lens[2] != expected[2] {
    95  		t.Error("Resulting sets do not have the right lengths")
    96  	}
    97  
    98  	// Sporadically weave the transactions and make sure the set finder still
    99  	// parses the sets correctly (sets can assumed to be ordered, but not all in
   100  	// a row).
   101  	var sporadic []types.Transaction
   102  	for len(graph1) > 0 || len(graph2) > 0 || len(graph3) > 0 {
   103  		if len(graph1) > 0 {
   104  			sporadic = append(sporadic, graph1[0])
   105  			graph1 = graph1[1:]
   106  		}
   107  		if len(graph2) > 0 {
   108  			sporadic = append(sporadic, graph2[0])
   109  			graph2 = graph2[1:]
   110  		}
   111  		if len(graph3) > 0 {
   112  			sporadic = append(sporadic, graph3[0])
   113  			graph3 = graph3[1:]
   114  		}
   115  	}
   116  	if len(sporadic) != graph1Size+graph2Size+graph3Size {
   117  		t.Error("sporadic block creation failed")
   118  	}
   119  	// Result of findSets should match previous result.
   120  	sets = findSets(sporadic)
   121  	if len(sets) != 3 {
   122  		t.Fatal("there should be two sets")
   123  	}
   124  	lens = []int{len(sets[0]), len(sets[1]), len(sets[2])}
   125  	sort.Ints(lens)
   126  	expected = []int{graph1Size, graph2Size, graph3Size}
   127  	sort.Ints(expected)
   128  	if lens[0] != expected[0] || lens[1] != expected[1] || lens[2] != expected[2] {
   129  		t.Error("Resulting sets do not have the right lengths")
   130  	}
   131  }
   132  
   133  // TestArbDataOnly tries submitting a transaction with only arbitrary data to
   134  // the transaction pool. Then a block is mined, putting the transaction on the
   135  // blockchain. The arb data transaction should no longer be in the transaction
   136  // pool.
   137  func TestArbDataOnly(t *testing.T) {
   138  	if testing.Short() {
   139  		t.SkipNow()
   140  	}
   141  	tpt, err := createTpoolTester(t.Name())
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	defer tpt.Close()
   146  	txn := types.Transaction{
   147  		ArbitraryData: [][]byte{
   148  			append(modules.PrefixNonSia[:], []byte("arb-data")...),
   149  		},
   150  	}
   151  	err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn})
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	if len(tpt.tpool.TransactionList()) != 1 {
   156  		t.Error("expecting to see a transaction in the transaction pool")
   157  	}
   158  	_, err = tpt.miner.AddBlock()
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	if len(tpt.tpool.TransactionList()) != 0 {
   163  		t.Error("transaction was not cleared from the transaction pool")
   164  	}
   165  }
   166  
   167  // TestValidRevertedTransaction verifies that if a transaction appears in a
   168  // block's reverted transactions, it is added correctly to the pool.
   169  func TestValidRevertedTransaction(t *testing.T) {
   170  	if testing.Short() {
   171  		t.SkipNow()
   172  	}
   173  	tpt, err := createTpoolTester(t.Name())
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	defer tpt.Close()
   178  	// Mine a few extra blocks on tpt to get past the signature hardfork height.
   179  	for i := 0; i < 10; i++ {
   180  		_, err = tpt.miner.AddBlock()
   181  		if err != nil {
   182  			t.Fatal(err)
   183  		}
   184  	}
   185  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  	defer tpt2.Close()
   190  
   191  	// connect the testers and wait for them to have the same current block
   192  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	success := false
   197  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   198  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   199  			success = true
   200  			break
   201  		}
   202  	}
   203  	if !success {
   204  		t.Fatal("testers did not have the same block height after one minute")
   205  	}
   206  
   207  	// disconnect the testers
   208  	err = tpt2.gateway.Disconnect(tpt.gateway.Address())
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	tpt.gateway.Disconnect(tpt2.gateway.Address())
   213  
   214  	// make some transactions on tpt
   215  	var txnSets [][]types.Transaction
   216  	for i := 0; i < 5; i++ {
   217  		txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   218  		if err != nil {
   219  			t.Fatal(err)
   220  		}
   221  		txnSets = append(txnSets, txns)
   222  	}
   223  	// mine some blocks to cause a re-org
   224  	for i := 0; i < 3; i++ {
   225  		_, err = tpt.miner.AddBlock()
   226  		if err != nil {
   227  			t.Fatal(err)
   228  		}
   229  	}
   230  	// put tpt2 at a higher height
   231  	for i := 0; i < 10; i++ {
   232  		_, err = tpt2.miner.AddBlock()
   233  		if err != nil {
   234  			t.Fatal(err)
   235  		}
   236  	}
   237  
   238  	// connect the testers and wait for them to have the same current block
   239  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	success = false
   244  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   245  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   246  			success = true
   247  			break
   248  		}
   249  	}
   250  	if !success {
   251  		t.Fatal("testers did not have the same block height after one minute")
   252  	}
   253  
   254  	// verify the transaction pool still has the reorged txns
   255  	for _, txnSet := range txnSets {
   256  		for _, txn := range txnSet {
   257  			_, _, exists := tpt.tpool.Transaction(txn.ID())
   258  			if !exists {
   259  				t.Error("Transaction was not re-added to the transaction pool after being re-orged out of the blockchain:", txn.ID())
   260  			}
   261  		}
   262  	}
   263  
   264  	// Try to get the transactoins into a block.
   265  	_, err = tpt.miner.AddBlock()
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	if len(tpt.tpool.TransactionList()) != 0 {
   270  		t.Error("Does not seem that the transactions were added to the transaction pool.")
   271  	}
   272  }
   273  
   274  // TestTransactionPoolPruning verifies that the transaction pool correctly
   275  // prunes transactions older than maxTxnAge.
   276  func TestTransactionPoolPruning(t *testing.T) {
   277  	if testing.Short() {
   278  		t.SkipNow()
   279  	}
   280  
   281  	tpt, err := createTpoolTester(t.Name())
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	defer tpt.Close()
   286  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	defer tpt2.Close()
   291  
   292  	// connect the testers and wait for them to have the same current block
   293  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  	success := false
   298  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   299  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   300  			success = true
   301  			break
   302  		}
   303  	}
   304  	if !success {
   305  		t.Fatal("testers did not have the same block height after one minute")
   306  	}
   307  
   308  	// disconnect tpt, create an unconfirmed transaction on tpt, mine maxTxnAge
   309  	// blocks on tpt2 and reconnect. The unconfirmed transactions should be
   310  	// removed from tpt's pool.
   311  	err = tpt.gateway.Disconnect(tpt2.gateway.Address())
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	tpt2.gateway.Disconnect(tpt.gateway.Address())
   316  	txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  	for i := types.BlockHeight(0); i < maxTxnAge+1; i++ {
   321  		_, err = tpt2.miner.AddBlock()
   322  		if err != nil {
   323  			t.Fatal(err)
   324  		}
   325  	}
   326  
   327  	// reconnect the testers
   328  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  	success = false
   333  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   334  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   335  			success = true
   336  			break
   337  		}
   338  	}
   339  	if !success {
   340  		t.Fatal("testers did not have the same block height after one minute")
   341  	}
   342  
   343  	for _, txn := range txns {
   344  		_, _, exists := tpt.tpool.Transaction(txn.ID())
   345  		if exists {
   346  			t.Fatal("transaction pool had a transaction that should have been pruned")
   347  		}
   348  	}
   349  	if len(tpt.tpool.TransactionList()) != 0 {
   350  		t.Fatal("should have no unconfirmed transactions")
   351  	}
   352  	if len(tpt.tpool.knownObjects) != 0 {
   353  		t.Fatal("should have no known objects")
   354  	}
   355  	if len(tpt.tpool.transactionSetDiffs) != 0 {
   356  		t.Fatal("should have no transaction set diffs")
   357  	}
   358  	if tpt.tpool.transactionListSize != 0 {
   359  		t.Fatal("transactionListSize should be zero")
   360  	}
   361  }
   362  
   363  // TestUpdateBlockHeight verifies that the transactionpool updates its internal
   364  // block height correctly.
   365  func TestUpdateBlockHeight(t *testing.T) {
   366  	if testing.Short() {
   367  		t.SkipNow()
   368  	}
   369  
   370  	tpt, err := blankTpoolTester(t.Name())
   371  	if err != nil {
   372  		t.Fatal(err)
   373  	}
   374  	defer tpt.Close()
   375  
   376  	targetHeight := 20
   377  	for i := 0; i < targetHeight; i++ {
   378  		_, err = tpt.miner.AddBlock()
   379  		if err != nil {
   380  			t.Fatal(err)
   381  		}
   382  	}
   383  	if tpt.tpool.blockHeight != types.BlockHeight(targetHeight) {
   384  		t.Fatalf("transaction pool had the wrong block height, got %v wanted %v\n", tpt.tpool.blockHeight, targetHeight)
   385  	}
   386  }
   387  
   388  // TestDatabaseUpgrade verifies that the database will upgrade correctly from
   389  // v1.3.1 or earlier to the new sanity check persistence, by clearing out the
   390  // persistence at various points in the process of a reorg.
   391  func TestDatabaseUpgrade(t *testing.T) {
   392  	if testing.Short() {
   393  		t.SkipNow()
   394  	}
   395  	tpt, err := createTpoolTester(t.Name())
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	defer tpt.Close()
   400  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	defer tpt2.Close()
   405  
   406  	// connect the testers and wait for them to have the same current block
   407  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	success := false
   412  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   413  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   414  			success = true
   415  			break
   416  		}
   417  	}
   418  	if !success {
   419  		t.Fatal("testers did not have the same block height after one minute")
   420  	}
   421  
   422  	// disconnect the testers
   423  	err = tpt2.gateway.Disconnect(tpt.gateway.Address())
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	tpt.gateway.Disconnect(tpt2.gateway.Address())
   428  
   429  	// make some transactions on tpt
   430  	var txnSets [][]types.Transaction
   431  	for i := 0; i < 5; i++ {
   432  		txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   433  		if err != nil {
   434  			t.Fatal(err)
   435  		}
   436  		txnSets = append(txnSets, txns)
   437  	}
   438  	// mine some blocks to cause a re-org, first clearing the persistence to
   439  	// simulate an un-upgraded database.
   440  	err = tpt.tpool.dbTx.Bucket(bucketRecentConsensusChange).Delete(fieldRecentBlockID)
   441  	if err != nil {
   442  		t.Fatal(err)
   443  	}
   444  	for i := 0; i < 3; i++ {
   445  		_, err = tpt.miner.AddBlock()
   446  		if err != nil {
   447  			t.Fatal(err)
   448  		}
   449  	}
   450  	// put tpt2 at a higher height
   451  	for i := 0; i < 10; i++ {
   452  		_, err = tpt2.miner.AddBlock()
   453  		if err != nil {
   454  			t.Fatal(err)
   455  		}
   456  	}
   457  
   458  	// connect the testers and wait for them to have the same current block,
   459  	// first clearing the persistence to simulate an un-upgraded database.
   460  	err = tpt.tpool.dbTx.Bucket(bucketRecentConsensusChange).Delete(fieldRecentBlockID)
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	success = false
   469  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   470  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   471  			success = true
   472  			break
   473  		}
   474  	}
   475  	if !success {
   476  		t.Fatal("testers did not have the same block height after one minute")
   477  	}
   478  }