github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/accept_txntypes_test.go (about) 1 package consensus 2 3 import ( 4 "testing" 5 6 "github.com/NebulousLabs/Sia/crypto" 7 "github.com/NebulousLabs/Sia/types" 8 ) 9 10 // testBlockSuite tests a wide variety of blocks. 11 func (cst *consensusSetTester) testBlockSuite() { 12 cst.testSimpleBlock() 13 cst.testSpendSiacoinsBlock() 14 cst.testValidStorageProofBlocks() 15 cst.testMissedStorageProofBlocks() 16 cst.testFileContractRevision() 17 cst.testSpendSiafunds() 18 } 19 20 // testSimpleBlock mines a simple block (no transactions except those 21 // automatically added by the miner) and adds it to the consnesus set. 22 func (cst *consensusSetTester) testSimpleBlock() { 23 // Get the starting hash of the consenesus set. 24 initialChecksum := cst.cs.dbConsensusChecksum() 25 initialHeight := cst.cs.dbBlockHeight() 26 initialBlockID := cst.cs.dbCurrentBlockID() 27 28 // Mine and submit a block 29 block, err := cst.miner.AddBlock() 30 if err != nil { 31 panic(err) 32 } 33 34 // Check that the consensus info functions changed as expected. 35 resultingChecksum := cst.cs.dbConsensusChecksum() 36 if initialChecksum == resultingChecksum { 37 panic("checksum is unchanged after mining a block") 38 } 39 resultingHeight := cst.cs.dbBlockHeight() 40 if resultingHeight != initialHeight+1 { 41 panic("height of consensus set did not increase as expected") 42 } 43 currentPB := cst.cs.dbCurrentProcessedBlock() 44 if currentPB.Block.ParentID != initialBlockID { 45 panic("new processed block does not have correct information") 46 } 47 if currentPB.Block.ID() != block.ID() { 48 panic("the state's current block is not reporting as the recently mined block.") 49 } 50 if currentPB.Height != initialHeight+1 { 51 panic("the processed block is not reporting the correct height") 52 } 53 pathID, err := cst.cs.dbGetPath(currentPB.Height) 54 if err != nil { 55 panic(err) 56 } 57 if pathID != block.ID() { 58 panic("current path does not point to the correct block") 59 } 60 61 // Revert the block that was just added to the consensus set and check for 62 // parity with the original state of consensus. 63 parent, err := cst.cs.dbGetBlockMap(currentPB.Block.ParentID) 64 if err != nil { 65 panic(err) 66 } 67 _, _, err = cst.cs.dbForkBlockchain(parent) 68 if err != nil { 69 panic(err) 70 } 71 if cst.cs.dbConsensusChecksum() != initialChecksum { 72 panic("adding and reverting a block changed the consensus set") 73 } 74 // Re-add the block and check for parity with the first time it was added. 75 // This test is useful because a different codepath is followed if the 76 // diffs have already been generated. 77 _, _, err = cst.cs.dbForkBlockchain(currentPB) 78 if err != nil { 79 panic(err) 80 } 81 if cst.cs.dbConsensusChecksum() != resultingChecksum { 82 panic("adding, reverting, and reading a block was inconsistent with just adding the block") 83 } 84 } 85 86 // TestIntegrationSimpleBlock creates a consensus set tester and uses it to 87 // call testSimpleBlock. 88 func TestIntegrationSimpleBlock(t *testing.T) { 89 if testing.Short() { 90 t.SkipNow() 91 } 92 cst, err := createConsensusSetTester("TestIntegrationSimpleBlock") 93 if err != nil { 94 t.Fatal(err) 95 } 96 defer cst.Close() 97 cst.testSimpleBlock() 98 } 99 100 // testSpendSiacoinsBlock mines a block with a transaction spending siacoins 101 // and adds it to the consensus set. 102 func (cst *consensusSetTester) testSpendSiacoinsBlock() { 103 // Create a random destination address for the output in the transaction. 104 destAddr := randAddress() 105 106 // Create a block containing a transaction with a valid siacoin output. 107 txnValue := types.NewCurrency64(1200) 108 txnBuilder := cst.wallet.StartTransaction() 109 err := txnBuilder.FundSiacoins(txnValue) 110 if err != nil { 111 panic(err) 112 } 113 outputIndex := txnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: txnValue, UnlockHash: destAddr}) 114 txnSet, err := txnBuilder.Sign(true) 115 if err != nil { 116 panic(err) 117 } 118 err = cst.tpool.AcceptTransactionSet(txnSet) 119 if err != nil { 120 panic(err) 121 } 122 123 // Mine and apply the block to the consensus set. 124 _, err = cst.miner.AddBlock() 125 if err != nil { 126 panic(err) 127 } 128 129 // See that the destination output was created. 130 outputID := txnSet[len(txnSet)-1].SiacoinOutputID(outputIndex) 131 sco, err := cst.cs.dbGetSiacoinOutput(outputID) 132 if err != nil { 133 panic(err) 134 } 135 if sco.Value.Cmp(txnValue) != 0 { 136 panic("output added with wrong value") 137 } 138 if sco.UnlockHash != destAddr { 139 panic("output sent to the wrong address") 140 } 141 } 142 143 // TestIntegrationSpendSiacoinsBlock creates a consensus set tester and uses it 144 // to call testSpendSiacoinsBlock. 145 func TestIntegrationSpendSiacoinsBlock(t *testing.T) { 146 if testing.Short() { 147 t.SkipNow() 148 } 149 cst, err := createConsensusSetTester("TestSpendSiacoinsBlock") 150 if err != nil { 151 t.Fatal(err) 152 } 153 defer cst.Close() 154 cst.testSpendSiacoinsBlock() 155 } 156 157 // testValidStorageProofBlocks adds a block with a file contract, and then 158 // submits a storage proof for that file contract. 159 func (cst *consensusSetTester) testValidStorageProofBlocks() { 160 // COMPATv0.4.0 - Step the block height up past the hardfork amount. This 161 // code stops nondeterministic failures when producing storage proofs that 162 // is related to buggy old code. 163 for cst.cs.dbBlockHeight() <= 10 { 164 _, err := cst.miner.AddBlock() 165 if err != nil { 166 panic(err) 167 } 168 } 169 170 // Create a file (as a bytes.Buffer) that will be used for the file 171 // contract. 172 filesize := uint64(4e3) 173 file, err := crypto.RandBytes(int(filesize)) 174 if err != nil { 175 panic(err) 176 } 177 merkleRoot := crypto.MerkleRoot(file) 178 179 // Create a file contract that will be successful. 180 validProofDest := randAddress() 181 payout := types.NewCurrency64(400e6) 182 fc := types.FileContract{ 183 FileSize: filesize, 184 FileMerkleRoot: merkleRoot, 185 WindowStart: cst.cs.dbBlockHeight() + 1, 186 WindowEnd: cst.cs.dbBlockHeight() + 2, 187 Payout: payout, 188 ValidProofOutputs: []types.SiacoinOutput{{ 189 UnlockHash: validProofDest, 190 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 191 }}, 192 MissedProofOutputs: []types.SiacoinOutput{{ 193 UnlockHash: types.UnlockHash{}, 194 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 195 }}, 196 } 197 198 // Submit a transaction with the file contract. 199 oldSiafundPool := cst.cs.dbGetSiafundPool() 200 txnBuilder := cst.wallet.StartTransaction() 201 err = txnBuilder.FundSiacoins(payout) 202 if err != nil { 203 panic(err) 204 } 205 fcIndex := txnBuilder.AddFileContract(fc) 206 txnSet, err := txnBuilder.Sign(true) 207 if err != nil { 208 panic(err) 209 } 210 err = cst.tpool.AcceptTransactionSet(txnSet) 211 if err != nil { 212 panic(err) 213 } 214 _, err = cst.miner.AddBlock() 215 if err != nil { 216 panic(err) 217 } 218 219 // Check that the siafund pool was increased by the tax on the payout. 220 siafundPool := cst.cs.dbGetSiafundPool() 221 if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 { 222 panic("siafund pool was not increased correctly") 223 } 224 225 // Check that the file contract made it into the database. 226 ti := len(txnSet) - 1 227 fcid := txnSet[ti].FileContractID(fcIndex) 228 _, err = cst.cs.dbGetFileContract(fcid) 229 if err != nil { 230 panic(err) 231 } 232 233 // Create and submit a storage proof for the file contract. 234 segmentIndex, err := cst.cs.StorageProofSegment(fcid) 235 if err != nil { 236 panic(err) 237 } 238 segment, hashSet := crypto.MerkleProof(file, segmentIndex) 239 sp := types.StorageProof{ 240 ParentID: fcid, 241 HashSet: hashSet, 242 } 243 copy(sp.Segment[:], segment) 244 txnBuilder = cst.wallet.StartTransaction() 245 txnBuilder.AddStorageProof(sp) 246 txnSet, err = txnBuilder.Sign(true) 247 if err != nil { 248 panic(err) 249 } 250 err = cst.tpool.AcceptTransactionSet(txnSet) 251 if err != nil { 252 panic(err) 253 } 254 _, err = cst.miner.AddBlock() 255 if err != nil { 256 panic(err) 257 } 258 259 // Check that the file contract has been removed. 260 _, err = cst.cs.dbGetFileContract(fcid) 261 if err != errNilItem { 262 panic("file contract should not exist in the database") 263 } 264 265 // Check that the siafund pool has not changed. 266 postProofPool := cst.cs.dbGetSiafundPool() 267 if postProofPool.Cmp(siafundPool) != 0 { 268 panic("siafund pool should not change after submitting a storage proof") 269 } 270 271 // Check that a delayed output was created for the valid proof. 272 spoid := fcid.StorageProofOutputID(types.ProofValid, 0) 273 dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid) 274 if err != nil { 275 panic(err) 276 } 277 if dsco.UnlockHash != fc.ValidProofOutputs[0].UnlockHash { 278 panic("wrong unlock hash in dsco") 279 } 280 if dsco.Value.Cmp(fc.ValidProofOutputs[0].Value) != 0 { 281 panic("wrong sco value in dsco") 282 } 283 } 284 285 // TestIntegrationValidStorageProofBlocks creates a consensus set tester and 286 // uses it to call testValidStorageProofBlocks. 287 func TestIntegrationValidStorageProofBlocks(t *testing.T) { 288 if testing.Short() { 289 t.SkipNow() 290 } 291 cst, err := createConsensusSetTester("TestIntegrationValidStorageProofBlocks") 292 if err != nil { 293 t.Fatal(err) 294 } 295 defer cst.Close() 296 cst.testValidStorageProofBlocks() 297 } 298 299 // testMissedStorageProofBlocks adds a block with a file contract, and then 300 // fails to submit a storage proof before expiration. 301 func (cst *consensusSetTester) testMissedStorageProofBlocks() { 302 // Create a file contract that will be successful. 303 filesize := uint64(4e3) 304 payout := types.NewCurrency64(400e6) 305 missedProofDest := randAddress() 306 fc := types.FileContract{ 307 FileSize: filesize, 308 FileMerkleRoot: crypto.Hash{}, 309 WindowStart: cst.cs.dbBlockHeight() + 1, 310 WindowEnd: cst.cs.dbBlockHeight() + 2, 311 Payout: payout, 312 ValidProofOutputs: []types.SiacoinOutput{{ 313 UnlockHash: types.UnlockHash{}, 314 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 315 }}, 316 MissedProofOutputs: []types.SiacoinOutput{{ 317 UnlockHash: missedProofDest, 318 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 319 }}, 320 } 321 322 // Submit a transaction with the file contract. 323 oldSiafundPool := cst.cs.dbGetSiafundPool() 324 txnBuilder := cst.wallet.StartTransaction() 325 err := txnBuilder.FundSiacoins(payout) 326 if err != nil { 327 panic(err) 328 } 329 fcIndex := txnBuilder.AddFileContract(fc) 330 txnSet, err := txnBuilder.Sign(true) 331 if err != nil { 332 panic(err) 333 } 334 err = cst.tpool.AcceptTransactionSet(txnSet) 335 if err != nil { 336 panic(err) 337 } 338 _, err = cst.miner.AddBlock() 339 if err != nil { 340 panic(err) 341 } 342 343 // Check that the siafund pool was increased by the tax on the payout. 344 siafundPool := cst.cs.dbGetSiafundPool() 345 if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 { 346 panic("siafund pool was not increased correctly") 347 } 348 349 // Check that the file contract made it into the database. 350 ti := len(txnSet) - 1 351 fcid := txnSet[ti].FileContractID(fcIndex) 352 _, err = cst.cs.dbGetFileContract(fcid) 353 if err != nil { 354 panic(err) 355 } 356 357 // Mine a block to close the storage proof window. 358 _, err = cst.miner.AddBlock() 359 if err != nil { 360 panic(err) 361 } 362 363 // Check that the file contract has been removed. 364 _, err = cst.cs.dbGetFileContract(fcid) 365 if err != errNilItem { 366 panic("file contract should not exist in the database") 367 } 368 369 // Check that the siafund pool has not changed. 370 postProofPool := cst.cs.dbGetSiafundPool() 371 if postProofPool.Cmp(siafundPool) != 0 { 372 panic("siafund pool should not change after submitting a storage proof") 373 } 374 375 // Check that a delayed output was created for the missed proof. 376 spoid := fcid.StorageProofOutputID(types.ProofMissed, 0) 377 dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid) 378 if err != nil { 379 panic(err) 380 } 381 if dsco.UnlockHash != fc.MissedProofOutputs[0].UnlockHash { 382 panic("wrong unlock hash in dsco") 383 } 384 if dsco.Value.Cmp(fc.MissedProofOutputs[0].Value) != 0 { 385 panic("wrong sco value in dsco") 386 } 387 } 388 389 // TestIntegrationMissedStorageProofBlocks creates a consensus set tester and 390 // uses it to call testMissedStorageProofBlocks. 391 func TestIntegrationMissedStorageProofBlocks(t *testing.T) { 392 if testing.Short() { 393 t.SkipNow() 394 } 395 cst, err := createConsensusSetTester("TestIntegrationMissedStorageProofBlocks") 396 if err != nil { 397 t.Fatal(err) 398 } 399 defer cst.Close() 400 cst.testMissedStorageProofBlocks() 401 } 402 403 // testFileContractRevision creates and revises a file contract on the 404 // blockchain. 405 func (cst *consensusSetTester) testFileContractRevision() { 406 // COMPATv0.4.0 - Step the block height up past the hardfork amount. This 407 // code stops nondeterministic failures when producing storage proofs that 408 // is related to buggy old code. 409 for cst.cs.dbBlockHeight() <= 10 { 410 _, err := cst.miner.AddBlock() 411 if err != nil { 412 panic(err) 413 } 414 } 415 416 // Create a file (as a bytes.Buffer) that will be used for the file 417 // contract. 418 filesize := uint64(4e3) 419 file, err := crypto.RandBytes(int(filesize)) 420 if err != nil { 421 panic(err) 422 } 423 merkleRoot := crypto.MerkleRoot(file) 424 425 // Create a spendable unlock hash for the file contract. 426 sk, pk, err := crypto.GenerateKeyPair() 427 if err != nil { 428 panic(err) 429 } 430 uc := types.UnlockConditions{ 431 PublicKeys: []types.SiaPublicKey{{ 432 Algorithm: types.SignatureEd25519, 433 Key: pk[:], 434 }}, 435 SignaturesRequired: 1, 436 } 437 438 // Create a file contract that will be revised. 439 validProofDest := randAddress() 440 payout := types.NewCurrency64(400e6) 441 fc := types.FileContract{ 442 FileSize: filesize, 443 FileMerkleRoot: crypto.Hash{}, 444 WindowStart: cst.cs.dbBlockHeight() + 2, 445 WindowEnd: cst.cs.dbBlockHeight() + 3, 446 Payout: payout, 447 ValidProofOutputs: []types.SiacoinOutput{{ 448 UnlockHash: validProofDest, 449 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 450 }}, 451 MissedProofOutputs: []types.SiacoinOutput{{ 452 UnlockHash: types.UnlockHash{}, 453 Value: types.PostTax(cst.cs.dbBlockHeight(), payout), 454 }}, 455 UnlockHash: uc.UnlockHash(), 456 } 457 458 // Submit a transaction with the file contract. 459 txnBuilder := cst.wallet.StartTransaction() 460 err = txnBuilder.FundSiacoins(payout) 461 if err != nil { 462 panic(err) 463 } 464 fcIndex := txnBuilder.AddFileContract(fc) 465 txnSet, err := txnBuilder.Sign(true) 466 if err != nil { 467 panic(err) 468 } 469 err = cst.tpool.AcceptTransactionSet(txnSet) 470 if err != nil { 471 panic(err) 472 } 473 _, err = cst.miner.AddBlock() 474 if err != nil { 475 panic(err) 476 } 477 478 // Submit a revision for the file contract. 479 ti := len(txnSet) - 1 480 fcid := txnSet[ti].FileContractID(fcIndex) 481 fcr := types.FileContractRevision{ 482 ParentID: fcid, 483 UnlockConditions: uc, 484 NewRevisionNumber: 69292, 485 486 NewFileSize: filesize, 487 NewFileMerkleRoot: merkleRoot, 488 NewWindowStart: cst.cs.dbBlockHeight() + 1, 489 NewWindowEnd: cst.cs.dbBlockHeight() + 2, 490 NewValidProofOutputs: fc.ValidProofOutputs, 491 NewMissedProofOutputs: fc.MissedProofOutputs, 492 NewUnlockHash: uc.UnlockHash(), 493 } 494 ts := types.TransactionSignature{ 495 ParentID: crypto.Hash(fcid), 496 CoveredFields: types.CoveredFields{WholeTransaction: true}, 497 PublicKeyIndex: 0, 498 } 499 txn := types.Transaction{ 500 FileContractRevisions: []types.FileContractRevision{fcr}, 501 TransactionSignatures: []types.TransactionSignature{ts}, 502 } 503 encodedSig, err := crypto.SignHash(txn.SigHash(0), sk) 504 if err != nil { 505 panic(err) 506 } 507 txn.TransactionSignatures[0].Signature = encodedSig[:] 508 err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn}) 509 if err != nil { 510 panic(err) 511 } 512 _, err = cst.miner.AddBlock() 513 if err != nil { 514 panic(err) 515 } 516 517 // Create and submit a storage proof for the file contract. 518 segmentIndex, err := cst.cs.StorageProofSegment(fcid) 519 if err != nil { 520 panic(err) 521 } 522 segment, hashSet := crypto.MerkleProof(file, segmentIndex) 523 sp := types.StorageProof{ 524 ParentID: fcid, 525 HashSet: hashSet, 526 } 527 copy(sp.Segment[:], segment) 528 txnBuilder = cst.wallet.StartTransaction() 529 txnBuilder.AddStorageProof(sp) 530 txnSet, err = txnBuilder.Sign(true) 531 if err != nil { 532 panic(err) 533 } 534 err = cst.tpool.AcceptTransactionSet(txnSet) 535 if err != nil { 536 panic(err) 537 } 538 _, err = cst.miner.AddBlock() 539 if err != nil { 540 panic(err) 541 } 542 543 // Check that the file contract has been removed. 544 _, err = cst.cs.dbGetFileContract(fcid) 545 if err != errNilItem { 546 panic("file contract should not exist in the database") 547 } 548 } 549 550 // TestIntegrationFileContractRevision creates a consensus set tester and uses 551 // it to call testFileContractRevision. 552 func TestIntegrationFileContractRevision(t *testing.T) { 553 if testing.Short() { 554 t.SkipNow() 555 } 556 cst, err := createConsensusSetTester("TestIntegrationFileContractRevision") 557 if err != nil { 558 t.Fatal(err) 559 } 560 defer cst.Close() 561 cst.testFileContractRevision() 562 } 563 564 // testSpendSiafunds spends siafunds on the blockchain. 565 func (cst *consensusSetTester) testSpendSiafunds() { 566 // Create a random destination address for the output in the transaction. 567 destAddr := randAddress() 568 569 // Create a block containing a transaction with a valid siafund output. 570 txnValue := types.NewCurrency64(3) 571 txnBuilder := cst.wallet.StartTransaction() 572 err := txnBuilder.FundSiafunds(txnValue) 573 if err != nil { 574 panic(err) 575 } 576 outputIndex := txnBuilder.AddSiafundOutput(types.SiafundOutput{Value: txnValue, UnlockHash: destAddr}) 577 txnSet, err := txnBuilder.Sign(true) 578 if err != nil { 579 panic(err) 580 } 581 err = cst.tpool.AcceptTransactionSet(txnSet) 582 if err != nil { 583 panic(err) 584 } 585 586 // Find the siafund inputs used in the txn set. 587 var claimValues []types.Currency 588 var claimIDs []types.SiacoinOutputID 589 for _, txn := range txnSet { 590 for _, sfi := range txn.SiafundInputs { 591 sfo, err := cst.cs.dbGetSiafundOutput(sfi.ParentID) 592 if err != nil { 593 // It's not in the database because it's in an earlier 594 // transaction: disregard it - testing the first layer of 595 // dependencies is sufficient. 596 continue 597 } 598 poolDiff := cst.cs.dbGetSiafundPool().Sub(sfo.ClaimStart) 599 value := poolDiff.Div(types.SiafundCount).Mul(sfo.Value) 600 claimValues = append(claimValues, value) 601 claimIDs = append(claimIDs, sfi.ParentID.SiaClaimOutputID()) 602 } 603 } 604 if len(claimValues) == 0 { 605 panic("no siafund outputs created?") 606 } 607 608 // Mine and apply the block to the consensus set. 609 _, err = cst.miner.AddBlock() 610 if err != nil { 611 panic(err) 612 } 613 614 // See that the destination output was created. 615 outputID := txnSet[len(txnSet)-1].SiafundOutputID(outputIndex) 616 sfo, err := cst.cs.dbGetSiafundOutput(outputID) 617 if err != nil { 618 panic(err) 619 } 620 if sfo.Value.Cmp(txnValue) != 0 { 621 panic("output added with wrong value") 622 } 623 if sfo.UnlockHash != destAddr { 624 panic("output sent to the wrong address") 625 } 626 if sfo.ClaimStart.Cmp(cst.cs.dbGetSiafundPool()) != 0 { 627 panic("ClaimStart is not being set correctly") 628 } 629 630 // Verify that all expected claims were created and added to the set of 631 // delayed siacoin outputs. 632 for i, id := range claimIDs { 633 dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, id) 634 if err != nil { 635 panic(err) 636 } 637 if dsco.Value.Cmp(claimValues[i]) != 0 { 638 panic("expected a different claim value on the siaclaim") 639 } 640 } 641 } 642 643 // TestIntegrationSpendSiafunds creates a consensus set tester and uses it 644 // to call testSpendSiafunds. 645 func (cst *consensusSetTester) TestIntegrationSpendSiafunds(t *testing.T) { 646 if testing.Short() { 647 t.SkipNow() 648 } 649 cst, err := createConsensusSetTester("TestIntegtrationSpendSiafunds") 650 if err != nil { 651 t.Fatal(err) 652 } 653 defer cst.Close() 654 cst.testSpendSiafunds() 655 } 656 657 // testDelayedOutputMaturity adds blocks that result in many delayed outputs 658 // maturing at the same time, verifying that bulk maturity is handled 659 // correctly. 660 661 // TestRegressionDelayedOutputMaturity creates a consensus set tester and uses 662 // it to call testDelayedOutputMaturity. In the past, bolt's ForEach function 663 // had been used incorrectly resulting in the incorrect processing of bulk 664 // delayed outputs. 665 666 // testFileContractMaturity adds blocks that result in many file contracts 667 // being closed at the same time. 668 669 // TestRegressionFileContractMaturity creates a consensus set tester and uses 670 // it to call testFileContractMaturity. In the past, bolt's ForEach function 671 // had been used incorrectly, resulting in the incorrect processing of bulk 672 // file contracts. 673 674 /* 675 // testPaymentChannelBlocks submits blocks to set up, use, and close a payment 676 // channel. 677 func (cst *consensusSetTester) testPaymentChannelBlocks() error { 678 // The current method of doing payment channels is gimped because public 679 // keys do not have timelocks. We will be hardforking to include timelocks 680 // in public keys in 0.4.0, but in the meantime we need an alternate 681 // method. 682 683 // Gimped payment channels: 2-of-2 multisig where one key is controlled by 684 // the funding entity, and one key is controlled by the receiving entity. An 685 // address is created containing both keys, and then the funding entity 686 // creates, but does not sign, a transaction sending coins to the channel 687 // address. A second transaction is created that sends all the coins in the 688 // funding output back to the funding entity. The receiving entity signs the 689 // transaction with a timelocked signature. The funding entity will get the 690 // refund after T blocks as long as the output is not double spent. The 691 // funding entity then signs the first transaction and opens the channel. 692 // 693 // Creating the channel: 694 // 1. Create a 2-of-2 unlock conditions, one key held by each entity. 695 // 2. Funding entity creates, but does not sign, a transaction sending 696 // money to the payment channel address. (txn A) 697 // 3. Funding entity creates and signs a transaction spending the output 698 // created in txn A that sends all the money back as a refund. (txn B) 699 // 4. Receiving entity signs txn B with a timelocked signature, so that the 700 // funding entity cannot get the refund for several days. The funding entity 701 // is given a fully signed and eventually-spendable txn B. 702 // 5. The funding entity signs and broadcasts txn A. 703 // 704 // Using the channel: 705 // Each the receiving entity and the funding entity keeps a record of how 706 // much has been sent down the unclosed channel, and watches the 707 // blockchain for a channel closing transaction. To send more money down 708 // the channel, the funding entity creates and signs a transaction sending 709 // X+y coins to the receiving entity from the channel address. The 710 // transaction is sent to the receiving entity, who will keep it and 711 // potentially sign and broadcast it later. The funding entity will only 712 // send money down the channel if 'work' or some other sort of event has 713 // completed that indicates the receiving entity should get more money. 714 // 715 // Closing the channel: 716 // The receiving entity will sign the transaction that pays them the most 717 // money and then broadcast that transaction. This will spend the output 718 // and close the channel, invalidating txn B and preventing any future 719 // transactions from being made over the channel. The channel must be 720 // closed before the timelock expires on the second signature in txn B, 721 // otherwise the funding entity will be able to get a full refund. 722 // 723 // The funding entity should be waiting until either the receiving entity 724 // closes the channel or the timelock expires. If the receiving entity 725 // closes the channel, all is good. If not, then the funding entity can 726 // close the channel and get a full refund. 727 728 // Create a 2-of-2 unlock conditions, 1 key for each the sender and the 729 // receiver in the payment channel. 730 sk1, pk1, err := crypto.StdKeyGen.Generate() // Funding entity. 731 if err != nil { 732 return err 733 } 734 sk2, pk2, err := crypto.StdKeyGen.Generate() // Receiving entity. 735 if err != nil { 736 return err 737 } 738 uc := types.UnlockConditions{ 739 PublicKeys: []types.SiaPublicKey{ 740 { 741 Algorithm: types.SignatureEd25519, 742 Key: pk1[:], 743 }, 744 { 745 Algorithm: types.SignatureEd25519, 746 Key: pk2[:], 747 }, 748 }, 749 SignaturesRequired: 2, 750 } 751 channelAddress := uc.UnlockHash() 752 753 // Funding entity creates but does not sign a transaction that funds the 754 // channel address. Because the wallet is not very flexible, the channel 755 // txn needs to be fully custom. To get a custom txn, manually create an 756 // address and then use the wallet to fund that address. 757 channelSize := types.NewCurrency64(10e3) 758 channelFundingSK, channelFundingPK, err := crypto.StdKeyGen.Generate() 759 if err != nil { 760 return err 761 } 762 channelFundingUC := types.UnlockConditions{ 763 PublicKeys: []types.SiaPublicKey{{ 764 Algorithm: types.SignatureEd25519, 765 Key: channelFundingPK[:], 766 }}, 767 SignaturesRequired: 1, 768 } 769 channelFundingAddr := channelFundingUC.UnlockHash() 770 fundTxnBuilder := cst.wallet.StartTransaction() 771 if err != nil { 772 return err 773 } 774 err = fundTxnBuilder.FundSiacoins(channelSize) 775 if err != nil { 776 return err 777 } 778 scoFundIndex := fundTxnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: channelSize, UnlockHash: channelFundingAddr}) 779 fundTxnSet, err := fundTxnBuilder.Sign(true) 780 if err != nil { 781 return err 782 } 783 fundOutputID := fundTxnSet[len(fundTxnSet)-1].SiacoinOutputID(int(scoFundIndex)) 784 channelTxn := types.Transaction{ 785 SiacoinInputs: []types.SiacoinInput{{ 786 ParentID: fundOutputID, 787 UnlockConditions: channelFundingUC, 788 }}, 789 SiacoinOutputs: []types.SiacoinOutput{{ 790 Value: channelSize, 791 UnlockHash: channelAddress, 792 }}, 793 TransactionSignatures: []types.TransactionSignature{{ 794 ParentID: crypto.Hash(fundOutputID), 795 PublicKeyIndex: 0, 796 CoveredFields: types.CoveredFields{WholeTransaction: true}, 797 }}, 798 } 799 800 // Funding entity creates and signs a transaction that spends the full 801 // channel output. 802 channelOutputID := channelTxn.SiacoinOutputID(0) 803 refundUC, err := cst.wallet.NextAddress() 804 refundAddr := refundUC.UnlockHash() 805 if err != nil { 806 return err 807 } 808 refundTxn := types.Transaction{ 809 SiacoinInputs: []types.SiacoinInput{{ 810 ParentID: channelOutputID, 811 UnlockConditions: uc, 812 }}, 813 SiacoinOutputs: []types.SiacoinOutput{{ 814 Value: channelSize, 815 UnlockHash: refundAddr, 816 }}, 817 TransactionSignatures: []types.TransactionSignature{{ 818 ParentID: crypto.Hash(channelOutputID), 819 PublicKeyIndex: 0, 820 CoveredFields: types.CoveredFields{WholeTransaction: true}, 821 }}, 822 } 823 sigHash := refundTxn.SigHash(0) 824 cryptoSig1, err := crypto.SignHash(sigHash, sk1) 825 if err != nil { 826 return err 827 } 828 refundTxn.TransactionSignatures[0].Signature = cryptoSig1[:] 829 830 // Receiving entity signs the transaction that spends the full channel 831 // output, but with a timelock. 832 refundTxn.TransactionSignatures = append(refundTxn.TransactionSignatures, types.TransactionSignature{ 833 ParentID: crypto.Hash(channelOutputID), 834 PublicKeyIndex: 1, 835 Timelock: cst.cs.dbBlockHeight() + 2, 836 CoveredFields: types.CoveredFields{WholeTransaction: true}, 837 }) 838 sigHash = refundTxn.SigHash(1) 839 cryptoSig2, err := crypto.SignHash(sigHash, sk2) 840 if err != nil { 841 return err 842 } 843 refundTxn.TransactionSignatures[1].Signature = cryptoSig2[:] 844 845 // Funding entity will now sign and broadcast the funding transaction. 846 sigHash = channelTxn.SigHash(0) 847 cryptoSig0, err := crypto.SignHash(sigHash, channelFundingSK) 848 if err != nil { 849 return err 850 } 851 channelTxn.TransactionSignatures[0].Signature = cryptoSig0[:] 852 err = cst.tpool.AcceptTransactionSet(append(fundTxnSet, channelTxn)) 853 if err != nil { 854 return err 855 } 856 // Put the txn in a block. 857 _, err = cst.miner.AddBlock() 858 if err != nil { 859 return err 860 } 861 862 // Try to submit the refund transaction before the timelock has expired. 863 err = cst.tpool.AcceptTransactionSet([]types.Transaction{refundTxn}) 864 if err != types.ErrPrematureSignature { 865 return err 866 } 867 868 // Create a transaction that has partially used the channel, and submit it 869 // to the blockchain to close the channel. 870 closeTxn := types.Transaction{ 871 SiacoinInputs: []types.SiacoinInput{{ 872 ParentID: channelOutputID, 873 UnlockConditions: uc, 874 }}, 875 SiacoinOutputs: []types.SiacoinOutput{ 876 { 877 Value: channelSize.Sub(types.NewCurrency64(5)), 878 UnlockHash: refundAddr, 879 }, 880 { 881 Value: types.NewCurrency64(5), 882 }, 883 }, 884 TransactionSignatures: []types.TransactionSignature{ 885 { 886 ParentID: crypto.Hash(channelOutputID), 887 PublicKeyIndex: 0, 888 CoveredFields: types.CoveredFields{WholeTransaction: true}, 889 }, 890 { 891 ParentID: crypto.Hash(channelOutputID), 892 PublicKeyIndex: 1, 893 CoveredFields: types.CoveredFields{WholeTransaction: true}, 894 }, 895 }, 896 } 897 sigHash = closeTxn.SigHash(0) 898 cryptoSig3, err := crypto.SignHash(sigHash, sk1) 899 if err != nil { 900 return err 901 } 902 closeTxn.TransactionSignatures[0].Signature = cryptoSig3[:] 903 sigHash = closeTxn.SigHash(1) 904 cryptoSig4, err := crypto.SignHash(sigHash, sk2) 905 if err != nil { 906 return err 907 } 908 closeTxn.TransactionSignatures[1].Signature = cryptoSig4[:] 909 err = cst.tpool.AcceptTransactionSet([]types.Transaction{closeTxn}) 910 if err != nil { 911 return err 912 } 913 914 // Mine the block with the transaction. 915 _, err = cst.miner.AddBlock() 916 if err != nil { 917 return err 918 } 919 closeRefundID := closeTxn.SiacoinOutputID(0) 920 closePaymentID := closeTxn.SiacoinOutputID(1) 921 exists := cst.cs.db.inSiacoinOutputs(closeRefundID) 922 if !exists { 923 return errors.New("close txn refund output doesn't exist") 924 } 925 exists = cst.cs.db.inSiacoinOutputs(closePaymentID) 926 if !exists { 927 return errors.New("close txn payment output doesn't exist") 928 } 929 930 // Create a payment channel where the receiving entity never responds to 931 // the initial transaction. 932 { 933 // Funding entity creates but does not sign a transaction that funds the 934 // channel address. Because the wallet is not very flexible, the channel 935 // txn needs to be fully custom. To get a custom txn, manually create an 936 // address and then use the wallet to fund that address. 937 channelSize := types.NewCurrency64(10e3) 938 channelFundingSK, channelFundingPK, err := crypto.StdKeyGen.Generate() 939 if err != nil { 940 return err 941 } 942 channelFundingUC := types.UnlockConditions{ 943 PublicKeys: []types.SiaPublicKey{{ 944 Algorithm: types.SignatureEd25519, 945 Key: channelFundingPK[:], 946 }}, 947 SignaturesRequired: 1, 948 } 949 channelFundingAddr := channelFundingUC.UnlockHash() 950 fundTxnBuilder := cst.wallet.StartTransaction() 951 err = fundTxnBuilder.FundSiacoins(channelSize) 952 if err != nil { 953 return err 954 } 955 scoFundIndex := fundTxnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: channelSize, UnlockHash: channelFundingAddr}) 956 fundTxnSet, err := fundTxnBuilder.Sign(true) 957 if err != nil { 958 return err 959 } 960 fundOutputID := fundTxnSet[len(fundTxnSet)-1].SiacoinOutputID(int(scoFundIndex)) 961 channelTxn := types.Transaction{ 962 SiacoinInputs: []types.SiacoinInput{{ 963 ParentID: fundOutputID, 964 UnlockConditions: channelFundingUC, 965 }}, 966 SiacoinOutputs: []types.SiacoinOutput{{ 967 Value: channelSize, 968 UnlockHash: channelAddress, 969 }}, 970 TransactionSignatures: []types.TransactionSignature{{ 971 ParentID: crypto.Hash(fundOutputID), 972 PublicKeyIndex: 0, 973 CoveredFields: types.CoveredFields{WholeTransaction: true}, 974 }}, 975 } 976 977 // Funding entity creates and signs a transaction that spends the full 978 // channel output. 979 channelOutputID := channelTxn.SiacoinOutputID(0) 980 refundUC, err := cst.wallet.NextAddress() 981 refundAddr := refundUC.UnlockHash() 982 if err != nil { 983 return err 984 } 985 refundTxn := types.Transaction{ 986 SiacoinInputs: []types.SiacoinInput{{ 987 ParentID: channelOutputID, 988 UnlockConditions: uc, 989 }}, 990 SiacoinOutputs: []types.SiacoinOutput{{ 991 Value: channelSize, 992 UnlockHash: refundAddr, 993 }}, 994 TransactionSignatures: []types.TransactionSignature{{ 995 ParentID: crypto.Hash(channelOutputID), 996 PublicKeyIndex: 0, 997 CoveredFields: types.CoveredFields{WholeTransaction: true}, 998 }}, 999 } 1000 sigHash := refundTxn.SigHash(0) 1001 cryptoSig1, err := crypto.SignHash(sigHash, sk1) 1002 if err != nil { 1003 return err 1004 } 1005 refundTxn.TransactionSignatures[0].Signature = cryptoSig1[:] 1006 1007 // Receiving entity never communitcates, funding entity must reclaim 1008 // the 'channelSize' coins that were intended to go to the channel. 1009 reclaimUC, err := cst.wallet.NextAddress() 1010 reclaimAddr := reclaimUC.UnlockHash() 1011 if err != nil { 1012 return err 1013 } 1014 reclaimTxn := types.Transaction{ 1015 SiacoinInputs: []types.SiacoinInput{{ 1016 ParentID: fundOutputID, 1017 UnlockConditions: channelFundingUC, 1018 }}, 1019 SiacoinOutputs: []types.SiacoinOutput{{ 1020 Value: channelSize, 1021 UnlockHash: reclaimAddr, 1022 }}, 1023 TransactionSignatures: []types.TransactionSignature{{ 1024 ParentID: crypto.Hash(fundOutputID), 1025 PublicKeyIndex: 0, 1026 CoveredFields: types.CoveredFields{WholeTransaction: true}, 1027 }}, 1028 } 1029 sigHash = reclaimTxn.SigHash(0) 1030 cryptoSig, err := crypto.SignHash(sigHash, channelFundingSK) 1031 if err != nil { 1032 return err 1033 } 1034 reclaimTxn.TransactionSignatures[0].Signature = cryptoSig[:] 1035 err = cst.tpool.AcceptTransactionSet(append(fundTxnSet, reclaimTxn)) 1036 if err != nil { 1037 return err 1038 } 1039 block, _ := cst.miner.FindBlock() 1040 err = cst.cs.AcceptBlock(block) 1041 if err != nil { 1042 return err 1043 } 1044 reclaimOutputID := reclaimTxn.SiacoinOutputID(0) 1045 exists := cst.cs.db.inSiacoinOutputs(reclaimOutputID) 1046 if !exists { 1047 return errors.New("failed to reclaim an output that belongs to the funding entity") 1048 } 1049 } 1050 1051 // Create a channel and the open the channel, but close the channel using 1052 // the timelocked signature. 1053 { 1054 // Funding entity creates but does not sign a transaction that funds the 1055 // channel address. Because the wallet is not very flexible, the channel 1056 // txn needs to be fully custom. To get a custom txn, manually create an 1057 // address and then use the wallet to fund that address. 1058 channelSize := types.NewCurrency64(10e3) 1059 channelFundingSK, channelFundingPK, err := crypto.StdKeyGen.Generate() 1060 if err != nil { 1061 return err 1062 } 1063 channelFundingUC := types.UnlockConditions{ 1064 PublicKeys: []types.SiaPublicKey{{ 1065 Algorithm: types.SignatureEd25519, 1066 Key: channelFundingPK[:], 1067 }}, 1068 SignaturesRequired: 1, 1069 } 1070 channelFundingAddr := channelFundingUC.UnlockHash() 1071 fundTxnBuilder := cst.wallet.StartTransaction() 1072 err = fundTxnBuilder.FundSiacoins(channelSize) 1073 if err != nil { 1074 return err 1075 } 1076 scoFundIndex := fundTxnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: channelSize, UnlockHash: channelFundingAddr}) 1077 fundTxnSet, err := fundTxnBuilder.Sign(true) 1078 if err != nil { 1079 return err 1080 } 1081 fundOutputID := fundTxnSet[len(fundTxnSet)-1].SiacoinOutputID(int(scoFundIndex)) 1082 channelTxn := types.Transaction{ 1083 SiacoinInputs: []types.SiacoinInput{{ 1084 ParentID: fundOutputID, 1085 UnlockConditions: channelFundingUC, 1086 }}, 1087 SiacoinOutputs: []types.SiacoinOutput{{ 1088 Value: channelSize, 1089 UnlockHash: channelAddress, 1090 }}, 1091 TransactionSignatures: []types.TransactionSignature{{ 1092 ParentID: crypto.Hash(fundOutputID), 1093 PublicKeyIndex: 0, 1094 CoveredFields: types.CoveredFields{WholeTransaction: true}, 1095 }}, 1096 } 1097 1098 // Funding entity creates and signs a transaction that spends the full 1099 // channel output. 1100 channelOutputID := channelTxn.SiacoinOutputID(0) 1101 refundUC, err := cst.wallet.NextAddress() 1102 refundAddr := refundUC.UnlockHash() 1103 if err != nil { 1104 return err 1105 } 1106 refundTxn := types.Transaction{ 1107 SiacoinInputs: []types.SiacoinInput{{ 1108 ParentID: channelOutputID, 1109 UnlockConditions: uc, 1110 }}, 1111 SiacoinOutputs: []types.SiacoinOutput{{ 1112 Value: channelSize, 1113 UnlockHash: refundAddr, 1114 }}, 1115 TransactionSignatures: []types.TransactionSignature{{ 1116 ParentID: crypto.Hash(channelOutputID), 1117 PublicKeyIndex: 0, 1118 CoveredFields: types.CoveredFields{WholeTransaction: true}, 1119 }}, 1120 } 1121 sigHash := refundTxn.SigHash(0) 1122 cryptoSig1, err := crypto.SignHash(sigHash, sk1) 1123 if err != nil { 1124 return err 1125 } 1126 refundTxn.TransactionSignatures[0].Signature = cryptoSig1[:] 1127 1128 // Receiving entity signs the transaction that spends the full channel 1129 // output, but with a timelock. 1130 refundTxn.TransactionSignatures = append(refundTxn.TransactionSignatures, types.TransactionSignature{ 1131 ParentID: crypto.Hash(channelOutputID), 1132 PublicKeyIndex: 1, 1133 Timelock: cst.cs.dbBlockHeight() + 2, 1134 CoveredFields: types.CoveredFields{WholeTransaction: true}, 1135 }) 1136 sigHash = refundTxn.SigHash(1) 1137 cryptoSig2, err := crypto.SignHash(sigHash, sk2) 1138 if err != nil { 1139 return err 1140 } 1141 refundTxn.TransactionSignatures[1].Signature = cryptoSig2[:] 1142 1143 // Funding entity will now sign and broadcast the funding transaction. 1144 sigHash = channelTxn.SigHash(0) 1145 cryptoSig0, err := crypto.SignHash(sigHash, channelFundingSK) 1146 if err != nil { 1147 return err 1148 } 1149 channelTxn.TransactionSignatures[0].Signature = cryptoSig0[:] 1150 err = cst.tpool.AcceptTransactionSet(append(fundTxnSet, channelTxn)) 1151 if err != nil { 1152 return err 1153 } 1154 // Put the txn in a block. 1155 block, _ := cst.miner.FindBlock() 1156 err = cst.cs.AcceptBlock(block) 1157 if err != nil { 1158 return err 1159 } 1160 1161 // Receiving entity never signs another transaction, so the funding 1162 // entity waits until the timelock is complete, and then submits the 1163 // refundTxn. 1164 for i := 0; i < 3; i++ { 1165 block, _ := cst.miner.FindBlock() 1166 err = cst.cs.AcceptBlock(block) 1167 if err != nil { 1168 return err 1169 } 1170 } 1171 err = cst.tpool.AcceptTransactionSet([]types.Transaction{refundTxn}) 1172 if err != nil { 1173 return err 1174 } 1175 block, _ = cst.miner.FindBlock() 1176 err = cst.cs.AcceptBlock(block) 1177 if err != nil { 1178 return err 1179 } 1180 refundOutputID := refundTxn.SiacoinOutputID(0) 1181 exists := cst.cs.db.inSiacoinOutputs(refundOutputID) 1182 if !exists { 1183 return errors.New("timelocked refund transaction did not get spent correctly") 1184 } 1185 } 1186 1187 return nil 1188 } 1189 */ 1190 1191 /* 1192 // TestPaymentChannelBlocks creates a consensus set tester and uses it to call 1193 // testPaymentChannelBlocks. 1194 func TestPaymentChannelBlocks(t *testing.T) { 1195 if testing.Short() { 1196 t.SkipNow() 1197 } 1198 cst, err := createConsensusSetTester("TestPaymentChannelBlocks") 1199 if err != nil { 1200 t.Fatal(err) 1201 } 1202 defer cst.closeCst() 1203 err = cst.testPaymentChannelBlocks() 1204 if err != nil { 1205 t.Fatal(err) 1206 } 1207 } 1208 */