gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/negotiatedownload.go (about) 1 package host 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "time" 8 9 "gitlab.com/SiaPrime/SiaPrime/encoding" 10 "gitlab.com/SiaPrime/SiaPrime/modules" 11 "gitlab.com/SiaPrime/SiaPrime/types" 12 ) 13 14 var ( 15 // errLargeDownloadBatch is returned if the renter requests a download 16 // batch that exceeds the maximum batch size that the host will 17 // accommodate. 18 errLargeDownloadBatch = ErrorCommunication("download request exceeded maximum batch size") 19 20 // errRequestOutOfBounds is returned when a download request is made which 21 // asks for elements of a sector which do not exist. 22 errRequestOutOfBounds = ErrorCommunication("download request has invalid sector bounds") 23 ) 24 25 // managedDownloadIteration is responsible for managing a single iteration of 26 // the download loop for RPCDownload. 27 func (h *Host) managedDownloadIteration(conn net.Conn, so *storageObligation) error { 28 // Exchange settings with the renter. 29 err := h.managedRPCSettings(conn) 30 if err != nil { 31 return extendErr("RPCSettings failed: ", err) 32 } 33 34 // Extend the deadline for the download. 35 conn.SetDeadline(time.Now().Add(modules.NegotiateDownloadTime)) 36 37 // The renter will either accept or reject the host's settings. 38 err = modules.ReadNegotiationAcceptance(conn) 39 if err == modules.ErrStopResponse { 40 return err // managedRPCDownload will catch this and exit gracefully 41 } else if err != nil { 42 return extendErr("renter rejected host settings: ", ErrorCommunication(err.Error())) 43 } 44 45 // Grab a set of variables that will be useful later in the function. 46 h.mu.Lock() 47 blockHeight := h.blockHeight 48 secretKey := h.secretKey 49 settings := h.externalSettings() 50 h.mu.Unlock() 51 52 // Read the download requests, followed by the file contract revision that 53 // pays for them. 54 var requests []modules.DownloadAction 55 var paymentRevision types.FileContractRevision 56 err = encoding.ReadObject(conn, &requests, modules.NegotiateMaxDownloadActionRequestSize) 57 if err != nil { 58 return extendErr("failed to read download requests:", ErrorConnection(err.Error())) 59 } 60 err = encoding.ReadObject(conn, &paymentRevision, modules.NegotiateMaxFileContractRevisionSize) 61 if err != nil { 62 return extendErr("failed to read payment revision:", ErrorConnection(err.Error())) 63 } 64 65 // Verify that the request is acceptable, and then fetch all of the data 66 // for the renter. 67 existingRevision := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0] 68 var payload [][]byte 69 err = func() error { 70 // Check that the length of each file is in-bounds, and that the total 71 // size being requested is acceptable. 72 var totalSize uint64 73 for _, request := range requests { 74 if request.Length > modules.SectorSize || request.Offset+request.Length > modules.SectorSize { 75 return extendErr("download iteration request failed: ", errRequestOutOfBounds) 76 } 77 totalSize += request.Length 78 } 79 if totalSize > settings.MaxDownloadBatchSize { 80 return extendErr("download iteration batch failed: ", errLargeDownloadBatch) 81 } 82 83 // Verify that the correct amount of money has been moved from the 84 // renter's contract funds to the host's contract funds. 85 expectedTransfer := settings.DownloadBandwidthPrice.Mul64(totalSize) 86 err = verifyPaymentRevision(existingRevision, paymentRevision, blockHeight, expectedTransfer) 87 if err != nil { 88 return extendErr("payment verification failed: ", err) 89 } 90 91 // Load the sectors and build the data payload. 92 for _, request := range requests { 93 sectorData, err := h.ReadSector(request.MerkleRoot) 94 if err != nil { 95 return extendErr("failed to load sector: ", ErrorInternal(err.Error())) 96 } 97 payload = append(payload, sectorData[request.Offset:request.Offset+request.Length]) 98 } 99 return nil 100 }() 101 if err != nil { 102 modules.WriteNegotiationRejection(conn, err) // Error not reported to preserve type in extendErr 103 return extendErr("download request rejected: ", err) 104 } 105 // Revision is acceptable, write acceptance. 106 err = modules.WriteNegotiationAcceptance(conn) 107 if err != nil { 108 return extendErr("failed to write acceptance for renter revision: ", ErrorConnection(err.Error())) 109 } 110 111 // Renter will send a transaction signature for the file contract revision. 112 var renterSignature types.TransactionSignature 113 err = encoding.ReadObject(conn, &renterSignature, modules.NegotiateMaxTransactionSignatureSize) 114 if err != nil { 115 return extendErr("failed to read renter signature: ", ErrorConnection(err.Error())) 116 } 117 txn, err := createRevisionSignature(paymentRevision, renterSignature, secretKey, blockHeight) 118 119 // Update the storage obligation. 120 paymentTransfer := existingRevision.NewValidProofOutputs[0].Value.Sub(paymentRevision.NewValidProofOutputs[0].Value) 121 so.PotentialDownloadRevenue = so.PotentialDownloadRevenue.Add(paymentTransfer) 122 so.RevisionTransactionSet = []types.Transaction{{ 123 FileContractRevisions: []types.FileContractRevision{paymentRevision}, 124 TransactionSignatures: []types.TransactionSignature{renterSignature, txn.TransactionSignatures[1]}, 125 }} 126 h.mu.Lock() 127 err = h.modifyStorageObligation(*so, nil, nil, nil) 128 h.mu.Unlock() 129 if err != nil { 130 return extendErr("failed to modify storage obligation: ", ErrorInternal(modules.WriteNegotiationRejection(conn, err).Error())) 131 } 132 133 // Write acceptance to the renter - the data request can be fulfilled by 134 // the host, the payment is satisfactory, signature is correct. Then send 135 // the host signature and all of the data. 136 err = modules.WriteNegotiationAcceptance(conn) 137 if err != nil { 138 return extendErr("failed to write acceptance following obligation modification: ", ErrorConnection(err.Error())) 139 } 140 err = encoding.WriteObject(conn, txn.TransactionSignatures[1]) 141 if err != nil { 142 return extendErr("failed to write signature: ", ErrorConnection(err.Error())) 143 } 144 err = encoding.WriteObject(conn, payload) 145 if err != nil { 146 return extendErr("failed to write payload: ", ErrorConnection(err.Error())) 147 } 148 return nil 149 } 150 151 // verifyPaymentRevision verifies that the revision being provided to pay for 152 // the data has transferred the expected amount of money from the renter to the 153 // host. 154 func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractRevision, blockHeight types.BlockHeight, expectedTransfer types.Currency) error { 155 // Check that the revision is well-formed. 156 if len(paymentRevision.NewValidProofOutputs) != 2 || len(paymentRevision.NewMissedProofOutputs) != 3 { 157 return errBadContractOutputCounts 158 } 159 160 // Check that the time to finalize and submit the file contract revision 161 // has not already passed. 162 if existingRevision.NewWindowStart-revisionSubmissionBuffer <= blockHeight { 163 return errLateRevision 164 } 165 166 // Host payout addresses shouldn't change 167 if paymentRevision.NewValidProofOutputs[1].UnlockHash != existingRevision.NewValidProofOutputs[1].UnlockHash { 168 return errors.New("host payout address changed") 169 } 170 if paymentRevision.NewMissedProofOutputs[1].UnlockHash != existingRevision.NewMissedProofOutputs[1].UnlockHash { 171 return errors.New("host payout address changed") 172 } 173 // Make sure the lost collateral still goes to the void 174 if paymentRevision.NewMissedProofOutputs[2].UnlockHash != existingRevision.NewMissedProofOutputs[2].UnlockHash { 175 return errors.New("lost collateral address was changed") 176 } 177 178 // Determine the amount that was transferred from the renter. 179 if paymentRevision.NewValidProofOutputs[0].Value.Cmp(existingRevision.NewValidProofOutputs[0].Value) > 0 { 180 return extendErr("renter increased its valid proof output: ", errHighRenterValidOutput) 181 } 182 fromRenter := existingRevision.NewValidProofOutputs[0].Value.Sub(paymentRevision.NewValidProofOutputs[0].Value) 183 // Verify that enough money was transferred. 184 if fromRenter.Cmp(expectedTransfer) < 0 { 185 s := fmt.Sprintf("expected at least %v to be exchanged, but %v was exchanged: ", expectedTransfer, fromRenter) 186 return extendErr(s, errHighRenterValidOutput) 187 } 188 189 // Determine the amount of money that was transferred to the host. 190 if existingRevision.NewValidProofOutputs[1].Value.Cmp(paymentRevision.NewValidProofOutputs[1].Value) > 0 { 191 return extendErr("host valid proof output was decreased: ", errLowHostValidOutput) 192 } 193 toHost := paymentRevision.NewValidProofOutputs[1].Value.Sub(existingRevision.NewValidProofOutputs[1].Value) 194 // Verify that enough money was transferred. 195 if !toHost.Equals(fromRenter) { 196 s := fmt.Sprintf("expected exactly %v to be transferred to the host, but %v was transferred: ", fromRenter, toHost) 197 return extendErr(s, errLowHostValidOutput) 198 } 199 200 // If the renter's valid proof output is larger than the renter's missed 201 // proof output, the renter has incentive to see the host fail. Make sure 202 // that this incentive is not present. 203 if paymentRevision.NewValidProofOutputs[0].Value.Cmp(paymentRevision.NewMissedProofOutputs[0].Value) > 0 { 204 return extendErr("renter has incentive to see host fail: ", errHighRenterMissedOutput) 205 } 206 207 // Check that the host is not going to be posting collateral. 208 if paymentRevision.NewMissedProofOutputs[1].Value.Cmp(existingRevision.NewMissedProofOutputs[1].Value) < 0 { 209 collateral := existingRevision.NewMissedProofOutputs[1].Value.Sub(paymentRevision.NewMissedProofOutputs[1].Value) 210 s := fmt.Sprintf("host not expecting to post any collateral, but contract has host posting %v collateral: ", collateral) 211 return extendErr(s, errLowHostMissedOutput) 212 } 213 214 // Check that the revision count has increased. 215 if paymentRevision.NewRevisionNumber <= existingRevision.NewRevisionNumber { 216 return errBadRevisionNumber 217 } 218 219 // Check that all of the non-volatile fields are the same. 220 if paymentRevision.ParentID != existingRevision.ParentID { 221 return errBadParentID 222 } 223 if paymentRevision.UnlockConditions.UnlockHash() != existingRevision.UnlockConditions.UnlockHash() { 224 return errBadUnlockConditions 225 } 226 if paymentRevision.NewFileSize != existingRevision.NewFileSize { 227 return errBadFileSize 228 } 229 if paymentRevision.NewFileMerkleRoot != existingRevision.NewFileMerkleRoot { 230 return errBadFileMerkleRoot 231 } 232 if paymentRevision.NewWindowStart != existingRevision.NewWindowStart { 233 return errBadWindowStart 234 } 235 if paymentRevision.NewWindowEnd != existingRevision.NewWindowEnd { 236 return errBadWindowEnd 237 } 238 if paymentRevision.NewUnlockHash != existingRevision.NewUnlockHash { 239 return errBadUnlockHash 240 } 241 if !paymentRevision.NewMissedProofOutputs[1].Value.Equals(existingRevision.NewMissedProofOutputs[1].Value) { 242 return errLowHostMissedOutput 243 } 244 return nil 245 } 246 247 // managedRPCDownload is responsible for handling an RPC request from the 248 // renter to download data. 249 func (h *Host) managedRPCDownload(conn net.Conn) error { 250 // Get the start time to limit the length of the whole connection. 251 startTime := time.Now() 252 // Perform the file contract revision exchange, giving the renter the most 253 // recent file contract revision and getting the storage obligation that 254 // will be used to pay for the data. 255 _, so, err := h.managedRPCRecentRevision(conn) 256 if err != nil { 257 return extendErr("failed RPCRecentRevision during RPCDownload: ", err) 258 } 259 // The storage obligation is returned with a lock on it. Defer a call to 260 // unlock the storage obligation. 261 defer func() { 262 h.managedUnlockStorageObligation(so.id()) 263 }() 264 265 // Perform a loop that will allow downloads to happen until the maximum 266 // time for a single connection has been reached. 267 for time.Now().Before(startTime.Add(iteratedConnectionTime)) { 268 err := h.managedDownloadIteration(conn, &so) 269 if err == modules.ErrStopResponse { 270 // The renter has indicated that it has finished downloading the 271 // data, therefore there is no error. Return nil. 272 return nil 273 } else if err != nil { 274 return extendErr("download iteration failed: ", err) 275 } 276 } 277 return nil 278 }