github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/host/negotiatedownload.go (about) 1 package host 2 3 import ( 4 "errors" 5 "net" 6 "time" 7 8 "github.com/NebulousLabs/Sia/encoding" 9 "github.com/NebulousLabs/Sia/modules" 10 "github.com/NebulousLabs/Sia/types" 11 ) 12 13 var ( 14 // errDownloadBadHostValidOutputs is returned if the renter requests a 15 // download and pays an insufficient amount to the host valid addresses. 16 errDownloadBadHostValidOutputs = errors.New("download request rejected for bad host valid outputs") 17 18 // errDownloadBadNewFileMerkleRoot is returned if the renter requests a 19 // download and changes the file merkle root in the payment revision. 20 errDownloadBadNewFileMerkleRoot = errors.New("download request rejected for bad file merkle root") 21 22 // errDownloadBadNewFileSize is returned if the renter requests a download 23 // and changes the file size in the payment revision. 24 errDownloadBadNewFileSize = errors.New("download request rejected for bad file size") 25 26 // errDownloadBadHostMissedOutputs is returned if the renter requests a 27 // download and changes the host missed outputs in the payment revision. 28 errDownloadBadHostMissedOutputs = errors.New("download request rejected for bad host missed outputs") 29 30 // errDownloadBadNewWindowEnd is returned if the renter requests a download 31 // and changes the window end in the payment revision. 32 errDownloadBadNewWindowEnd = errors.New("download request rejected for bad new window end") 33 34 // errDownloadBadNewWindowStart is returned if the renter requests a 35 // download and changes the window start in the payment revision. 36 errDownloadBadNewWindowStart = errors.New("download request rejected for bad new window start") 37 38 // errDownloadBadNewUnlockHash is returned if the renter requests a 39 // download and changes the unlock hash in the payment revision. 40 errDownloadBadNewUnlockHash = errors.New("download request rejected for bad new unlock hash") 41 42 // errDownloadBadParentID is returned if the renter requests a download and 43 // provides the wrong parent id in the payment revision. 44 errDownloadBadParentID = errors.New("download request rejected for bad parent id") 45 46 // errDownloadBadRenterMissedOutputs is returned if the renter requests a 47 // download and deducts an insufficient amount from the renter missed 48 // outputs in the payment revision. 49 errDownloadBadRenterMissedOutputs = errors.New("download request rejected for bad renter missed outputs") 50 51 // errDownloadBadRenterValidOutputs is returned if the renter requests a 52 // download and deducts an insufficient amount from the renter valid 53 // outputs in the payment revision. 54 errDownloadBadRenterValidOutputs = errors.New("download request rejected for bad renter valid outputs") 55 56 // errDownloadBadRevision number is returned if the renter requests a 57 // download and does not increase the revision number in the payment 58 // revision. 59 errDownloadBadRevisionNumber = errors.New("download request rejected for bad revision number") 60 61 // errDownloadBadUnlockConditions is returned if the renter requests a 62 // download and does not provide the right unlock conditions in the payment 63 // revision. 64 errDownloadBadUnlockConditions = errors.New("download request rejected for bad unlock conditions") 65 66 // errDownloadBadVoidOutputs is returned if the renter requests a download 67 // and does not add sufficient payment to the void outputs in the payment 68 // revision. 69 errDownloadBadVoidOutputs = errors.New("download request rejected for bad void outputs") 70 71 // errLargeDownloadBatch is returned if the renter requests a download 72 // batch that exceeds the maximum batch size that the host will 73 // accomondate. 74 errLargeDownloadBatch = errors.New("download request exceeded maximum batch size") 75 76 // errRequestOutOfBounds is returned when a download request is made which 77 // asks for elements of a sector which do not exist. 78 errRequestOutOfBounds = errors.New("download request has invalid sector bounds") 79 ) 80 81 // managedDownloadIteration is responsible for managing a single iteration of 82 // the download loop for RPCDownload. 83 func (h *Host) managedDownloadIteration(conn net.Conn, so *storageObligation) error { 84 // Exchange settings with the renter. 85 err := h.managedRPCSettings(conn) 86 if err != nil { 87 return err 88 } 89 90 // Extend the deadline for the download. 91 conn.SetDeadline(time.Now().Add(modules.NegotiateDownloadTime)) 92 93 // The renter will either accept or reject the host's settings. 94 err = modules.ReadNegotiationAcceptance(conn) 95 if err != nil { 96 return err 97 } 98 99 // Grab a set of variables that will be useful later in the function. 100 h.mu.RLock() 101 blockHeight := h.blockHeight 102 secretKey := h.secretKey 103 settings := h.settings 104 h.mu.RUnlock() 105 106 // Read the download requests, followed by the file contract revision that 107 // pays for them. 108 var requests []modules.DownloadAction 109 var paymentRevision types.FileContractRevision 110 err = encoding.ReadObject(conn, &requests, modules.NegotiateMaxDownloadActionRequestSize) 111 if err != nil { 112 return err 113 } 114 err = encoding.ReadObject(conn, &paymentRevision, modules.NegotiateMaxFileContractRevisionSize) 115 if err != nil { 116 return err 117 } 118 119 // Verify that the request is acceptable, and then fetch all of the data 120 // for the renter. 121 existingRevision := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0] 122 var payload [][]byte 123 err = func() error { 124 // Check that the length of each file is in-bounds, and that the total 125 // size being requested is acceptable. 126 var totalSize uint64 127 for _, request := range requests { 128 if request.Length > modules.SectorSize || request.Offset+request.Length > modules.SectorSize { 129 return errRequestOutOfBounds 130 } 131 totalSize += request.Length 132 } 133 if totalSize > settings.MaxDownloadBatchSize { 134 return errLargeDownloadBatch 135 } 136 137 // Verify that the correct amount of money has been moved from the 138 // renter's contract funds to the host's contract funds. 139 expectedTransfer := settings.MinimumDownloadBandwidthPrice.Mul64(totalSize) 140 err = verifyPaymentRevision(existingRevision, paymentRevision, blockHeight, expectedTransfer) 141 if err != nil { 142 return err 143 } 144 145 // Load the sectors and build the data payload. 146 for _, request := range requests { 147 sectorData, err := h.ReadSector(request.MerkleRoot) 148 if err != nil { 149 return err 150 } 151 payload = append(payload, sectorData[request.Offset:request.Offset+request.Length]) 152 } 153 return nil 154 }() 155 if err != nil { 156 return modules.WriteNegotiationRejection(conn, err) 157 } 158 // Revision is acceptable, write acceptance. 159 err = modules.WriteNegotiationAcceptance(conn) 160 if err != nil { 161 return err 162 } 163 164 // Renter will send a transaction siganture for the file contract revision. 165 var renterSignature types.TransactionSignature 166 err = encoding.ReadObject(conn, &renterSignature, modules.NegotiateMaxTransactionSignatureSize) 167 if err != nil { 168 return err 169 } 170 txn, err := createRevisionSignature(paymentRevision, renterSignature, secretKey, blockHeight) 171 172 // Update the storage obligation. 173 paymentTransfer := existingRevision.NewValidProofOutputs[0].Value.Sub(paymentRevision.NewValidProofOutputs[0].Value) 174 so.PotentialDownloadRevenue = so.PotentialDownloadRevenue.Add(paymentTransfer) 175 so.RevisionTransactionSet = []types.Transaction{{ 176 FileContractRevisions: []types.FileContractRevision{paymentRevision}, 177 TransactionSignatures: []types.TransactionSignature{renterSignature, txn.TransactionSignatures[1]}, 178 }} 179 err = h.modifyStorageObligation(so, nil, nil, nil) 180 if err != nil { 181 return modules.WriteNegotiationRejection(conn, err) 182 } 183 184 // Write acceptance to the renter - the data request can be fulfilled by 185 // the host, the payment is satisfactory, signature is correct. Then send 186 // the host signature and all of the data. 187 err = modules.WriteNegotiationAcceptance(conn) 188 if err != nil { 189 return err 190 } 191 err = encoding.WriteObject(conn, txn.TransactionSignatures[1]) 192 if err != nil { 193 return err 194 } 195 return encoding.WriteObject(conn, payload) 196 } 197 198 // verifyPaymentRevision verifies that the revision being provided to pay for 199 // the data has transferred the expected amount of money from the renter to the 200 // host. 201 func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractRevision, blockHeight types.BlockHeight, expectedTransfer types.Currency) error { 202 // Check that the revision is well-formed. 203 if len(paymentRevision.NewValidProofOutputs) != 2 || len(paymentRevision.NewMissedProofOutputs) != 3 { 204 return errInsaneFileContractRevisionOutputCounts 205 } 206 207 // Check that the time to finalize and submit the file contract revision 208 // has not already passed. 209 if existingRevision.NewWindowStart-revisionSubmissionBuffer <= blockHeight { 210 return errLateRevision 211 } 212 213 // The new revenue comes out of the renter's valid outputs. 214 if paymentRevision.NewValidProofOutputs[0].Value.Add(expectedTransfer).Cmp(existingRevision.NewValidProofOutputs[0].Value) > 0 { 215 return errDownloadBadRenterValidOutputs 216 } 217 // The new revenue goes into the host's valid outputs. 218 if existingRevision.NewValidProofOutputs[1].Value.Add(expectedTransfer).Cmp(paymentRevision.NewValidProofOutputs[1].Value) < 0 { 219 return errDownloadBadHostValidOutputs 220 } 221 // The new revenue comes out of the renter's missed outputs. 222 if paymentRevision.NewMissedProofOutputs[0].Value.Add(expectedTransfer).Cmp(existingRevision.NewMissedProofOutputs[0].Value) > 0 { 223 return errDownloadBadRenterMissedOutputs 224 } 225 // The new revenue goes into the void outputs. 226 if existingRevision.NewMissedProofOutputs[2].Value.Add(expectedTransfer).Cmp(paymentRevision.NewMissedProofOutputs[2].Value) < 0 { 227 return errDownloadBadVoidOutputs 228 } 229 // Check that the revision count has increased. 230 if paymentRevision.NewRevisionNumber <= existingRevision.NewRevisionNumber { 231 return errDownloadBadRevisionNumber 232 } 233 234 // Check that all of the non-volatile fields are the same. 235 if paymentRevision.ParentID != existingRevision.ParentID { 236 return errDownloadBadParentID 237 } 238 if paymentRevision.UnlockConditions.UnlockHash() != existingRevision.UnlockConditions.UnlockHash() { 239 return errDownloadBadUnlockConditions 240 } 241 if paymentRevision.NewFileSize != existingRevision.NewFileSize { 242 return errDownloadBadNewFileSize 243 } 244 if paymentRevision.NewFileMerkleRoot != existingRevision.NewFileMerkleRoot { 245 return errDownloadBadNewFileMerkleRoot 246 } 247 if paymentRevision.NewWindowStart != existingRevision.NewWindowStart { 248 return errDownloadBadNewWindowStart 249 } 250 if paymentRevision.NewWindowEnd != existingRevision.NewWindowEnd { 251 return errDownloadBadNewWindowEnd 252 } 253 if paymentRevision.NewUnlockHash != existingRevision.NewUnlockHash { 254 return errDownloadBadNewUnlockHash 255 } 256 if paymentRevision.NewMissedProofOutputs[1].Value.Cmp(existingRevision.NewMissedProofOutputs[1].Value) != 0 { 257 return errDownloadBadHostMissedOutputs 258 } 259 return nil 260 } 261 262 // managedRPCDownload is responsible for handling an RPC request from the 263 // renter to download data. 264 func (h *Host) managedRPCDownload(conn net.Conn) error { 265 // Get the start time to limit the length of the whole connection. 266 startTime := time.Now() 267 // Perform the file contract revision exchange, giving the renter the most 268 // recent file contract revision and getting the storage obligation that 269 // will be used to pay for the data. 270 _, so, err := h.managedRPCRecentRevision(conn) 271 if err != nil { 272 return err 273 } 274 275 // Lock the storage obligation during the revision. 276 h.mu.Lock() 277 err = h.lockStorageObligation(so) 278 h.mu.Unlock() 279 if err != nil { 280 return err 281 } 282 defer func() { 283 h.mu.Lock() 284 err = h.unlockStorageObligation(so) 285 h.mu.Unlock() 286 if err != nil { 287 h.log.Critical(err) 288 } 289 }() 290 291 // Perform a loop that will allow downloads to happen until the maximum 292 // time for a single connection has been reached. 293 for time.Now().Before(startTime.Add(iteratedConnectionTime)) { 294 err := h.managedDownloadIteration(conn, so) 295 if err == modules.ErrStopResponse { 296 // The renter has indicated that it has finished downloading the 297 // data, therefore there is no error. Return nil. 298 return nil 299 } else if err != nil { 300 return err 301 } 302 } 303 return nil 304 }