github.com/nebulouslabs/sia@v1.3.7/modules/transactionpool/update_test.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/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  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	defer tpt2.Close()
   183  
   184  	// connect the testers and wait for them to have the same current block
   185  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  	success := false
   190  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   191  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   192  			success = true
   193  			break
   194  		}
   195  	}
   196  	if !success {
   197  		t.Fatal("testers did not have the same block height after one minute")
   198  	}
   199  
   200  	// disconnect the testers
   201  	err = tpt2.gateway.Disconnect(tpt.gateway.Address())
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	tpt.gateway.Disconnect(tpt2.gateway.Address())
   206  
   207  	// make some transactions on tpt
   208  	var txnSets [][]types.Transaction
   209  	for i := 0; i < 5; i++ {
   210  		txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   211  		if err != nil {
   212  			t.Fatal(err)
   213  		}
   214  		txnSets = append(txnSets, txns)
   215  	}
   216  	// mine some blocks to cause a re-org
   217  	for i := 0; i < 3; i++ {
   218  		_, err = tpt.miner.AddBlock()
   219  		if err != nil {
   220  			t.Fatal(err)
   221  		}
   222  	}
   223  	// put tpt2 at a higher height
   224  	for i := 0; i < 10; i++ {
   225  		_, err = tpt2.miner.AddBlock()
   226  		if err != nil {
   227  			t.Fatal(err)
   228  		}
   229  	}
   230  
   231  	// connect the testers and wait for them to have the same current block
   232  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	success = false
   237  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   238  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   239  			success = true
   240  			break
   241  		}
   242  	}
   243  	if !success {
   244  		t.Fatal("testers did not have the same block height after one minute")
   245  	}
   246  
   247  	// verify the transaction pool still has the reorged txns
   248  	for _, txnSet := range txnSets {
   249  		for _, txn := range txnSet {
   250  			_, _, exists := tpt.tpool.Transaction(txn.ID())
   251  			if !exists {
   252  				t.Error("Transaction was not re-added to the transaction pool after being re-orged out of the blockchain:", txn.ID())
   253  			}
   254  		}
   255  	}
   256  
   257  	// Try to get the transactoins into a block.
   258  	_, err = tpt.miner.AddBlock()
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	if len(tpt.tpool.TransactionList()) != 0 {
   263  		t.Error("Does not seem that the transactions were added to the transaction pool.")
   264  	}
   265  }
   266  
   267  // TestTransactionPoolPruning verifies that the transaction pool correctly
   268  // prunes transactions older than maxTxnAge.
   269  func TestTransactionPoolPruning(t *testing.T) {
   270  	if testing.Short() {
   271  		t.SkipNow()
   272  	}
   273  
   274  	tpt, err := createTpoolTester(t.Name())
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	defer tpt.Close()
   279  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	defer tpt2.Close()
   284  
   285  	// connect the testers and wait for them to have the same current block
   286  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	success := false
   291  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   292  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   293  			success = true
   294  			break
   295  		}
   296  	}
   297  	if !success {
   298  		t.Fatal("testers did not have the same block height after one minute")
   299  	}
   300  
   301  	// disconnect tpt, create an unconfirmed transaction on tpt, mine maxTxnAge
   302  	// blocks on tpt2 and reconnect. The unconfirmed transactions should be
   303  	// removed from tpt's pool.
   304  	err = tpt.gateway.Disconnect(tpt2.gateway.Address())
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	tpt2.gateway.Disconnect(tpt.gateway.Address())
   309  	txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	for i := types.BlockHeight(0); i < maxTxnAge+1; i++ {
   314  		_, err = tpt2.miner.AddBlock()
   315  		if err != nil {
   316  			t.Fatal(err)
   317  		}
   318  	}
   319  
   320  	// reconnect the testers
   321  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   322  	if err != nil {
   323  		t.Fatal(err)
   324  	}
   325  	success = false
   326  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   327  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   328  			success = true
   329  			break
   330  		}
   331  	}
   332  	if !success {
   333  		t.Fatal("testers did not have the same block height after one minute")
   334  	}
   335  
   336  	for _, txn := range txns {
   337  		_, _, exists := tpt.tpool.Transaction(txn.ID())
   338  		if exists {
   339  			t.Fatal("transaction pool had a transaction that should have been pruned")
   340  		}
   341  	}
   342  	if len(tpt.tpool.TransactionList()) != 0 {
   343  		t.Fatal("should have no unconfirmed transactions")
   344  	}
   345  	if len(tpt.tpool.knownObjects) != 0 {
   346  		t.Fatal("should have no known objects")
   347  	}
   348  	if len(tpt.tpool.transactionSetDiffs) != 0 {
   349  		t.Fatal("should have no transaction set diffs")
   350  	}
   351  	if tpt.tpool.transactionListSize != 0 {
   352  		t.Fatal("transactionListSize should be zero")
   353  	}
   354  }
   355  
   356  // TestUpdateBlockHeight verifies that the transactionpool updates its internal
   357  // block height correctly.
   358  func TestUpdateBlockHeight(t *testing.T) {
   359  	if testing.Short() {
   360  		t.SkipNow()
   361  	}
   362  
   363  	tpt, err := blankTpoolTester(t.Name())
   364  	if err != nil {
   365  		t.Fatal(err)
   366  	}
   367  	defer tpt.Close()
   368  
   369  	targetHeight := 20
   370  	for i := 0; i < targetHeight; i++ {
   371  		_, err = tpt.miner.AddBlock()
   372  		if err != nil {
   373  			t.Fatal(err)
   374  		}
   375  	}
   376  	if tpt.tpool.blockHeight != types.BlockHeight(targetHeight) {
   377  		t.Fatalf("transaction pool had the wrong block height, got %v wanted %v\n", tpt.tpool.blockHeight, targetHeight)
   378  	}
   379  }
   380  
   381  // TestDatabaseUpgrade verifies that the database will upgrade correctly from
   382  // v1.3.1 or earlier to the new sanity check persistence, by clearing out the
   383  // persistence at various points in the process of a reorg.
   384  func TestDatabaseUpgrade(t *testing.T) {
   385  	if testing.Short() {
   386  		t.SkipNow()
   387  	}
   388  	tpt, err := createTpoolTester(t.Name())
   389  	if err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	defer tpt.Close()
   393  	tpt2, err := blankTpoolTester(t.Name() + "-tpt2")
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	defer tpt2.Close()
   398  
   399  	// connect the testers and wait for them to have the same current block
   400  	err = tpt2.gateway.Connect(tpt.gateway.Address())
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	success := false
   405  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   406  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   407  			success = true
   408  			break
   409  		}
   410  	}
   411  	if !success {
   412  		t.Fatal("testers did not have the same block height after one minute")
   413  	}
   414  
   415  	// disconnect the testers
   416  	err = tpt2.gateway.Disconnect(tpt.gateway.Address())
   417  	if err != nil {
   418  		t.Fatal(err)
   419  	}
   420  	tpt.gateway.Disconnect(tpt2.gateway.Address())
   421  
   422  	// make some transactions on tpt
   423  	var txnSets [][]types.Transaction
   424  	for i := 0; i < 5; i++ {
   425  		txns, err := tpt.wallet.SendSiacoins(types.SiacoinPrecision.Mul64(1000), types.UnlockHash{})
   426  		if err != nil {
   427  			t.Fatal(err)
   428  		}
   429  		txnSets = append(txnSets, txns)
   430  	}
   431  	// mine some blocks to cause a re-org, first clearing the persistence to
   432  	// simulate an un-upgraded database.
   433  	err = tpt.tpool.dbTx.Bucket(bucketRecentConsensusChange).Delete(fieldRecentBlockID)
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  	for i := 0; i < 3; i++ {
   438  		_, err = tpt.miner.AddBlock()
   439  		if err != nil {
   440  			t.Fatal(err)
   441  		}
   442  	}
   443  	// put tpt2 at a higher height
   444  	for i := 0; i < 10; i++ {
   445  		_, err = tpt2.miner.AddBlock()
   446  		if err != nil {
   447  			t.Fatal(err)
   448  		}
   449  	}
   450  
   451  	// connect the testers and wait for them to have the same current block,
   452  	// first clearing the persistence to simulate an un-upgraded database.
   453  	err = tpt.tpool.dbTx.Bucket(bucketRecentConsensusChange).Delete(fieldRecentBlockID)
   454  	if err != nil {
   455  		t.Fatal(err)
   456  	}
   457  	err = tpt.gateway.Connect(tpt2.gateway.Address())
   458  	if err != nil {
   459  		t.Fatal(err)
   460  	}
   461  	success = false
   462  	for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(time.Millisecond * 100) {
   463  		if tpt.cs.CurrentBlock().ID() == tpt2.cs.CurrentBlock().ID() {
   464  			success = true
   465  			break
   466  		}
   467  	}
   468  	if !success {
   469  		t.Fatal("testers did not have the same block height after one minute")
   470  	}
   471  }