github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/validtransaction_test.go (about) 1 package consensus 2 3 import ( 4 "testing" 5 6 "github.com/NebulousLabs/Sia/types" 7 ) 8 9 // TestTryValidTransactionSet submits a valid transaction set to the 10 // TryTransactionSet method. 11 func TestTryValidTransactionSet(t *testing.T) { 12 if testing.Short() { 13 t.SkipNow() 14 } 15 cst, err := createConsensusSetTester("TestValidTransaction") 16 if err != nil { 17 t.Fatal(err) 18 } 19 defer cst.Close() 20 initialHash := cst.cs.dbConsensusChecksum() 21 22 // Try a valid transaction. 23 _, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{}) 24 if err != nil { 25 t.Fatal(err) 26 } 27 txns := cst.tpool.TransactionList() 28 cc, err := cst.cs.TryTransactionSet(txns) 29 if err != nil { 30 t.Error(err) 31 } 32 if cst.cs.dbConsensusChecksum() != initialHash { 33 t.Error("TryTransactionSet did not resotre order") 34 } 35 if len(cc.SiacoinOutputDiffs) == 0 { 36 t.Error("consensus change is missing diffs after verifying a transction clump") 37 } 38 } 39 40 // TestTryInvalidTransactionSet submits an invalid transaction set to the 41 // TryTransaction method. 42 func TestTryInvalidTransactionSet(t *testing.T) { 43 if testing.Short() { 44 t.SkipNow() 45 } 46 cst, err := createConsensusSetTester("TestValidTransaction") 47 if err != nil { 48 t.Fatal(err) 49 } 50 defer cst.Close() 51 initialHash := cst.cs.dbConsensusChecksum() 52 53 // Try a valid transaction followed by an invalid transaction. 54 _, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{}) 55 if err != nil { 56 t.Fatal(err) 57 } 58 txns := cst.tpool.TransactionList() 59 txn := types.Transaction{ 60 SiacoinInputs: []types.SiacoinInput{{}}, 61 } 62 txns = append(txns, txn) 63 cc, err := cst.cs.TryTransactionSet(txns) 64 if err == nil { 65 t.Error("bad transaction survived filter") 66 } 67 if cst.cs.dbConsensusChecksum() != initialHash { 68 t.Error("TryTransactionSet did not restore order") 69 } 70 if len(cc.SiacoinOutputDiffs) != 0 { 71 t.Error("consensus change was not empty despite an error being returned") 72 } 73 } 74 75 /* 76 // TestValidSiacoins probes the validSiacoins method of the consensus set. 77 func TestValidSiacoins(t *testing.T) { 78 if testing.Short() { 79 t.SkipNow() 80 } 81 cst, err := createConsensusSetTester("TestValidSiacoins") 82 if err != nil { 83 t.Fatal(err) 84 } 85 defer cst.closeCst() 86 87 // Create a transaction pointing to a nonexistent siacoin output. 88 txn := types.Transaction{ 89 SiacoinInputs: []types.SiacoinInput{{}}, 90 } 91 err = cst.cs.validSiacoins(txn) 92 if err != ErrMissingSiacoinOutput { 93 t.Error(err) 94 } 95 96 // Create a transaction with invalid unlock conditions. 97 var scoid types.SiacoinOutputID 98 cst.cs.db.forEachSiacoinOutputs(func(mapScoid types.SiacoinOutputID, sco types.SiacoinOutput) { 99 scoid = mapScoid 100 }) 101 txn = types.Transaction{ 102 SiacoinInputs: []types.SiacoinInput{{ 103 ParentID: scoid, 104 }}, 105 } 106 err = cst.cs.validSiacoins(txn) 107 if err != ErrWrongUnlockConditions { 108 t.Error(err) 109 } 110 111 // Create a txn with more outputs than inputs. 112 txn = types.Transaction{ 113 SiacoinOutputs: []types.SiacoinOutput{{ 114 Value: types.NewCurrency64(1), 115 }}, 116 } 117 err = cst.cs.validSiacoins(txn) 118 if err != ErrSiacoinInputOutputMismatch { 119 t.Error(err) 120 } 121 } 122 123 // TestStorageProofSegment probes the storageProofSegment method of the 124 // consensus set. 125 func TestStorageProofSegment(t *testing.T) { 126 if testing.Short() { 127 t.SkipNow() 128 } 129 cst, err := createConsensusSetTester("TestStorageProofSegment") 130 if err != nil { 131 t.Fatal(err) 132 } 133 defer cst.closeCst() 134 135 // Submit a file contract that is unrecognized. 136 _, err = cst.cs.storageProofSegment(types.FileContractID{}) 137 if err != ErrUnrecognizedFileContractID { 138 t.Error(err) 139 } 140 141 // Try to get the segment of an unfinished file contract. 142 cst.cs.db.addFileContracts(types.FileContractID{}, types.FileContract{ 143 WindowStart: 100000, 144 }) 145 _, err = cst.cs.storageProofSegment(types.FileContractID{}) 146 if err != ErrUnfinishedFileContract { 147 t.Error(err) 148 } 149 } 150 151 // TestStorageProofSegmentRandomness checks that the storageProofSegment method 152 // is producing outputs that pass an imperfect randomness check (gzip). 153 func TestStorageProofSegmentRandomness(t *testing.T) { 154 t.Skip("randomness check takes a long time") 155 cst, err := createConsensusSetTester("TestStorageProofSegment") 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer cst.closeCst() 160 161 // Add a file contract to the consensus set that can be used to probe the 162 // storage segment. 163 var outputs []byte 164 for i := 0; i < 32*256; i++ { 165 var fcid types.FileContractID 166 rand.Read(fcid[:]) 167 fc := types.FileContract{ 168 WindowStart: 2, 169 FileSize: 256 * 64, 170 } 171 cst.cs.db.addFileContracts(fcid, fc) 172 index, err := cst.cs.storageProofSegment(fcid) 173 if err != nil { 174 t.Error(err) 175 } 176 outputs = append(outputs, byte(index)) 177 } 178 179 // Perform entropy testing on 'outputs' to verify randomness. 180 var b bytes.Buffer 181 zip := gzip.NewWriter(&b) 182 _, err = zip.Write(outputs) 183 if err != nil { 184 t.Fatal(err) 185 } 186 zip.Close() 187 if b.Len() < len(outputs) { 188 t.Error("supposedly high entropy random segments have been compressed!") 189 } 190 } 191 192 // TestValidStorageProofs probes the validStorageProofs method of the consensus 193 // set. 194 func TestValidStorageProofs(t *testing.T) { 195 if testing.Short() { 196 t.SkipNow() 197 } 198 cst, err := createConsensusSetTester("TestValidStorageProofs") 199 if err != nil { 200 t.Fatal(err) 201 } 202 defer cst.closeCst() 203 204 // COMPATv0.4.0 205 // 206 // Mine 10 blocks so that the post-hardfork rules are in effect. 207 for i := 0; i < 10; i++ { 208 block, _ := cst.miner.FindBlock() 209 err = cst.cs.AcceptBlock(block) 210 if err != nil { 211 t.Fatal(err) 212 } 213 } 214 215 // Create a file contract for which a storage proof can be created. 216 var fcid types.FileContractID 217 fcid[0] = 12 218 simFile := make([]byte, 64*1024) 219 _, err = rand.Read(simFile) 220 if err != nil { 221 t.Fatal(err) 222 } 223 buffer := bytes.NewReader(simFile) 224 root, err := crypto.ReaderMerkleRoot(buffer) 225 if err != nil { 226 t.Fatal(err) 227 } 228 fc := types.FileContract{ 229 FileSize: 64 * 1024, 230 FileMerkleRoot: root, 231 WindowStart: 2, 232 WindowEnd: 1200, 233 } 234 cst.cs.db.addFileContracts(fcid, fc) 235 buffer.Seek(0, 0) 236 237 // Create a transaction with a storage proof. 238 proofIndex, err := cst.cs.storageProofSegment(fcid) 239 if err != nil { 240 t.Fatal(err) 241 } 242 base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex) 243 if err != nil { 244 t.Fatal(err) 245 } 246 txn := types.Transaction{ 247 StorageProofs: []types.StorageProof{{ 248 ParentID: fcid, 249 HashSet: proofSet, 250 }}, 251 } 252 copy(txn.StorageProofs[0].Segment[:], base) 253 err = cst.cs.validStorageProofs(txn) 254 if err != nil { 255 t.Error(err) 256 } 257 258 // Corrupt the proof set. 259 proofSet[0][0]++ 260 txn = types.Transaction{ 261 StorageProofs: []types.StorageProof{{ 262 ParentID: fcid, 263 HashSet: proofSet, 264 }}, 265 } 266 copy(txn.StorageProofs[0].Segment[:], base) 267 err = cst.cs.validStorageProofs(txn) 268 if err != ErrInvalidStorageProof { 269 t.Error(err) 270 } 271 272 // Try to validate a proof for a file contract that doesn't exist. 273 txn.StorageProofs[0].ParentID = types.FileContractID{} 274 err = cst.cs.validStorageProofs(txn) 275 if err != ErrUnrecognizedFileContractID { 276 t.Error(err) 277 } 278 279 // Try a proof set where there is padding on the last segment in the file. 280 file := make([]byte, 100) 281 _, err = rand.Read(file) 282 if err != nil { 283 t.Fatal(err) 284 } 285 buffer = bytes.NewReader(file) 286 root, err = crypto.ReaderMerkleRoot(buffer) 287 if err != nil { 288 t.Fatal(err) 289 } 290 fc = types.FileContract{ 291 FileSize: 100, 292 FileMerkleRoot: root, 293 WindowStart: 2, 294 WindowEnd: 1200, 295 } 296 buffer.Seek(0, 0) 297 298 // Find a proofIndex that has the value '1'. 299 for { 300 fcid[0]++ 301 cst.cs.db.addFileContracts(fcid, fc) 302 proofIndex, err = cst.cs.storageProofSegment(fcid) 303 if err != nil { 304 t.Fatal(err) 305 } 306 if proofIndex == 1 { 307 break 308 } 309 } 310 base, proofSet, err = crypto.BuildReaderProof(buffer, proofIndex) 311 if err != nil { 312 t.Fatal(err) 313 } 314 txn = types.Transaction{ 315 StorageProofs: []types.StorageProof{{ 316 ParentID: fcid, 317 HashSet: proofSet, 318 }}, 319 } 320 copy(txn.StorageProofs[0].Segment[:], base) 321 err = cst.cs.validStorageProofs(txn) 322 if err != nil { 323 t.Fatal(err) 324 } 325 } 326 327 // COMPATv0.4.0 328 // 329 // TestPreForkValidStorageProofs checks that storage proofs which are invalid 330 // before the hardfork (but valid afterwards) are still rejected before the 331 // hardfork). 332 func TestPreForkValidStorageProofs(t *testing.T) { 333 if testing.Short() { 334 t.SkipNow() 335 } 336 cst, err := createConsensusSetTester("TestPreForkValidStorageProofs") 337 if err != nil { 338 t.Fatal(err) 339 } 340 defer cst.closeCst() 341 342 // Try a proof set where there is padding on the last segment in the file. 343 file := make([]byte, 100) 344 _, err = rand.Read(file) 345 if err != nil { 346 t.Fatal(err) 347 } 348 buffer := bytes.NewReader(file) 349 root, err := crypto.ReaderMerkleRoot(buffer) 350 if err != nil { 351 t.Fatal(err) 352 } 353 fc := types.FileContract{ 354 FileSize: 100, 355 FileMerkleRoot: root, 356 WindowStart: 2, 357 WindowEnd: 1200, 358 } 359 buffer.Seek(0, 0) 360 361 // Find a proofIndex that has the value '1'. 362 var fcid types.FileContractID 363 var proofIndex uint64 364 for { 365 fcid[0]++ 366 cst.cs.db.addFileContracts(fcid, fc) 367 proofIndex, err = cst.cs.storageProofSegment(fcid) 368 if err != nil { 369 t.Fatal(err) 370 } 371 if proofIndex == 1 { 372 break 373 } 374 } 375 base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex) 376 if err != nil { 377 t.Fatal(err) 378 } 379 txn := types.Transaction{ 380 StorageProofs: []types.StorageProof{{ 381 ParentID: fcid, 382 HashSet: proofSet, 383 }}, 384 } 385 copy(txn.StorageProofs[0].Segment[:], base) 386 err = cst.cs.validStorageProofs(txn) 387 if err != ErrInvalidStorageProof { 388 t.Fatal(err) 389 } 390 } 391 392 // TestValidFileContractRevisions probes the validFileContractRevisions method 393 // of the consensus set. 394 func TestValidFileContractRevisions(t *testing.T) { 395 if testing.Short() { 396 t.SkipNow() 397 } 398 cst, err := createConsensusSetTester("TestValidFileContractRevisions") 399 if err != nil { 400 t.Fatal(err) 401 } 402 defer cst.closeCst() 403 404 // Grab an address + unlock conditions for the transaction. 405 unlockConditions, err := cst.wallet.NextAddress() 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 // Create a file contract for which a storage proof can be created. 411 var fcid types.FileContractID 412 fcid[0] = 12 413 simFile := make([]byte, 64*1024) 414 rand.Read(simFile) 415 buffer := bytes.NewReader(simFile) 416 root, err := crypto.ReaderMerkleRoot(buffer) 417 if err != nil { 418 t.Fatal(err) 419 } 420 fc := types.FileContract{ 421 FileSize: 64 * 1024, 422 FileMerkleRoot: root, 423 WindowStart: 102, 424 WindowEnd: 1200, 425 UnlockHash: unlockConditions.UnlockHash(), 426 RevisionNumber: 1, 427 } 428 cst.cs.db.addFileContracts(fcid, fc) 429 430 // Try a working file contract revision. 431 txn := types.Transaction{ 432 FileContractRevisions: []types.FileContractRevision{ 433 { 434 ParentID: fcid, 435 UnlockConditions: unlockConditions, 436 NewRevisionNumber: 2, 437 }, 438 }, 439 } 440 err = cst.cs.validFileContractRevisions(txn) 441 if err != nil { 442 t.Error(err) 443 } 444 445 // Try a transaction with an insufficient revision number. 446 txn = types.Transaction{ 447 FileContractRevisions: []types.FileContractRevision{ 448 { 449 ParentID: fcid, 450 UnlockConditions: unlockConditions, 451 NewRevisionNumber: 1, 452 }, 453 }, 454 } 455 err = cst.cs.validFileContractRevisions(txn) 456 if err != ErrLowRevisionNumber { 457 t.Error(err) 458 } 459 txn = types.Transaction{ 460 FileContractRevisions: []types.FileContractRevision{ 461 { 462 ParentID: fcid, 463 UnlockConditions: unlockConditions, 464 NewRevisionNumber: 0, 465 }, 466 }, 467 } 468 err = cst.cs.validFileContractRevisions(txn) 469 if err != ErrLowRevisionNumber { 470 t.Error(err) 471 } 472 473 // Submit a file contract revision pointing to an invalid parent. 474 txn.FileContractRevisions[0].ParentID[0]-- 475 err = cst.cs.validFileContractRevisions(txn) 476 if err != ErrUnrecognizedFileContractID { 477 t.Error(err) 478 } 479 txn.FileContractRevisions[0].ParentID[0]++ 480 481 // Submit a file contract revision for a file contract whose window has 482 // already opened. 483 fc = cst.cs.db.getFileContracts(fcid) 484 fc.WindowStart = 0 485 cst.cs.db.rmFileContracts(fcid) 486 cst.cs.db.addFileContracts(fcid, fc) 487 txn.FileContractRevisions[0].NewRevisionNumber = 3 488 err = cst.cs.validFileContractRevisions(txn) 489 if err != ErrLateRevision { 490 t.Error(err) 491 } 492 493 // Submit a file contract revision with incorrect unlock conditions. 494 fc.WindowStart = 100 495 cst.cs.db.rmFileContracts(fcid) 496 cst.cs.db.addFileContracts(fcid, fc) 497 txn.FileContractRevisions[0].UnlockConditions.Timelock++ 498 err = cst.cs.validFileContractRevisions(txn) 499 if err != ErrWrongUnlockConditions { 500 t.Error(err) 501 } 502 txn.FileContractRevisions[0].UnlockConditions.Timelock-- 503 504 // Submit file contract revisions for file contracts with altered payouts. 505 txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{ 506 Value: types.NewCurrency64(1), 507 }} 508 txn.FileContractRevisions[0].NewMissedProofOutputs = []types.SiacoinOutput{{ 509 Value: types.NewCurrency64(1), 510 }} 511 err = cst.cs.validFileContractRevisions(txn) 512 if err != ErrAlteredRevisionPayouts { 513 t.Error(err) 514 } 515 txn.FileContractRevisions[0].NewValidProofOutputs = nil 516 err = cst.cs.validFileContractRevisions(txn) 517 if err != ErrAlteredRevisionPayouts { 518 t.Error(err) 519 } 520 txn.FileContractRevisions[0].NewValidProofOutputs = []types.SiacoinOutput{{ 521 Value: types.NewCurrency64(1), 522 }} 523 txn.FileContractRevisions[0].NewMissedProofOutputs = nil 524 err = cst.cs.validFileContractRevisions(txn) 525 if err != ErrAlteredRevisionPayouts { 526 t.Error(err) 527 } 528 } 529 530 // TestValidSiafunds probes the validSiafunds mthod of the consensus set. 531 func TestValidSiafunds(t *testing.T) { 532 if testing.Short() { 533 t.SkipNow() 534 } 535 cst, err := createConsensusSetTester("TestValidSiafunds") 536 if err != nil { 537 t.Fatal(err) 538 } 539 defer cst.closeCst() 540 541 // Create a transaction pointing to a nonexistent siafund output. 542 txn := types.Transaction{ 543 SiafundInputs: []types.SiafundInput{{}}, 544 } 545 err = cst.cs.validSiafunds(txn) 546 if err != ErrMissingSiafundOutput { 547 t.Error(err) 548 } 549 550 // Create a transaction with invalid unlock conditions. 551 var sfoid types.SiafundOutputID 552 cst.cs.db.forEachSiafundOutputs(func(mapSfoid types.SiafundOutputID, sfo types.SiafundOutput) { 553 sfoid = mapSfoid 554 // pointless to do this but I can't think of a better way. 555 }) 556 txn = types.Transaction{ 557 SiafundInputs: []types.SiafundInput{{ 558 ParentID: sfoid, 559 UnlockConditions: types.UnlockConditions{Timelock: 12345}, // avoid collisions with existing outputs 560 }}, 561 } 562 err = cst.cs.validSiafunds(txn) 563 if err != ErrWrongUnlockConditions { 564 t.Error(err) 565 } 566 567 // Create a transaction with more outputs than inputs. 568 txn = types.Transaction{ 569 SiafundOutputs: []types.SiafundOutput{{ 570 Value: types.NewCurrency64(1), 571 }}, 572 } 573 err = cst.cs.validSiafunds(txn) 574 if err != ErrSiafundInputOutputMismatch { 575 t.Error(err) 576 } 577 } 578 579 // TestValidTransaction probes the validTransaction method of the consensus 580 // set. 581 func TestValidTransaction(t *testing.T) { 582 if testing.Short() { 583 t.SkipNow() 584 } 585 cst, err := createConsensusSetTester("TestValidTransaction") 586 if err != nil { 587 t.Fatal(err) 588 } 589 defer cst.closeCst() 590 591 // Create a transaction that is not standalone valid. 592 txn := types.Transaction{ 593 FileContracts: []types.FileContract{{ 594 WindowStart: 0, 595 }}, 596 } 597 err = cst.cs.validTransaction(txn) 598 if err == nil { 599 t.Error("transaction is valid") 600 } 601 602 // Create a transaction with invalid siacoins. 603 txn = types.Transaction{ 604 SiacoinInputs: []types.SiacoinInput{{}}, 605 } 606 err = cst.cs.validTransaction(txn) 607 if err == nil { 608 t.Error("transaction is valid") 609 } 610 611 // Create a transaction with invalid storage proofs. 612 txn = types.Transaction{ 613 StorageProofs: []types.StorageProof{{}}, 614 } 615 err = cst.cs.validTransaction(txn) 616 if err == nil { 617 t.Error("transaction is valid") 618 } 619 620 // Create a transaction with invalid file contract revisions. 621 txn = types.Transaction{ 622 FileContractRevisions: []types.FileContractRevision{{ 623 NewWindowStart: 5000, 624 NewWindowEnd: 5005, 625 ParentID: types.FileContractID{}, 626 }}, 627 } 628 err = cst.cs.validTransaction(txn) 629 if err == nil { 630 t.Error("transaction is valid") 631 } 632 633 // Create a transaction with invalid siafunds. 634 txn = types.Transaction{ 635 SiafundInputs: []types.SiafundInput{{}}, 636 } 637 err = cst.cs.validTransaction(txn) 638 if err == nil { 639 t.Error("transaction is valid") 640 } 641 } 642 */