gitlab.com/jokerrs1/Sia@v1.3.2/modules/host/negotiateformcontract.go (about) 1 package host 2 3 import ( 4 "net" 5 "time" 6 7 "github.com/NebulousLabs/Sia/crypto" 8 "github.com/NebulousLabs/Sia/encoding" 9 "github.com/NebulousLabs/Sia/modules" 10 "github.com/NebulousLabs/Sia/types" 11 ) 12 13 var ( 14 // errCollateralBudgetExceeded is returned if the host does not have enough 15 // room in the collateral budget to accept a particular file contract. 16 errCollateralBudgetExceeded = ErrorInternal("host has reached its collateral budget and cannot accept the file contract") 17 18 // errMaxCollateralReached is returned if a file contract is provided which 19 // would require the host to supply more collateral than the host allows 20 // per file contract. 21 errMaxCollateralReached = ErrorInternal("file contract proposal expects the host to pay more than the maximum allowed collateral") 22 ) 23 24 // contractCollateral returns the amount of collateral that the host is 25 // expected to add to the file contract based on the payout of the file 26 // contract and based on the host settings. 27 func contractCollateral(settings modules.HostExternalSettings, fc types.FileContract) types.Currency { 28 return fc.ValidProofOutputs[1].Value.Sub(settings.ContractPrice) 29 } 30 31 // managedAddCollateral adds the host's collateral to the file contract 32 // transaction set, returning the new inputs and outputs that get added to the 33 // transaction, as well as any new parents that get added to the transaction 34 // set. The builder that is used to add the collateral is also returned, 35 // because the new transaction has not yet been signed. 36 func (h *Host) managedAddCollateral(settings modules.HostExternalSettings, txnSet []types.Transaction) (builder modules.TransactionBuilder, newParents []types.Transaction, newInputs []types.SiacoinInput, newOutputs []types.SiacoinOutput, err error) { 37 txn := txnSet[len(txnSet)-1] 38 parents := txnSet[:len(txnSet)-1] 39 fc := txn.FileContracts[0] 40 hostPortion := contractCollateral(settings, fc) 41 builder = h.wallet.RegisterTransaction(txn, parents) 42 err = builder.FundSiacoins(hostPortion) 43 if err != nil { 44 builder.Drop() 45 return nil, nil, nil, nil, extendErr("could not add collateral: ", ErrorInternal(err.Error())) 46 } 47 48 // Return which inputs and outputs have been added by the collateral call. 49 newParentIndices, newInputIndices, newOutputIndices, _ := builder.ViewAdded() 50 updatedTxn, updatedParents := builder.View() 51 for _, parentIndex := range newParentIndices { 52 newParents = append(newParents, updatedParents[parentIndex]) 53 } 54 for _, inputIndex := range newInputIndices { 55 newInputs = append(newInputs, updatedTxn.SiacoinInputs[inputIndex]) 56 } 57 for _, outputIndex := range newOutputIndices { 58 newOutputs = append(newOutputs, updatedTxn.SiacoinOutputs[outputIndex]) 59 } 60 return builder, newParents, newInputs, newOutputs, nil 61 } 62 63 // managedRPCFormContract accepts a file contract from a renter, checks the 64 // file contract for compliance with the host settings, and then commits to the 65 // file contract, creating a storage obligation and submitting the contract to 66 // the blockchain. 67 func (h *Host) managedRPCFormContract(conn net.Conn) error { 68 // Send the host settings to the renter. 69 err := h.managedRPCSettings(conn) 70 if err != nil { 71 return extendErr("failed RPCSettings: ", err) 72 } 73 // If the host is not accepting contracts, the connection can be closed. 74 // The renter has been given enough information in the host settings to 75 // understand that the connection is going to be closed. 76 h.mu.Lock() 77 settings := h.externalSettings() 78 h.mu.Unlock() 79 if !settings.AcceptingContracts { 80 h.log.Debugln("Turning down contract because the host is not accepting contracts.") 81 return nil 82 } 83 84 // Extend the deadline to meet the rest of file contract negotiation. 85 conn.SetDeadline(time.Now().Add(modules.NegotiateFileContractTime)) 86 87 // The renter will either accept or reject the host's settings. 88 err = modules.ReadNegotiationAcceptance(conn) 89 if err != nil { 90 return extendErr("renter did not accept settings: ", ErrorCommunication(err.Error())) 91 } 92 // If the renter sends an acceptance of the settings, it will be followed 93 // by an unsigned transaction containing funding from the renter and a file 94 // contract which matches what the final file contract should look like. 95 // After the file contract, the renter will send a public key which is the 96 // renter's public key in the unlock conditions that protect the file 97 // contract from revision. 98 var txnSet []types.Transaction 99 var renterPK crypto.PublicKey 100 err = encoding.ReadObject(conn, &txnSet, modules.NegotiateMaxFileContractSetLen) 101 if err != nil { 102 return extendErr("could not read renter transaction set: ", ErrorConnection(err.Error())) 103 } 104 err = encoding.ReadObject(conn, &renterPK, modules.NegotiateMaxSiaPubkeySize) 105 if err != nil { 106 return extendErr("could not read renter public key: ", ErrorConnection(err.Error())) 107 } 108 109 // The host verifies that the file contract coming over the wire is 110 // acceptable. 111 err = h.managedVerifyNewContract(txnSet, renterPK, settings) 112 if err != nil { 113 // The incoming file contract is not acceptable to the host, indicate 114 // why to the renter. 115 modules.WriteNegotiationRejection(conn, err) // Error ignored to preserve type in extendErr 116 return extendErr("contract verification failed: ", err) 117 } 118 // The host adds collateral to the transaction. 119 txnBuilder, newParents, newInputs, newOutputs, err := h.managedAddCollateral(settings, txnSet) 120 if err != nil { 121 modules.WriteNegotiationRejection(conn, err) // Error ignored to preserve type in extendErr 122 return extendErr("failed to add collateral: ", err) 123 } 124 // The host indicates acceptance, and then sends any new parent 125 // transactions, inputs and outputs that were added to the transaction. 126 err = modules.WriteNegotiationAcceptance(conn) 127 if err != nil { 128 return extendErr("accepting verified contract failed: ", ErrorConnection(err.Error())) 129 } 130 err = encoding.WriteObject(conn, newParents) 131 if err != nil { 132 return extendErr("failed to write new parents: ", ErrorConnection(err.Error())) 133 } 134 err = encoding.WriteObject(conn, newInputs) 135 if err != nil { 136 return extendErr("failed to write new inputs: ", ErrorConnection(err.Error())) 137 } 138 err = encoding.WriteObject(conn, newOutputs) 139 if err != nil { 140 return extendErr("failed to write new outputs: ", ErrorConnection(err.Error())) 141 } 142 143 // The renter will now send a negotiation response, followed by transaction 144 // signatures for the file contract transaction in the case of acceptance. 145 // The transaction signatures will be followed by another transaction 146 // signature, to sign a no-op file contract revision. 147 err = modules.ReadNegotiationAcceptance(conn) 148 if err != nil { 149 return extendErr("renter did not accept updated transactions: ", ErrorCommunication(err.Error())) 150 } 151 var renterTxnSignatures []types.TransactionSignature 152 var renterRevisionSignature types.TransactionSignature 153 err = encoding.ReadObject(conn, &renterTxnSignatures, modules.NegotiateMaxTransactionSignaturesSize) 154 if err != nil { 155 return extendErr("could not read renter transaction signatures: ", ErrorConnection(err.Error())) 156 } 157 err = encoding.ReadObject(conn, &renterRevisionSignature, modules.NegotiateMaxTransactionSignatureSize) 158 if err != nil { 159 return extendErr("could not read renter revision signatures: ", ErrorConnection(err.Error())) 160 } 161 162 // The host adds the renter transaction signatures, then signs the 163 // transaction and submits it to the blockchain, creating a storage 164 // obligation in the process. The host's part is done before anything is 165 // written to the renter, but to give the renter confidence, the host will 166 // send the signatures so that the renter can immediately have the 167 // completed file contract. 168 // 169 // During finalization, the signature for the revision is also checked, and 170 // signatures for the revision transaction are created. 171 h.mu.RLock() 172 hostCollateral := contractCollateral(settings, txnSet[len(txnSet)-1].FileContracts[0]) 173 h.mu.RUnlock() 174 hostTxnSignatures, hostRevisionSignature, newSOID, err := h.managedFinalizeContract(txnBuilder, renterPK, renterTxnSignatures, renterRevisionSignature, nil, hostCollateral, types.ZeroCurrency, types.ZeroCurrency, settings) 175 if err != nil { 176 // The incoming file contract is not acceptable to the host, indicate 177 // why to the renter. 178 modules.WriteNegotiationRejection(conn, err) // Error ignored to preserve type in extendErr 179 return extendErr("contract finalization failed: ", err) 180 } 181 defer h.managedUnlockStorageObligation(newSOID) 182 err = modules.WriteNegotiationAcceptance(conn) 183 if err != nil { 184 return extendErr("failed to write acceptance after contract finalization: ", ErrorConnection(err.Error())) 185 } 186 // The host sends the transaction signatures to the renter, followed by the 187 // revision signature. Negotiation is complete. 188 err = encoding.WriteObject(conn, hostTxnSignatures) 189 if err != nil { 190 return extendErr("failed to write host transaction signatures: ", ErrorConnection(err.Error())) 191 } 192 err = encoding.WriteObject(conn, hostRevisionSignature) 193 if err != nil { 194 return extendErr("failed to write host revision signatures: ", ErrorConnection(err.Error())) 195 } 196 return nil 197 } 198 199 // managedVerifyNewContract checks that an incoming file contract matches the host's 200 // expectations for a valid contract. 201 func (h *Host) managedVerifyNewContract(txnSet []types.Transaction, renterPK crypto.PublicKey, eSettings modules.HostExternalSettings) error { 202 // Check that the transaction set is not empty. 203 if len(txnSet) < 1 { 204 return extendErr("zero-length transaction set: ", errEmptyObject) 205 } 206 // Check that there is a file contract in the txnSet. 207 if len(txnSet[len(txnSet)-1].FileContracts) < 1 { 208 return extendErr("transaction without file contract: ", errEmptyObject) 209 } 210 211 h.mu.RLock() 212 blockHeight := h.blockHeight 213 lockedStorageCollateral := h.financialMetrics.LockedStorageCollateral 214 publicKey := h.publicKey 215 iSettings := h.settings 216 unlockHash := h.unlockHash 217 h.mu.RUnlock() 218 fc := txnSet[len(txnSet)-1].FileContracts[0] 219 220 // A new file contract should have a file size of zero. 221 if fc.FileSize != 0 { 222 return errBadFileSize 223 } 224 if fc.FileMerkleRoot != (crypto.Hash{}) { 225 return errBadFileMerkleRoot 226 } 227 // WindowStart must be at least revisionSubmissionBuffer blocks into the 228 // future. 229 if fc.WindowStart <= blockHeight+revisionSubmissionBuffer { 230 h.log.Debugf("A renter tried to form a contract that had a window start which was too soon. The contract started at %v, the current height is %v, the revisionSubmissionBuffer is %v, and the comparison was %v <= %v\n", fc.WindowStart, blockHeight, revisionSubmissionBuffer, fc.WindowStart, blockHeight+revisionSubmissionBuffer) 231 return errEarlyWindow 232 } 233 // WindowEnd must be at least settings.WindowSize blocks after 234 // WindowStart. 235 if fc.WindowEnd < fc.WindowStart+eSettings.WindowSize { 236 return errSmallWindow 237 } 238 // WindowEnd must not be more than settings.MaxDuration blocks into the 239 // future. 240 if fc.WindowStart > blockHeight+eSettings.MaxDuration { 241 return errLongDuration 242 } 243 244 // ValidProofOutputs shoud have 2 outputs (renter + host) and missed 245 // outputs should have 3 (renter + host + void) 246 if len(fc.ValidProofOutputs) != 2 || len(fc.MissedProofOutputs) != 3 { 247 return errBadContractOutputCounts 248 } 249 // The unlock hashes of the valid and missed proof outputs for the host 250 // must match the host's unlock hash. The third missed output should point 251 // to the void. 252 if fc.ValidProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[2].UnlockHash != (types.UnlockHash{}) { 253 return errBadPayoutUnlockHashes 254 } 255 // Check that the payouts for the valid proof outputs and the missed proof 256 // outputs are the same - this is important because no data has been added 257 // to the file contract yet. 258 if !fc.ValidProofOutputs[1].Value.Equals(fc.MissedProofOutputs[1].Value) { 259 return errMismatchedHostPayouts 260 } 261 // Check that there's enough payout for the host to cover at least the 262 // contract price. This will prevent negative currency panics when working 263 // with the collateral. 264 if fc.ValidProofOutputs[1].Value.Cmp(eSettings.ContractPrice) < 0 { 265 return errLowHostValidOutput 266 } 267 // Check that the collateral does not exceed the maximum amount of 268 // collateral allowed. 269 expectedCollateral := contractCollateral(eSettings, fc) 270 if expectedCollateral.Cmp(eSettings.MaxCollateral) > 0 { 271 return errMaxCollateralReached 272 } 273 // Check that the host has enough room in the collateral budget to add this 274 // collateral. 275 if lockedStorageCollateral.Add(expectedCollateral).Cmp(iSettings.CollateralBudget) > 0 { 276 return errCollateralBudgetExceeded 277 } 278 279 // The unlock hash for the file contract must match the unlock hash that 280 // the host knows how to spend. 281 expectedUH := types.UnlockConditions{ 282 PublicKeys: []types.SiaPublicKey{ 283 types.Ed25519PublicKey(renterPK), 284 publicKey, 285 }, 286 SignaturesRequired: 2, 287 }.UnlockHash() 288 if fc.UnlockHash != expectedUH { 289 return errBadUnlockHash 290 } 291 292 // Check that the transaction set has enough fees on it to get into the 293 // blockchain. 294 setFee := modules.CalculateFee(txnSet) 295 minFee, _ := h.tpool.FeeEstimation() 296 if setFee.Cmp(minFee) < 0 { 297 return errLowTransactionFees 298 } 299 return nil 300 }