// TestUploadAndDownload creates a network with a host and then uploads a file // from the renter to the host, and then downloads it. func TestUploadAndDownload(t *testing.T) { t.Skip("broken -- fix when host/renter are revamped") // Create a server and add a host to the network. st := newServerTester("TestUploadAndDownload", t) err := st.announceHost() if err != nil { t.Fatal(err) } for len(st.server.hostdb.ActiveHosts()) == 0 { time.Sleep(time.Millisecond) } // Upload to the host. uploadName := "api.go" st.callAPI("/renter/files/upload?pieces=1&nickname=api.go&source=" + uploadName) // Wait for the upload to finish - this is necessary due to the // fact that zero-conf transactions aren't actually propagated properly. // // TODO: There should be some way to just spinblock until the download // completes. Except there's no exported function in the renter that will // indicate if a download has completed or not. time.Sleep(types.RenterZeroConfDelay + time.Second*10) files := st.server.renter.FileList() if len(files) != 1 || !files[0].Available() { t.Fatal("file is not uploaded") } // Try to download the file. downloadName := build.TempDir("api", "TestUploadAndDownload", "downloadTestData") st.callAPI("/renter/files/download?nickname=api.go&destination=" + downloadName) time.Sleep(time.Second * 2) // Check that the downloaded file is equal to the uploaded file. upFile, err := os.Open(uploadName) if err != nil { t.Fatal(err) } defer upFile.Close() downFile, err := os.Open(downloadName) if err != nil { t.Fatal(err) } defer upFile.Close() upRoot, err := crypto.ReaderMerkleRoot(upFile) if err != nil { t.Fatal(err) } downRoot, err := crypto.ReaderMerkleRoot(downFile) if err != nil { t.Fatal(err) } if upRoot != downRoot { t.Error("uploaded and downloaded file have a hash mismatch") } }
// downloadPiece attempts to retrieve a file piece from a host. func (d *Download) downloadPiece(piece filePiece) error { conn, err := net.DialTimeout("tcp", string(piece.HostIP), 10e9) if err != nil { return err } defer conn.Close() err = encoding.WriteObject(conn, [8]byte{'R', 'e', 't', 'r', 'i', 'e', 'v', 'e'}) if err != nil { return err } // Send the ID of the contract for the file piece we're requesting. if err := encoding.WriteObject(conn, piece.ContractID); err != nil { return err } // Simultaneously download, decrypt, and calculate the Merkle root of the file. tee := io.TeeReader( // Use a LimitedReader to ensure we don't read indefinitely. io.LimitReader(conn, int64(piece.Contract.FileSize)), // Write the decrypted bytes to the file. piece.EncryptionKey.NewWriter(d), ) merkleRoot, err := crypto.ReaderMerkleRoot(tee) if err != nil { return err } if merkleRoot != piece.Contract.FileMerkleRoot { return errors.New("host provided a file that's invalid") } return nil }
func TestStorageProof(t *testing.T) { if testing.Short() { t.SkipNow() } ht := CreateHostTester("TestStorageProof", t) // create a file contract fc := types.FileContract{ WindowStart: types.MaturityDelay + 3, WindowEnd: 1000, Payout: types.NewCurrency64(1), UnlockHash: types.UnlockConditions{}.UnlockHash(), ValidProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(1)}}, MissedProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(1)}}, } txnBuilder := ht.wallet.StartTransaction() err := txnBuilder.FundSiacoins(fc.Payout) if err != nil { t.Fatal(err) } txnBuilder.AddFileContract(fc) signedTxnSet, err := txnBuilder.Sign(true) if err != nil { t.Fatal(err) } fcid := signedTxnSet[len(signedTxnSet)-1].FileContractID(0) // generate data const dataSize = 777 data := make([]byte, dataSize) rand.Read(data) root, err := crypto.ReaderMerkleRoot(bytes.NewReader(data)) if err != nil { t.Fatal(err) } ioutil.WriteFile(filepath.Join(ht.host.persistDir, "foo"), data, 0777) // create revision rev := types.FileContractRevision{ ParentID: fcid, UnlockConditions: types.UnlockConditions{}, NewFileSize: dataSize, NewWindowStart: fc.WindowStart, NewFileMerkleRoot: root, NewWindowEnd: fc.WindowEnd, NewValidProofOutputs: fc.ValidProofOutputs, NewMissedProofOutputs: fc.MissedProofOutputs, NewRevisionNumber: 1, } revTxn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{rev}, } // create obligation obligation := &contractObligation{ ID: fcid, FileContract: fc, Path: filepath.Join(ht.host.persistDir, "foo"), } ht.host.obligationsByID[fcid] = obligation ht.host.obligationsByHeight[fc.WindowStart+1] = []*contractObligation{obligation} // submit both to tpool err = ht.tpool.AcceptTransactionSet(append(signedTxnSet, revTxn)) if err != nil { t.Fatal(err) } _, err = ht.miner.AddBlock() if err != nil { t.Fatal(err) } // storage proof will be submitted after mining one more block _, err = ht.miner.AddBlock() if err != nil { t.Fatal(err) } }
// 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") } }
// uploadFile uploads a file to the host from the tester's renter. The data // used to make the file is returned. The nickname of the file in the renter is // the same as the name provided as input. func (ht *hostTester) uploadFile(path string, renew bool) ([]byte, error) { // Check that renting is initialized properly. err := ht.initRenting() if err != nil { return nil, err } // Create a file to upload to the host. source := filepath.Join(ht.persistDir, path+".testfile") datasize := uint64(1024) data, err := crypto.RandBytes(int(datasize)) if err != nil { return nil, err } dataMerkleRoot, err := crypto.ReaderMerkleRoot(bytes.NewReader(data)) if err != nil { return nil, err } err = ioutil.WriteFile(source, data, 0600) if err != nil { return nil, err } // Have the renter upload to the host. rsc, err := renter.NewRSCode(1, 1) if err != nil { return nil, err } fup := modules.FileUploadParams{ Source: source, SiaPath: path, Duration: testUploadDuration, Renew: renew, ErasureCode: rsc, PieceSize: 0, } err = ht.renter.Upload(fup) if err != nil { return nil, err } // Wait until the upload has finished. for i := 0; i < 100; i++ { time.Sleep(time.Millisecond * 100) // Asynchronous processes in the host access obligations by id, // therefore a lock is required to scan the set of obligations. if func() bool { ht.host.mu.Lock() defer ht.host.mu.Unlock() for _, ob := range ht.host.obligationsByID { if dataMerkleRoot == ob.merkleRoot() { return true } } return false }() { break } } // Block until the renter is at 50 upload progress - it takes time for the // contract to confirm renter-side. complete := false for i := 0; i < 50 && !complete; i++ { fileInfos := ht.renter.FileList() for _, fileInfo := range fileInfos { if fileInfo.UploadProgress >= 50 { complete = true } } if complete { break } time.Sleep(time.Millisecond * 50) } if !complete { return nil, errors.New("renter never recognized that the upload completed") } // The rest of the upload can be performed under lock. ht.host.mu.Lock() defer ht.host.mu.Unlock() if len(ht.host.obligationsByID) != 1 { return nil, errors.New("expecting a single obligation") } for _, ob := range ht.host.obligationsByID { if ob.fileSize() >= datasize { return data, nil } } return nil, errors.New("ht.uploadFile: upload failed") }
// rpcContract is an RPC that negotiates a file contract. If the // negotiation is successful, the file is downloaded and the host begins // submitting proofs of storage. func (h *Host) rpcContract(conn net.Conn) (err error) { // Read the contract terms. var terms modules.ContractTerms err = encoding.ReadObject(conn, &terms, maxContractLen) if err != nil { return } // Consider the contract terms. If they are unacceptable, return an error // describing why. lockID := h.mu.RLock() err = h.considerTerms(terms) h.mu.RUnlock(lockID) if err != nil { err = encoding.WriteObject(conn, err.Error()) return } // terms are acceptable; allocate space for file lockID = h.mu.Lock() file, path, err := h.allocate(terms.FileSize) h.mu.Unlock(lockID) if err != nil { return } defer file.Close() // rollback everything if something goes wrong defer func() { lockID := h.mu.Lock() defer h.mu.Unlock(lockID) if err != nil { h.deallocate(terms.FileSize, path) } }() // signal that we are ready to download file err = encoding.WriteObject(conn, modules.AcceptTermsResponse) if err != nil { return } // simultaneously download file and calculate its Merkle root. tee := io.TeeReader( // use a LimitedReader to ensure we don't read indefinitely io.LimitReader(conn, int64(terms.FileSize)), // each byte we read from tee will also be written to file file, ) merkleRoot, err := crypto.ReaderMerkleRoot(tee) if err != nil { return } // Data has been sent, read in the unsigned transaction with the file // contract. var unsignedTxn types.Transaction err = encoding.ReadObject(conn, &unsignedTxn, maxContractLen) if err != nil { return } // Verify that the transaction matches the agreed upon terms, and that the // Merkle root in the file contract matches our independently calculated // Merkle root. err = verifyTransaction(unsignedTxn, terms, merkleRoot) if err != nil { err = errors.New("transaction does not satisfy terms: " + err.Error()) return } // Add the collateral to the transaction, but do not sign the transaction. collateralTxn, txnBuilder, err := h.addCollateral(unsignedTxn, terms) if err != nil { return } err = encoding.WriteObject(conn, collateralTxn) if err != nil { return } // Read in the renter-signed transaction and check that it matches the // previously accepted transaction. var signedTxn types.Transaction err = encoding.ReadObject(conn, &signedTxn, maxContractLen) if err != nil { return } if collateralTxn.ID() != signedTxn.ID() { err = errors.New("signed transaction does not match the transaction with collateral") return } // Add the signatures from the renter signed transaction, and then sign the // transaction, then submit the transaction. for _, sig := range signedTxn.TransactionSignatures { txnBuilder.AddTransactionSignature(sig) if err != nil { return } } txnSet, err := txnBuilder.Sign(true) if err != nil { return } err = h.tpool.AcceptTransactionSet(txnSet) if err != nil { return } // Add this contract to the host's list of obligations. fcid := signedTxn.FileContractID(0) fc := signedTxn.FileContracts[0] proofHeight := fc.WindowStart + StorageProofReorgDepth co := contractObligation{ ID: fcid, FileContract: fc, Path: path, } lockID = h.mu.Lock() h.obligationsByHeight[proofHeight] = append(h.obligationsByHeight[proofHeight], co) h.obligationsByID[fcid] = co h.save() h.mu.Unlock(lockID) // Send an ack to the renter that all is well. err = encoding.WriteObject(conn, true) if err != nil { return } // TODO: we don't currently watch the blockchain to make sure that the // transaction actually gets into the blockchain. return }
// compatibilityLoad tries to load the file as a compatible version. func (h *Host) compatibilityLoad() error { // Try loading the file as a 0.4 file. c04h := new(compat04Host) err := persist.LoadFile(compat04Metadata, c04h, filepath.Join(h.persistDir, "settings.json")) if err != nil { // 0.4.x is the only backwards compatibility provided. File could not // be loaded. return err } // Copy over host identity. h.publicKey = c04h.PublicKey h.secretKey = c04h.SecretKey // Copy file management, including providing compatibility for old // obligations. h.fileCounter = int64(c04h.FileCounter) h.spaceRemaining = c04h.HostSettings.TotalStorage upgradedObligations := h.loadCompat04Obligations(c04h.Obligations) // Copy over statistics. h.revenue = c04h.Profit // Copy over utilities. h.settings = c04h.HostSettings // AcceptingContracts should be true by default h.settings.AcceptingContracts = true // Subscribe to the consensus set. if build.DEBUG && h.recentChange != (modules.ConsensusChangeID{}) { panic("compatibility loading is not starting from blank consensus?") } // initConsensusSubscription will scan through the consensus set and set // 'OriginConfirmed' and 'RevisionConfirmed' when the accociated file // contract and file contract revisions are found. err = h.initConsensusSubscription() if err != nil { return err } // Remove all obligations that have not had their origin transactions // confirmed on the blockchain, and add action items for the rest. for _, uo := range upgradedObligations { if !uo.OriginConfirmed { // Because there is no transaction on the blockchain, and because // the old host did not keep the transaction, it's highly unlikely // that a transaction will appear - the obligation should be // removed. h.removeObligation(uo, obligationUnconfirmed) continue } // Check that the file Merkle root matches the Merkle root found in the // blockchain. file, err := os.Open(uo.Path) if err != nil { h.log.Println("Compatibility contract file could not be opened.") h.removeObligation(uo, obligationFailed) continue } merkleRoot, err := crypto.ReaderMerkleRoot(file) file.Close() // Close the file after use, to prevent buildup if the loop iterates many times. if err != nil { h.log.Println("Compatibility contract file could not be checksummed") h.removeObligation(uo, obligationFailed) continue } if merkleRoot != uo.merkleRoot() { h.log.Println("Compatibility contract file has the wrong merkle root") h.removeObligation(uo, obligationFailed) continue } } return nil }
// negotiateContract creates a file contract for a host according to the // requests of the host. There is an assumption that only hosts with acceptable // terms will be put into the hostdb. func (r *Renter) negotiateContract(host modules.HostSettings, up modules.FileUploadParams, piece *filePiece) error { lockID := r.mu.RLock() height := r.blockHeight r.mu.RUnlock(lockID) key, err := crypto.GenerateTwofishKey() if err != nil { return err } file, err := os.Open(up.Filename) if err != nil { return err } defer file.Close() info, err := file.Stat() if err != nil { return err } filesize := uint64(info.Size()) // Get the price and payout. sizeCurrency := types.NewCurrency64(filesize) durationCurrency := types.NewCurrency64(uint64(up.Duration)) clientCost := host.Price.Mul(sizeCurrency).Mul(durationCurrency) hostCollateral := host.Collateral.Mul(sizeCurrency).Mul(durationCurrency) payout := clientCost.Add(hostCollateral) validOutputValue := payout.Sub(types.FileContract{Payout: payout}.Tax()) // Create the contract terms. terms := modules.ContractTerms{ FileSize: filesize, Duration: up.Duration, DurationStart: height - 3, WindowSize: defaultWindowSize, Price: host.Price, Collateral: host.Collateral, ValidProofOutputs: []types.SiacoinOutput{ {Value: validOutputValue, UnlockHash: host.UnlockHash}, }, MissedProofOutputs: []types.SiacoinOutput{ {Value: validOutputValue, UnlockHash: types.UnlockHash{}}, }, } // TODO: This is a hackish sleep, we need to be certain that all dependent // transactions have propgated to the host's transaction pool. Instead, // built into the protocol should be a step where any dependent // transactions are automatically provided. if build.Release == "standard" { time.Sleep(time.Minute) } else if build.Release == "testing" { time.Sleep(time.Second * 15) } else { time.Sleep(time.Second) } // Perform the negotiations with the host through a network call. conn, err := net.DialTimeout("tcp", string(host.IPAddress), 10e9) if err != nil { return err } defer conn.Close() err = encoding.WriteObject(conn, [8]byte{'C', 'o', 'n', 't', 'r', 'a', 'c', 't'}) if err != nil { return err } // Send the contract terms and read the response. if err = encoding.WriteObject(conn, terms); err != nil { return err } var response string if err = encoding.ReadObject(conn, &response, 128); err != nil { return err } if response != modules.AcceptTermsResponse { return errors.New(response) } // Encrypt and transmit the file data while calculating its Merkle root. tee := io.TeeReader( // wrap file reader in encryption layer key.NewReader(file), // each byte we read from tee will also be written to conn; // the uploadWriter updates the piece's 'Transferred' field &uploadWriter{piece, conn}, ) merkleRoot, err := crypto.ReaderMerkleRoot(tee) if err != nil { return err } // Create the transaction holding the contract. This is done first so the // transaction is created sooner, which will impact the user's wallet // balance faster vs. waiting for the whole thing to upload before // affecting the user's balance. unsignedTxn, txnBuilder, err := r.createContractTransaction(terms, merkleRoot) if err != nil { return err } // Send the unsigned transaction to the host. err = encoding.WriteObject(conn, unsignedTxn) if err != nil { return err } // The host will respond with a transaction with the collateral added. // Add the collateral inputs from the host to the original wallet // transaction. var collateralTxn types.Transaction err = encoding.ReadObject(conn, &collateralTxn, 16e3) if err != nil { return err } for i := len(unsignedTxn.SiacoinInputs); i < len(collateralTxn.SiacoinInputs); i++ { txnBuilder.AddSiacoinInput(collateralTxn.SiacoinInputs[i]) } signedTxn, err := txnBuilder.Sign(true) if err != nil { return err } // Send the signed transaction back to the host. err = encoding.WriteObject(conn, signedTxn) if err != nil { return err } // Read an ack from the host that all is well. var ack bool err = encoding.ReadObject(conn, &ack, 1) if err != nil { return err } if !ack { return errors.New("host negotiation failed") } // TODO: We don't actually watch the blockchain to make sure that the // file contract made it. // Negotiation was successful; update the filePiece. txIndex := len(signedTxn) - 1 lockID = r.mu.Lock() piece.Active = true piece.Repairing = false piece.Contract = signedTxn[txIndex].FileContracts[0] piece.ContractID = signedTxn[txIndex].FileContractID(0) piece.HostIP = host.IPAddress piece.EncryptionKey = key r.save() r.mu.Unlock(lockID) return nil }
// 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) } }
// TestValidFileContractRevisions probes the validFileContractRevisions method // of the consensus set. func TestValidFileContractRevisions(t *testing.T) { if testing.Short() { // t.SkipNow() } cst, err := createConsensusSetTester("TestValidStorageProofs") if err != nil { t.Fatal(err) } // Grab an address + unlock conditions for the transaction. unlockHash, unlockConditions, err := cst.wallet.CoinAddress(false) 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: 102, WindowEnd: 1200, UnlockHash: unlockHash, RevisionNumber: 1, } cst.cs.fileContracts[fcid] = fc // Try a working file contract revision. txn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{ { ParentID: fcid, UnlockConditions: unlockConditions, NewRevisionNumber: 2, }, }, } err = cst.cs.validFileContractRevisions(txn) if err != nil { t.Error(err) } // Try a transaction with an insufficient revision number. txn = types.Transaction{ FileContractRevisions: []types.FileContractRevision{ { ParentID: fcid, UnlockConditions: unlockConditions, NewRevisionNumber: 1, }, }, } err = cst.cs.validFileContractRevisions(txn) if err != ErrLowRevisionNumber { t.Error(err) } txn = types.Transaction{ FileContractRevisions: []types.FileContractRevision{ { ParentID: fcid, UnlockConditions: unlockConditions, NewRevisionNumber: 0, }, }, } err = cst.cs.validFileContractRevisions(txn) if err != ErrLowRevisionNumber { t.Error(err) } }