github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/transactionpool/accept_test.go (about) 1 package transactionpool 2 3 import ( 4 "crypto/rand" 5 "testing" 6 7 "github.com/NebulousLabs/Sia/modules" 8 "github.com/NebulousLabs/Sia/types" 9 ) 10 11 // TestIntegrationAcceptTransactionSet probes the AcceptTransactionSet method 12 // of the transaction pool. 13 func TestIntegrationAcceptTransactionSet(t *testing.T) { 14 // Create a transaction pool tester. 15 tpt, err := createTpoolTester("TestIntegrationAcceptTransactionSet") 16 if err != nil { 17 t.Fatal(err) 18 } 19 20 // Check that the transaction pool is empty. 21 if len(tpt.tpool.transactionSets) != 0 { 22 t.Error("transaction pool is not empty") 23 } 24 25 // Create a valid transaction set using the wallet. 26 txns, err := tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{}) 27 if err != nil { 28 t.Fatal(err) 29 } 30 if len(tpt.tpool.transactionSets) != 1 { 31 t.Error("sending coins did not increase the transaction sets by 1") 32 } 33 34 // Submit the transaction set again to trigger a duplication error. 35 err = tpt.tpool.AcceptTransactionSet(txns) 36 if err != modules.ErrDuplicateTransactionSet { 37 t.Error(err) 38 } 39 40 // Mine a block and check that the transaction pool gets emptied. 41 block, _ := tpt.miner.FindBlock() 42 err = tpt.cs.AcceptBlock(block) 43 if err != nil { 44 t.Fatal(err) 45 } 46 if len(tpt.tpool.TransactionList()) != 0 { 47 t.Error("transaction pool was not emptied after mining a block") 48 } 49 50 // Try to resubmit the transaction set to verify 51 err = tpt.tpool.AcceptTransactionSet(txns) 52 if err == nil { 53 t.Error("transaction set was supposed to be rejected") 54 } 55 } 56 57 // TestIntegrationConflictingTransactionSets tries to add two transaction sets 58 // to the transaction pool that are each legal individually, but double spend 59 // an output. 60 func TestIntegrationConflictingTransactionSets(t *testing.T) { 61 if testing.Short() { 62 t.SkipNow() 63 } 64 // Create a transaction pool tester. 65 tpt, err := createTpoolTester("TestIntegrationConflictingTransactionSets") 66 if err != nil { 67 t.Fatal(err) 68 } 69 70 // Fund a partial transaction. 71 fund := types.NewCurrency64(30e6) 72 txnBuilder := tpt.wallet.StartTransaction() 73 err = txnBuilder.FundSiacoins(fund) 74 if err != nil { 75 t.Fatal(err) 76 } 77 // wholeTransaction is set to false so that we can use the same signature 78 // to create a double spend. 79 txnSet, err := txnBuilder.Sign(false) 80 if err != nil { 81 t.Fatal(err) 82 } 83 txnSetDoubleSpend := make([]types.Transaction, len(txnSet)) 84 copy(txnSetDoubleSpend, txnSet) 85 86 // There are now two sets of transactions that are signed and ready to 87 // spend the same output. Have one spend the money in a miner fee, and the 88 // other create a siacoin output. 89 txnIndex := len(txnSet) - 1 90 txnSet[txnIndex].MinerFees = append(txnSet[txnIndex].MinerFees, fund) 91 txnSetDoubleSpend[txnIndex].SiacoinOutputs = append(txnSetDoubleSpend[txnIndex].SiacoinOutputs, types.SiacoinOutput{Value: fund}) 92 93 // Add the first and then the second txn set. 94 err = tpt.tpool.AcceptTransactionSet(txnSet) 95 if err != nil { 96 t.Error(err) 97 } 98 err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend) 99 if err == nil { 100 t.Error("transaction should not have passed inspection") 101 } 102 103 // Purge and try the sets in the reverse order. 104 tpt.tpool.PurgeTransactionPool() 105 err = tpt.tpool.AcceptTransactionSet(txnSetDoubleSpend) 106 if err != nil { 107 t.Error(err) 108 } 109 err = tpt.tpool.AcceptTransactionSet(txnSet) 110 if err == nil { 111 t.Error("transaction should not have passed inspection") 112 } 113 } 114 115 // TestIntegrationCheckMinerFees probes the checkMinerFees method of the 116 // transaction pool. 117 func TestIntegrationCheckMinerFees(t *testing.T) { 118 if testing.Short() { 119 t.SkipNow() 120 } 121 // Create a transaction pool tester. 122 tpt, err := createTpoolTester("TestIntegrationCheckMinerFees") 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 // Fill the transaction pool to the fee limit. 128 for i := 0; i < TransactionPoolSizeForFee/10e3; i++ { 129 arbData := make([]byte, 10e3) 130 copy(arbData, modules.PrefixNonSia[:]) 131 _, err = rand.Read(arbData[100:116]) // prevents collisions with other transacitons in the loop. 132 if err != nil { 133 t.Fatal(err) 134 } 135 txn := types.Transaction{ArbitraryData: [][]byte{arbData}} 136 err := tpt.tpool.AcceptTransactionSet([]types.Transaction{txn}) 137 if err != nil { 138 t.Fatal(err) 139 } 140 } 141 142 // Add another transaction, this one should fail for having too few fees. 143 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{{}}) 144 if err != errLowMinerFees { 145 t.Error(err) 146 } 147 148 // Add a transaction that has sufficient fees. 149 _, err = tpt.wallet.SendSiacoins(types.NewCurrency64(100), types.UnlockHash{}) 150 if err != nil { 151 t.Error(err) 152 } 153 154 // TODO: fill the pool up all the way and try again. 155 } 156 157 // TestTransactionSuperset submits a single transaction to the network, 158 // followed by a transaction set containing that single transaction. 159 func TestIntegrationTransactionSuperset(t *testing.T) { 160 if testing.Short() { 161 t.SkipNow() 162 } 163 // Create a transaction pool tester. 164 tpt, err := createTpoolTester("TestTransactionSuperset") 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 // Fund a partial transaction. 170 fund := types.NewCurrency64(30e6) 171 txnBuilder := tpt.wallet.StartTransaction() 172 err = txnBuilder.FundSiacoins(fund) 173 if err != nil { 174 t.Fatal(err) 175 } 176 txnBuilder.AddMinerFee(fund) 177 // wholeTransaction is set to false so that we can use the same signature 178 // to create a double spend. 179 txnSet, err := txnBuilder.Sign(false) 180 if err != nil { 181 t.Fatal(err) 182 } 183 if len(txnSet) <= 1 { 184 t.Fatal("test is invalid unless the transaction set has two or more transactions") 185 } 186 // Check that the second transaction is dependent on the first. 187 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 188 if err == nil { 189 t.Fatal("transaction set must have dependent transactions") 190 } 191 192 // Submit the first transaction in the set to the transaction pool, and 193 // then the superset. 194 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 195 if err != nil { 196 t.Fatal("first transaction in the transaction set was not valid?") 197 } 198 err = tpt.tpool.AcceptTransactionSet(txnSet) 199 if err != nil { 200 t.Fatal("super setting is not working:", err) 201 } 202 203 // Try resubmitting the individual transaction and the superset, a 204 // duplication error should be returned for each case. 205 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 206 if err != modules.ErrDuplicateTransactionSet { 207 t.Fatal(err) 208 } 209 err = tpt.tpool.AcceptTransactionSet(txnSet) 210 if err != modules.ErrDuplicateTransactionSet { 211 t.Fatal("super setting is not working:", err) 212 } 213 } 214 215 // TestTransactionSubset submits a transaction set to the network, followed by 216 // just a subset, expectint ErrDuplicateTransactionSet as a response. 217 func TestIntegrationTransactionSubset(t *testing.T) { 218 if testing.Short() { 219 t.SkipNow() 220 } 221 // Create a transaction pool tester. 222 tpt, err := createTpoolTester("TestTransactionSubset") 223 if err != nil { 224 t.Fatal(err) 225 } 226 227 // Fund a partial transaction. 228 fund := types.NewCurrency64(30e6) 229 txnBuilder := tpt.wallet.StartTransaction() 230 err = txnBuilder.FundSiacoins(fund) 231 if err != nil { 232 t.Fatal(err) 233 } 234 txnBuilder.AddMinerFee(fund) 235 // wholeTransaction is set to false so that we can use the same signature 236 // to create a double spend. 237 txnSet, err := txnBuilder.Sign(false) 238 if err != nil { 239 t.Fatal(err) 240 } 241 if len(txnSet) <= 1 { 242 t.Fatal("test is invalid unless the transaction set has two or more transactions") 243 } 244 // Check that the second transaction is dependent on the first. 245 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 246 if err == nil { 247 t.Fatal("transaction set must have dependent transactions") 248 } 249 250 // Submit the set to the pool, followed by just the transaction. 251 err = tpt.tpool.AcceptTransactionSet(txnSet) 252 if err != nil { 253 t.Fatal("super setting is not working:", err) 254 } 255 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 256 if err != modules.ErrDuplicateTransactionSet { 257 t.Fatal(err) 258 } 259 } 260 261 // TestIntegrationTransactionChild submits a single transaction to the network, 262 // followed by a child transaction. 263 func TestIntegrationTransactionChild(t *testing.T) { 264 if testing.Short() { 265 t.SkipNow() 266 } 267 // Create a transaction pool tester. 268 tpt, err := createTpoolTester("TestTransactionChild") 269 if err != nil { 270 t.Fatal(err) 271 } 272 273 // Fund a partial transaction. 274 fund := types.NewCurrency64(30e6) 275 txnBuilder := tpt.wallet.StartTransaction() 276 err = txnBuilder.FundSiacoins(fund) 277 if err != nil { 278 t.Fatal(err) 279 } 280 txnBuilder.AddMinerFee(fund) 281 // wholeTransaction is set to false so that we can use the same signature 282 // to create a double spend. 283 txnSet, err := txnBuilder.Sign(false) 284 if err != nil { 285 t.Fatal(err) 286 } 287 if len(txnSet) <= 1 { 288 t.Fatal("test is invalid unless the transaction set has two or more transactions") 289 } 290 // Check that the second transaction is dependent on the first. 291 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txnSet[1]}) 292 if err == nil { 293 t.Fatal("transaction set must have dependent transactions") 294 } 295 296 // Submit the first transaction in the set to the transaction pool. 297 err = tpt.tpool.AcceptTransactionSet(txnSet[:1]) 298 if err != nil { 299 t.Fatal("first transaction in the transaction set was not valid?") 300 } 301 err = tpt.tpool.AcceptTransactionSet(txnSet[1:]) 302 if err != nil { 303 t.Fatal("child transaction not seen as valid") 304 } 305 } 306 307 // TestIntegrationNilAccept tries submitting a nil transaction set and a 0-len 308 // transaction set to the transaction pool. 309 func TestIntegrationNilAccept(t *testing.T) { 310 if testing.Short() { 311 t.SkipNow() 312 } 313 tpt, err := createTpoolTester("TestTransactionChild") 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 err = tpt.tpool.AcceptTransactionSet(nil) 319 if err == nil { 320 t.Error("no error returned when submitting nothing to the transaction pool") 321 } 322 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{}) 323 if err == nil { 324 t.Error("no error returned when submitting nothing to the transaction pool") 325 } 326 } 327 328 // TestAcceptFCAndConflictingRevision checks that the transaction pool 329 // correctly accepts a file contract in a transaction set followed by a correct 330 // revision to that file contract in the a following transaction set, with no 331 // block separating them. 332 func TestAcceptFCAndConflictingRevision(t *testing.T) { 333 if testing.Short() { 334 t.SkipNow() 335 } 336 tpt, err := createTpoolTester("TestAcceptFCAndConflictingRevision") 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 // Create and fund a valid file contract. 342 builder := tpt.wallet.StartTransaction() 343 payout := types.NewCurrency64(1e9) 344 err = builder.FundSiacoins(payout) 345 if err != nil { 346 t.Fatal(err) 347 } 348 builder.AddFileContract(types.FileContract{ 349 WindowStart: tpt.cs.Height() + 2, 350 WindowEnd: tpt.cs.Height() + 5, 351 Payout: payout, 352 ValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 353 MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 354 UnlockHash: types.UnlockConditions{}.UnlockHash(), 355 }) 356 tSet, err := builder.Sign(true) 357 if err != nil { 358 t.Fatal(err) 359 } 360 err = tpt.tpool.AcceptTransactionSet(tSet) 361 if err != nil { 362 t.Fatal(err) 363 } 364 fcid := tSet[len(tSet)-1].FileContractID(0) 365 366 // Create a file contract revision and submit it. 367 rSet := []types.Transaction{{ 368 FileContractRevisions: []types.FileContractRevision{{ 369 ParentID: fcid, 370 NewRevisionNumber: 2, 371 372 NewWindowStart: tpt.cs.Height() + 2, 373 NewWindowEnd: tpt.cs.Height() + 5, 374 NewValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 375 NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 376 }}, 377 }} 378 err = tpt.tpool.AcceptTransactionSet(rSet) 379 if err != nil { 380 t.Fatal(err) 381 } 382 } 383 384 // TestPartialConfirmation checks that the transaction pool correctly accepts a 385 // transaction set which has parents that have been accepted by the consensus 386 // set but not the whole set has been accepted by the consensus set. 387 func TestPartialConfirmation(t *testing.T) { 388 if testing.Short() { 389 t.SkipNow() 390 } 391 tpt, err := createTpoolTester("TestPartialConfirmation") 392 if err != nil { 393 t.Fatal(err) 394 } 395 396 // Create and fund a valid file contract. 397 builder := tpt.wallet.StartTransaction() 398 payout := types.NewCurrency64(1e9) 399 err = builder.FundSiacoins(payout) 400 if err != nil { 401 t.Fatal(err) 402 } 403 builder.AddFileContract(types.FileContract{ 404 WindowStart: tpt.cs.Height() + 2, 405 WindowEnd: tpt.cs.Height() + 5, 406 Payout: payout, 407 ValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 408 MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 409 UnlockHash: types.UnlockConditions{}.UnlockHash(), 410 }) 411 tSet, err := builder.Sign(true) 412 if err != nil { 413 t.Fatal(err) 414 } 415 fcid := tSet[len(tSet)-1].FileContractID(0) 416 417 // Create a file contract revision. 418 rSet := []types.Transaction{{ 419 FileContractRevisions: []types.FileContractRevision{{ 420 ParentID: fcid, 421 NewRevisionNumber: 2, 422 423 NewWindowStart: tpt.cs.Height() + 2, 424 NewWindowEnd: tpt.cs.Height() + 5, 425 NewValidProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 426 NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}}, 427 }}, 428 }} 429 430 // Combine the contract and revision in to a single set. 431 fullSet := append(tSet, rSet...) 432 433 // Get the tSet onto the blockchain. 434 unsolvedBlock, target, err := tpt.miner.BlockForWork() 435 if err != nil { 436 t.Fatal(err) 437 } 438 unsolvedBlock.Transactions = append(unsolvedBlock.Transactions, tSet...) 439 solvedBlock, solved := tpt.miner.SolveBlock(unsolvedBlock, target) 440 if !solved { 441 t.Fatal("Failed to solve block") 442 } 443 err = tpt.cs.AcceptBlock(solvedBlock) 444 if err != nil { 445 t.Fatal(err) 446 } 447 448 // Try to get the full set into the transaction pool. The transaction pool 449 // should recognize that the set is partially accepted, and be able to 450 // accept on the the transactions that are new and are not yet on the 451 // blockchain. 452 err = tpt.tpool.AcceptTransactionSet(fullSet) 453 if err != nil { 454 t.Fatal(err) 455 } 456 } 457 458 // TestPartialConfirmationWeave checks that the transaction pool correctly 459 // accepts a transaction set which has parents that have been accepted by the 460 // consensus set but not the whole set has been accepted by the consensus set, 461 // this time weaving the dependencies, such that the first transaction is not 462 // in the consensus set, the second is, and the third has both as dependencies. 463 func TestPartialConfirmationWeave(t *testing.T) { 464 if testing.Short() { 465 t.SkipNow() 466 } 467 tpt, err := createTpoolTester("TestPartialConfirmation") 468 if err != nil { 469 t.Fatal(err) 470 } 471 472 // Step 1: create an output to the empty address in a tx. 473 // Step 2: create a second output to the empty address in another tx. 474 // Step 3: create a transaction using both those outputs. 475 // Step 4: mine the txn set in step 2 476 // Step 5: Submit the complete set. 477 478 // Create a transaction with a single output to a fully controlled address. 479 emptyUH := types.UnlockConditions{}.UnlockHash() 480 builder1 := tpt.wallet.StartTransaction() 481 funding1 := types.NewCurrency64(1e9) 482 err = builder1.FundSiacoins(funding1) 483 if err != nil { 484 t.Fatal(err) 485 } 486 scOutput1 := types.SiacoinOutput{ 487 Value: funding1, 488 UnlockHash: emptyUH, 489 } 490 i1 := builder1.AddSiacoinOutput(scOutput1) 491 tSet1, err := builder1.Sign(true) 492 if err != nil { 493 t.Fatal(err) 494 } 495 // Submit to the transaction pool and mine the block, to minimize 496 // complexity. 497 err = tpt.tpool.AcceptTransactionSet(tSet1) 498 if err != nil { 499 t.Fatal(err) 500 } 501 _, err = tpt.miner.AddBlock() 502 if err != nil { 503 t.Fatal(err) 504 } 505 506 // Create a second output to the fully controlled address, to fund the 507 // second transaction in the weave. 508 builder2 := tpt.wallet.StartTransaction() 509 funding2 := types.NewCurrency64(2e9) 510 err = builder2.FundSiacoins(funding2) 511 if err != nil { 512 t.Fatal(err) 513 } 514 scOutput2 := types.SiacoinOutput{ 515 Value: funding2, 516 UnlockHash: emptyUH, 517 } 518 i2 := builder2.AddSiacoinOutput(scOutput2) 519 tSet2, err := builder2.Sign(true) 520 if err != nil { 521 t.Fatal(err) 522 } 523 // Submit to the transaction pool and mine the block, to minimize 524 // complexity. 525 err = tpt.tpool.AcceptTransactionSet(tSet2) 526 if err != nil { 527 t.Fatal(err) 528 } 529 _, err = tpt.miner.AddBlock() 530 if err != nil { 531 t.Fatal(err) 532 } 533 534 // Create a passthrough transaction for output1 and output2, so that they 535 // can be used as unconfirmed dependencies. 536 txn1 := types.Transaction{ 537 SiacoinInputs: []types.SiacoinInput{{ 538 ParentID: tSet1[len(tSet1)-1].SiacoinOutputID(i1), 539 }}, 540 SiacoinOutputs: []types.SiacoinOutput{{ 541 Value: funding1, 542 UnlockHash: emptyUH, 543 }}, 544 } 545 txn2 := types.Transaction{ 546 SiacoinInputs: []types.SiacoinInput{{ 547 ParentID: tSet2[len(tSet2)-1].SiacoinOutputID(i2), 548 }}, 549 SiacoinOutputs: []types.SiacoinOutput{{ 550 Value: funding2, 551 UnlockHash: emptyUH, 552 }}, 553 } 554 555 // Create a child transaction that depends on inputs from both txn1 and 556 // txn2. 557 child := types.Transaction{ 558 SiacoinInputs: []types.SiacoinInput{ 559 { 560 ParentID: txn1.SiacoinOutputID(0), 561 }, 562 { 563 ParentID: txn2.SiacoinOutputID(0), 564 }, 565 }, 566 SiacoinOutputs: []types.SiacoinOutput{{ 567 Value: funding1.Add(funding2), 568 }}, 569 } 570 571 // Get txn2 accepted into the consensus set. 572 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn2}) 573 if err != nil { 574 t.Fatal(err) 575 } 576 _, err = tpt.miner.AddBlock() 577 if err != nil { 578 t.Fatal(err) 579 } 580 581 // Try to get the set of txn1, txn2, and child accepted into the 582 // transaction pool. 583 err = tpt.tpool.AcceptTransactionSet([]types.Transaction{txn1, txn2, child}) 584 if err != nil { 585 t.Fatal(err) 586 } 587 }