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