// threadedCreateStorageProof creates a storage proof for a file contract // obligation and submits it to the blockchain. func (h *Host) threadedCreateStorageProof(obligation contractObligation, heightForProof types.BlockHeight) { defer h.threadedDeleteObligation(obligation) fullpath := filepath.Join(h.saveDir, obligation.Path) file, err := os.Open(fullpath) if err != nil { fmt.Println(err) return } defer file.Close() segmentIndex, err := h.cs.StorageProofSegment(obligation.ID) if err != nil { fmt.Println(err) return } base, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { fmt.Println(err) return } sp := types.StorageProof{obligation.ID, base, hashSet} // Create and send the transaction. id, err := h.wallet.RegisterTransaction(types.Transaction{}) if err != nil { fmt.Println(err) return } _, _, err = h.wallet.AddStorageProof(id, sp) if err != nil { fmt.Println(err) return } t, err := h.wallet.SignTransaction(id, true) if err != nil { fmt.Println(err) return } err = h.tpool.AcceptTransaction(t) if err != nil { fmt.Println(err) return } }
// threadedCreateStorageProof creates a storage proof for a file contract // obligation and submits it to the blockchain. Though a lock is never held, a // significant amount of disk I/O happens, meaning this function should be // called in a separate goroutine. func (h *Host) threadedCreateStorageProof(obligation *contractObligation) { h.resourceLock.RLock() defer h.resourceLock.RUnlock() if build.DEBUG && h.closed { panic("the close order should guarantee that threadedCreateStorageProof has access to host resources - yet host is closed!") } file, err := os.Open(obligation.Path) if err != nil { h.log.Printf("ERROR: could not open obligation %v (%v) for storage proof: %v", obligation.ID, obligation.Path, err) return } defer file.Close() segmentIndex, err := h.cs.StorageProofSegment(obligation.ID) if err != nil { h.log.Printf("ERROR: could not determine storage proof index for %v (%v): %v", obligation.ID, obligation.Path, err) return } base, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { h.log.Printf("ERROR: could not construct storage proof for %v (%v): %v", obligation.ID, obligation.Path, err) return } sp := types.StorageProof{ ParentID: obligation.ID, HashSet: hashSet, } copy(sp.Segment[:], base) // Create and send the transaction. txnBuilder := h.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err := txnBuilder.Sign(true) if err != nil { h.log.Println("couldn't sign storage proof transaction:", err) return } err = h.tpool.AcceptTransactionSet(txnSet) if err != nil { h.log.Printf("ERROR: could not submit storage proof txn for %v (%v): %v", obligation.ID, obligation.Path, err) return } }
// threadedCreateStorageProof creates a storage proof for a file contract // obligation and submits it to the blockchain. // // TODO: The printlns here should be logging messages. func (h *Host) threadedCreateStorageProof(obligation contractObligation) { defer h.threadedDeleteObligation(obligation) file, err := os.Open(obligation.Path) if err != nil { h.log.Printf("ERROR: could not open obligation %v (%v) for storage proof: %v", obligation.ID, obligation.Path, err) return } defer file.Close() segmentIndex, err := h.cs.StorageProofSegment(obligation.ID) if err != nil { h.log.Printf("ERROR: could not determine storage proof index for %v (%v): %v", obligation.ID, obligation.Path, err) return } base, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { h.log.Printf("ERROR: could not construct storage proof for %v (%v): %v", obligation.ID, obligation.Path, err) return } sp := types.StorageProof{obligation.ID, [crypto.SegmentSize]byte{}, hashSet} copy(sp.Segment[:], base) // Create and send the transaction. txnBuilder := h.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err := txnBuilder.Sign(true) if err != nil { h.log.Println("couldn't sign storage proof transaction:", err) return } err = h.tpool.AcceptTransactionSet(txnSet) if err != nil { h.log.Printf("ERROR: could not submit storage proof txn for %v (%v): %v", obligation.ID, obligation.Path, err) return } // Storage proof was successful, so increment profit tracking h.mu.Lock() h.profit = h.profit.Add(obligation.FileContract.Payout) h.mu.Unlock() }
// threadedCreateStorageProof creates a storage proof for a file contract // obligation and submits it to the blockchain. // // TODO: The printlns here should be logging messages. func (h *Host) threadedCreateStorageProof(obligation contractObligation) { defer h.threadedDeleteObligation(obligation) file, err := os.Open(obligation.Path) if err != nil { fmt.Println(err) return } defer file.Close() segmentIndex, err := h.cs.StorageProofSegment(obligation.ID) if err != nil { fmt.Println(err) return } base, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { fmt.Println(err) return } sp := types.StorageProof{obligation.ID, [crypto.SegmentSize]byte{}, hashSet} copy(sp.Segment[:], base) // Create and send the transaction. txnBuilder := h.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err := txnBuilder.Sign(true) if err != nil { fmt.Println(err) return } err = h.tpool.AcceptTransactionSet(txnSet) if err != nil { fmt.Println(err) return } // Storage proof was successful, so increment profit tracking lockID := h.mu.Lock() h.profit = h.profit.Add(obligation.FileContract.Payout) h.mu.Unlock(lockID) }
// threadedCreateStorageProof creates a storage proof for a file contract // obligation and submits it to the blockchain. // // TODO: The printlns here should be logging messages. func (h *Host) threadedCreateStorageProof(obligation contractObligation, heightForProof types.BlockHeight) { defer h.threadedDeleteObligation(obligation) fullpath := filepath.Join(h.saveDir, obligation.Path) file, err := os.Open(fullpath) if err != nil { fmt.Println(err) return } defer file.Close() segmentIndex, err := h.cs.StorageProofSegment(obligation.ID) if err != nil { fmt.Println(err) return } base, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { fmt.Println(err) return } sp := types.StorageProof{obligation.ID, [crypto.SegmentSize]byte{}, hashSet} copy(sp.Segment[:], base) // Create and send the transaction. txnBuilder := h.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err := txnBuilder.Sign(true) if err != nil { fmt.Println(err) return } err = h.tpool.AcceptTransactionSet(txnSet) if err != nil { fmt.Println(err) return } }
// testFileContractRevision creates and revises a file contract on the // blockchain. func (cst *consensusSetTester) testFileContractRevision() { // COMPATv0.4.0 - Step the block height up past the hardfork amount. This // code stops nondeterministic failures when producing storage proofs that // is related to buggy old code. for cst.cs.dbBlockHeight() <= 10 { _, err := cst.miner.AddBlock() if err != nil { panic(err) } } // Create a file (as a bytes.Buffer) that will be used for the file // contract. filesize := uint64(4e3) file := randFile(filesize) merkleRoot, err := crypto.ReaderMerkleRoot(file) if err != nil { panic(err) } file.Seek(0, 0) // Create a spendable unlock hash for the file contract. sk, pk, err := crypto.GenerateKeyPair() if err != nil { panic(err) } uc := types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{{ Algorithm: types.SignatureEd25519, Key: pk[:], }}, SignaturesRequired: 1, } // Create a file contract that will be revised. validProofDest := randAddress() payout := types.NewCurrency64(400e6) fc := types.FileContract{ FileSize: filesize, FileMerkleRoot: crypto.Hash{}, WindowStart: cst.cs.dbBlockHeight() + 2, WindowEnd: cst.cs.dbBlockHeight() + 3, Payout: payout, ValidProofOutputs: []types.SiacoinOutput{{ UnlockHash: validProofDest, Value: types.PostTax(cst.cs.dbBlockHeight(), payout), }}, MissedProofOutputs: []types.SiacoinOutput{{ UnlockHash: types.UnlockHash{}, Value: types.PostTax(cst.cs.dbBlockHeight(), payout), }}, UnlockHash: uc.UnlockHash(), } // Submit a transaction with the file contract. txnBuilder := cst.wallet.StartTransaction() err = txnBuilder.FundSiacoins(payout) if err != nil { panic(err) } fcIndex := txnBuilder.AddFileContract(fc) txnSet, err := txnBuilder.Sign(true) if err != nil { panic(err) } err = cst.tpool.AcceptTransactionSet(txnSet) if err != nil { panic(err) } _, err = cst.miner.AddBlock() if err != nil { panic(err) } // Submit a revision for the file contract. ti := len(txnSet) - 1 fcid := txnSet[ti].FileContractID(fcIndex) fcr := types.FileContractRevision{ ParentID: fcid, UnlockConditions: uc, NewRevisionNumber: 69292, NewFileSize: filesize, NewFileMerkleRoot: merkleRoot, NewWindowStart: cst.cs.dbBlockHeight() + 1, NewWindowEnd: cst.cs.dbBlockHeight() + 2, NewValidProofOutputs: fc.ValidProofOutputs, NewMissedProofOutputs: fc.MissedProofOutputs, NewUnlockHash: uc.UnlockHash(), } ts := types.TransactionSignature{ ParentID: crypto.Hash(fcid), CoveredFields: types.CoveredFields{WholeTransaction: true}, PublicKeyIndex: 0, } txn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{fcr}, TransactionSignatures: []types.TransactionSignature{ts}, } encodedSig, err := crypto.SignHash(txn.SigHash(0), sk) if err != nil { panic(err) } txn.TransactionSignatures[0].Signature = encodedSig[:] err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn}) if err != nil { panic(err) } _, err = cst.miner.AddBlock() if err != nil { panic(err) } // Create and submit a storage proof for the file contract. segmentIndex, err := cst.cs.StorageProofSegment(fcid) if err != nil { panic(err) } segment, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { panic(err) } sp := types.StorageProof{ ParentID: fcid, HashSet: hashSet, } copy(sp.Segment[:], segment) txnBuilder = cst.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err = txnBuilder.Sign(true) if err != nil { panic(err) } err = cst.tpool.AcceptTransactionSet(txnSet) if err != nil { panic(err) } _, err = cst.miner.AddBlock() if err != nil { panic(err) } // Check that the file contract has been removed. _, err = cst.cs.dbGetFileContract(fcid) if err != errNilItem { panic("file contract should not exist in the database") } }
// testValidStorageProofBlocks adds a block with a file contract, and then // submits a storage proof for that file contract. func (cst *consensusSetTester) testValidStorageProofBlocks() { // COMPATv0.4.0 - Step the block height up past the hardfork amount. This // code stops nondeterministic failures when producing storage proofs that // is related to buggy old code. for cst.cs.dbBlockHeight() <= 10 { _, err := cst.miner.AddBlock() if err != nil { panic(err) } } // Create a file (as a bytes.Buffer) that will be used for the file // contract. filesize := uint64(4e3) file := randFile(filesize) merkleRoot, err := crypto.ReaderMerkleRoot(file) if err != nil { panic(err) } file.Seek(0, 0) // Create a file contract that will be successful. validProofDest := randAddress() payout := types.NewCurrency64(400e6) fc := types.FileContract{ FileSize: filesize, FileMerkleRoot: merkleRoot, WindowStart: cst.cs.dbBlockHeight() + 1, WindowEnd: cst.cs.dbBlockHeight() + 2, Payout: payout, ValidProofOutputs: []types.SiacoinOutput{{ UnlockHash: validProofDest, Value: types.PostTax(cst.cs.dbBlockHeight(), payout), }}, MissedProofOutputs: []types.SiacoinOutput{{ UnlockHash: types.UnlockHash{}, Value: types.PostTax(cst.cs.dbBlockHeight(), payout), }}, } // Submit a transaction with the file contract. oldSiafundPool := cst.cs.dbGetSiafundPool() txnBuilder := cst.wallet.StartTransaction() err = txnBuilder.FundSiacoins(payout) if err != nil { panic(err) } fcIndex := txnBuilder.AddFileContract(fc) txnSet, err := txnBuilder.Sign(true) if err != nil { panic(err) } err = cst.tpool.AcceptTransactionSet(txnSet) if err != nil { panic(err) } _, err = cst.miner.AddBlock() if err != nil { panic(err) } // Check that the siafund pool was increased by the tax on the payout. siafundPool := cst.cs.dbGetSiafundPool() if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 { panic("siafund pool was not increased correctly") } // Check that the file contract made it into the database. ti := len(txnSet) - 1 fcid := txnSet[ti].FileContractID(fcIndex) _, err = cst.cs.dbGetFileContract(fcid) if err != nil { panic(err) } // Create and submit a storage proof for the file contract. segmentIndex, err := cst.cs.StorageProofSegment(fcid) if err != nil { panic(err) } segment, hashSet, err := crypto.BuildReaderProof(file, segmentIndex) if err != nil { panic(err) } sp := types.StorageProof{ ParentID: fcid, HashSet: hashSet, } copy(sp.Segment[:], segment) txnBuilder = cst.wallet.StartTransaction() txnBuilder.AddStorageProof(sp) txnSet, err = txnBuilder.Sign(true) if err != nil { panic(err) } err = cst.tpool.AcceptTransactionSet(txnSet) if err != nil { panic(err) } _, err = cst.miner.AddBlock() if err != nil { panic(err) } // Check that the file contract has been removed. _, err = cst.cs.dbGetFileContract(fcid) if err != errNilItem { panic("file contract should not exist in the database") } // Check that the siafund pool has not changed. postProofPool := cst.cs.dbGetSiafundPool() if postProofPool.Cmp(siafundPool) != 0 { panic("siafund pool should not change after submitting a storage proof") } // Check that a delayed output was created for the valid proof. spoid := fcid.StorageProofOutputID(types.ProofValid, 0) dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid) if err != nil { panic(err) } if dsco.UnlockHash != fc.ValidProofOutputs[0].UnlockHash { panic("wrong unlock hash in dsco") } if dsco.Value.Cmp(fc.ValidProofOutputs[0].Value) != 0 { panic("wrong sco value in dsco") } }
// TestValidStorageProofs probes the validStorageProofs method of the consensus // set. func TestValidStorageProofs(t *testing.T) { if testing.Short() { t.SkipNow() } cst, err := createConsensusSetTester("TestValidStorageProofs") if err != nil { t.Fatal(err) } // Create a file contract for which a storage proof can be created. var fcid types.FileContractID fcid[0] = 12 simFile := make([]byte, 64*1024) rand.Read(simFile) buffer := bytes.NewReader(simFile) root, err := crypto.ReaderMerkleRoot(buffer) if err != nil { t.Fatal(err) } fc := types.FileContract{ FileSize: 64 * 1024, FileMerkleRoot: root, WindowStart: 2, WindowEnd: 1200, } cst.cs.fileContracts[fcid] = fc buffer.Seek(0, 0) // Create a transaction with a storage proof. proofIndex, err := cst.cs.storageProofSegment(fcid) if err != nil { t.Fatal(err) } base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex) if err != nil { t.Fatal(err) } txn := types.Transaction{ StorageProofs: []types.StorageProof{ { ParentID: fcid, Segment: base, HashSet: proofSet, }, }, } err = cst.cs.validStorageProofs(txn) if err != nil { t.Error(err) } // Corrupt the proof set. proofSet[0][0]++ txn = types.Transaction{ StorageProofs: []types.StorageProof{ { ParentID: fcid, Segment: base, HashSet: proofSet, }, }, } err = cst.cs.validStorageProofs(txn) if err != ErrInvalidStorageProof { t.Error(err) } }