// Close cleanly terminates the download loop with the host and closes the // connection. func (hd *Downloader) Close() error { extendDeadline(hd.conn, modules.NegotiateSettingsTime) // don't care about these errors _, _ = verifySettings(hd.conn, hd.host) _ = modules.WriteNegotiationStop(hd.conn) return hd.conn.Close() }
// TestNegotiateRevisionStopResponse tests that when the host sends // StopResponse, the renter continues processing the revision instead of // immediately terminating. func TestNegotiateRevisionStopResponse(t *testing.T) { // simulate a renter-host connection rConn, hConn := net.Pipe() // handle the host's half of the pipe go func() { defer hConn.Close() // read revision encoding.ReadObject(hConn, new(types.FileContractRevision), 1<<22) // write acceptance modules.WriteNegotiationAcceptance(hConn) // read txn signature encoding.ReadObject(hConn, new(types.TransactionSignature), 1<<22) // write StopResponse modules.WriteNegotiationStop(hConn) // write txn signature encoding.WriteObject(hConn, types.TransactionSignature{}) }() // since the host wrote StopResponse, we should proceed to validating the // transaction. This will return a known error because we are supplying an // empty revision. _, err := negotiateRevision(rConn, types.FileContractRevision{}, crypto.SecretKey{}) if err != types.ErrFileContractWindowStartViolation { t.Fatalf("expected %q, got \"%v\"", types.ErrFileContractWindowStartViolation, err) } rConn.Close() // same as above, but send an error instead of StopResponse. The error // should be returned by negotiateRevision immediately (if it is not, we // should expect to see a transaction validation error instead). rConn, hConn = net.Pipe() go func() { defer hConn.Close() encoding.ReadObject(hConn, new(types.FileContractRevision), 1<<22) modules.WriteNegotiationAcceptance(hConn) encoding.ReadObject(hConn, new(types.TransactionSignature), 1<<22) // write a sentinel error modules.WriteNegotiationRejection(hConn, errors.New("sentinel")) encoding.WriteObject(hConn, types.TransactionSignature{}) }() expectedErr := "host did not accept transaction signature: sentinel" _, err = negotiateRevision(rConn, types.FileContractRevision{}, crypto.SecretKey{}) if err == nil || err.Error() != expectedErr { t.Fatalf("expected %q, got \"%v\"", expectedErr, err) } rConn.Close() }
// managedRevisionIteration handles one iteration of the revision loop. As a // performance optimization, multiple iterations of revisions are allowed to be // made over the same connection. func (h *Host) managedRevisionIteration(conn net.Conn, so *storageObligation, finalIter bool) error { // Send the settings to the renter. The host will keep going even if it is // not accepting contracts, because in this case the contract already // exists. err := h.managedRPCSettings(conn) if err != nil { return err } // Set the negotiation deadline. conn.SetDeadline(time.Now().Add(modules.NegotiateFileContractRevisionTime)) // The renter will either accept or reject the settings + revision // transaction. It may also return a stop response to indicate that it // wishes to terminate the revision loop. err = modules.ReadNegotiationAcceptance(conn) if err != nil { return err } // Read some variables from the host for use later in the function. h.mu.RLock() settings := h.settings secretKey := h.secretKey blockHeight := h.blockHeight h.mu.RUnlock() // The renter is going to send its intended modifications, followed by the // file contract revision that pays for them. var modifications []modules.RevisionAction var revision types.FileContractRevision err = encoding.ReadObject(conn, &modifications, settings.MaxReviseBatchSize) if err != nil { return err } err = encoding.ReadObject(conn, &revision, modules.NegotiateMaxFileContractRevisionSize) if err != nil { return err } // First read all of the modifications. Then make the modifications, but // with the ability to reverse them. Then verify the file contract revision // correctly accounts for the changes. var bandwidthRevenue types.Currency // Upload bandwidth. var storageRevenue types.Currency var newCollateral types.Currency var sectorsRemoved []crypto.Hash var sectorsGained []crypto.Hash var gainedSectorData [][]byte err = func() error { for _, modification := range modifications { // Check that the index points to an existing sector root. If the type // is ActionInsert, we permit inserting at the end. if modification.Type == modules.ActionInsert { if modification.SectorIndex > uint64(len(so.SectorRoots)) { return errBadModificationIndex } } else if modification.SectorIndex >= uint64(len(so.SectorRoots)) { return errBadModificationIndex } // Check that the data sent for the sector is not too large. if uint64(len(modification.Data)) > modules.SectorSize { return errLargeSector } switch modification.Type { case modules.ActionDelete: // There is no financial information to change, it is enough to // remove the sector. sectorsRemoved = append(sectorsRemoved, so.SectorRoots[modification.SectorIndex]) so.SectorRoots = append(so.SectorRoots[0:modification.SectorIndex], so.SectorRoots[modification.SectorIndex+1:]...) case modules.ActionInsert: // Check that the sector size is correct. if uint64(len(modification.Data)) != modules.SectorSize { return errBadSectorSize } // Update finances. blocksRemaining := so.proofDeadline() - blockHeight blockBytesCurrency := types.NewCurrency64(uint64(blocksRemaining)).Mul64(modules.SectorSize) bandwidthRevenue = bandwidthRevenue.Add(settings.MinUploadBandwidthPrice.Mul64(modules.SectorSize)) storageRevenue = storageRevenue.Add(settings.MinStoragePrice.Mul(blockBytesCurrency)) newCollateral = newCollateral.Add(settings.Collateral.Mul(blockBytesCurrency)) // Insert the sector into the root list. newRoot := crypto.MerkleRoot(modification.Data) sectorsGained = append(sectorsGained, newRoot) gainedSectorData = append(gainedSectorData, modification.Data) so.SectorRoots = append(so.SectorRoots[:modification.SectorIndex], append([]crypto.Hash{newRoot}, so.SectorRoots[modification.SectorIndex:]...)...) case modules.ActionModify: // Check that the offset and length are okay. Length is already // known to be appropriately small, but the offset needs to be // checked for being appropriately small as well otherwise there is // a risk of overflow. if modification.Offset > modules.SectorSize || modification.Offset+uint64(len(modification.Data)) > modules.SectorSize { return errIllegalOffsetAndLength } // Get the data for the new sector. sector, err := h.ReadSector(so.SectorRoots[modification.SectorIndex]) if err != nil { return err } copy(sector[modification.Offset:], modification.Data) // Update finances. bandwidthRevenue = bandwidthRevenue.Add(settings.MinUploadBandwidthPrice.Mul64(uint64(len(modification.Data)))) // Update the sectors removed and gained to indicate that the old // sector has been replaced with a new sector. newRoot := crypto.MerkleRoot(sector) sectorsRemoved = append(sectorsRemoved, so.SectorRoots[modification.SectorIndex]) sectorsGained = append(sectorsGained, newRoot) gainedSectorData = append(gainedSectorData, sector) so.SectorRoots[modification.SectorIndex] = newRoot default: return errUnknownModification } } newRevenue := storageRevenue.Add(bandwidthRevenue) return verifyRevision(so, revision, blockHeight, newRevenue, newCollateral) }() if err != nil { return modules.WriteNegotiationRejection(conn, err) } // Revision is acceptable, write an acceptance string. err = modules.WriteNegotiationAcceptance(conn) if err != nil { return err } // Renter will send a transaction signature for the file contract revision. var renterSig types.TransactionSignature err = encoding.ReadObject(conn, &renterSig, modules.NegotiateMaxTransactionSignatureSize) if err != nil { return err } // Verify that the signature is valid and get the host's signature. txn, err := createRevisionSignature(revision, renterSig, secretKey, blockHeight) if err != nil { return modules.WriteNegotiationRejection(conn, err) } so.PotentialStorageRevenue = so.PotentialStorageRevenue.Add(storageRevenue) so.RiskedCollateral = so.RiskedCollateral.Add(newCollateral) so.PotentialUploadRevenue = so.PotentialUploadRevenue.Add(bandwidthRevenue) so.RevisionTransactionSet = []types.Transaction{txn} err = h.modifyStorageObligation(so, sectorsRemoved, sectorsGained, gainedSectorData) if err != nil { return modules.WriteNegotiationRejection(conn, err) } // Host will now send acceptance and its signature to the renter. This // iteration is complete. If the finalIter flag is set, StopResponse will // be sent instead. This indicates to the renter that the host wishes to // terminate the revision loop. if finalIter { err = modules.WriteNegotiationStop(conn) } else { err = modules.WriteNegotiationAcceptance(conn) } if err != nil { return err } return encoding.WriteObject(conn, txn.TransactionSignatures[1]) }