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 }