github.com/nebulouslabs/sia@v1.3.7/modules/host/negotiate.go (about) 1 package host 2 3 import ( 4 "time" 5 6 "github.com/NebulousLabs/Sia/build" 7 "github.com/NebulousLabs/Sia/crypto" 8 "github.com/NebulousLabs/Sia/modules" 9 "github.com/NebulousLabs/Sia/types" 10 ) 11 12 var ( 13 // errBadContractOutputCounts is returned if the presented file contract 14 // revision has the wrong number of outputs for either the valid or the 15 // missed proof outputs. 16 errBadContractOutputCounts = ErrorCommunication("rejected for having an unexpected number of outputs") 17 18 // errBadContractParent is returned when a file contract revision is 19 // presented which has a parent id that doesn't match the file contract 20 // which is supposed to be getting revised. 21 errBadContractParent = ErrorCommunication("could not find contract's parent") 22 23 // errBadFileMerkleRoot is returned if the renter incorrectly updates the 24 // file merkle root during a file contract revision. 25 errBadFileMerkleRoot = ErrorCommunication("rejected for bad file merkle root") 26 27 // errBadFileSize is returned if the renter incorrectly download and 28 // changes the file size during a file contract revision. 29 errBadFileSize = ErrorCommunication("rejected for bad file size") 30 31 // errBadModificationIndex is returned if the renter requests a change on a 32 // sector root that is not in the file contract. 33 errBadModificationIndex = ErrorCommunication("renter has made a modification that points to a nonexistent sector") 34 35 // errBadParentID is returned if the renter incorrectly download and 36 // provides the wrong parent id during a file contract revision. 37 errBadParentID = ErrorCommunication("rejected for bad parent id") 38 39 // errBadPayoutUnlockHashes is returned if the renter incorrectly sets the 40 // payout unlock hashes during contract formation. 41 errBadPayoutUnlockHashes = ErrorCommunication("rejected for bad unlock hashes in the payout") 42 43 // errBadRevisionNumber number is returned if the renter incorrectly 44 // download and does not increase the revision number during a file 45 // contract revision. 46 errBadRevisionNumber = ErrorCommunication("rejected for bad revision number") 47 48 // errBadSectorSize is returned if the renter provides a sector to be 49 // inserted that is the wrong size. 50 errBadSectorSize = ErrorCommunication("renter has provided an incorrectly sized sector") 51 52 // errBadUnlockConditions is returned if the renter incorrectly download 53 // and does not provide the right unlock conditions in the payment 54 // revision. 55 errBadUnlockConditions = ErrorCommunication("rejected for bad unlock conditions") 56 57 // errBadUnlockHash is returned if the renter incorrectly updates the 58 // unlock hash during a file contract revision. 59 errBadUnlockHash = ErrorCommunication("rejected for bad new unlock hash") 60 61 // errBadWindowEnd is returned if the renter incorrectly download and 62 // changes the window end during a file contract revision. 63 errBadWindowEnd = ErrorCommunication("rejected for bad new window end") 64 65 // errBadWindowStart is returned if the renter incorrectly updates the 66 // window start during a file contract revision. 67 errBadWindowStart = ErrorCommunication("rejected for bad new window start") 68 69 // errEarlyWindow is returned if the file contract provided by the renter 70 // has a storage proof window that is starting too near in the future. 71 errEarlyWindow = ErrorCommunication("rejected for a window that starts too soon") 72 73 // errEmptyObject is returned if the renter sends an empty or nil object 74 // unexpectedly. 75 errEmptyObject = ErrorCommunication("renter has unexpectedly send an empty/nil object") 76 77 // errHighRenterMissedOutput is returned if the renter incorrectly download 78 // and deducts an insufficient amount from the renter missed outputs during 79 // a file contract revision. 80 errHighRenterMissedOutput = ErrorCommunication("rejected for high paying renter missed output") 81 82 // errHighRenterValidOutput is returned if the renter incorrectly download 83 // and deducts an insufficient amount from the renter valid outputs during 84 // a file contract revision. 85 errHighRenterValidOutput = ErrorCommunication("rejected for high paying renter valid output") 86 87 // errIllegalOffsetAndLength is returned if the renter tries perform a 88 // modify operation that uses a troublesome combination of offset and 89 // length. 90 errIllegalOffsetAndLength = ErrorCommunication("renter is trying to do a modify with an illegal offset and length") 91 92 // errLargeSector is returned if the renter sends a RevisionAction that has 93 // data which creates a sector that is larger than what the host uses. 94 errLargeSector = ErrorCommunication("renter has sent a sector that exceeds the host's sector size") 95 96 // errLateRevision is returned if the renter is attempting to revise a 97 // revision after the revision deadline. The host needs time to submit the 98 // final revision to the blockchain to guarantee payment, and therefore 99 // will not accept revisions once the window start is too close. 100 errLateRevision = ErrorCommunication("renter is requesting revision after the revision deadline") 101 102 // errLongDuration is returned if the renter proposes a file contract with 103 // an experation that is too far into the future according to the host's 104 // settings. 105 errLongDuration = ErrorCommunication("renter proposed a file contract with a too-long duration") 106 107 // errLowHostMissedOutput is returned if the renter incorrectly updates the 108 // host missed proof output during a file contract revision. 109 errLowHostMissedOutput = ErrorCommunication("rejected for low paying host missed output") 110 111 // errLowHostValidOutput is returned if the renter incorrectly updates the 112 // host valid proof output during a file contract revision. 113 errLowHostValidOutput = ErrorCommunication("rejected for low paying host valid output") 114 115 // errLowTransactionFees is returned if the renter provides a transaction 116 // that the host does not feel is able to make it onto the blockchain. 117 errLowTransactionFees = ErrorCommunication("rejected for including too few transaction fees") 118 119 // errLowVoidOutput is returned if the renter has not allocated enough 120 // funds to the void output. 121 errLowVoidOutput = ErrorCommunication("rejected for low value void output") 122 123 // errMismatchedHostPayouts is returned if the renter incorrectly sets the 124 // host valid and missed payouts to different values during contract 125 // formation. 126 errMismatchedHostPayouts = ErrorCommunication("rejected because host valid and missed payouts are not the same value") 127 128 // errSmallWindow is returned if the renter suggests a storage proof window 129 // that is too small. 130 errSmallWindow = ErrorCommunication("rejected for small window size") 131 132 // errUnknownModification is returned if the host receives a modification 133 // action from the renter that it does not understand. 134 errUnknownModification = ErrorCommunication("renter is attempting an action that the host does not understand") 135 ) 136 137 // createRevisionSignature creates a signature for a file contract revision 138 // that signs on the file contract revision. The renter should have already 139 // provided the signature. createRevisionSignature will check to make sure that 140 // the renter's signature is valid. 141 func createRevisionSignature(fcr types.FileContractRevision, renterSig types.TransactionSignature, secretKey crypto.SecretKey, blockHeight types.BlockHeight) (types.Transaction, error) { 142 hostSig := types.TransactionSignature{ 143 ParentID: crypto.Hash(fcr.ParentID), 144 PublicKeyIndex: 1, 145 CoveredFields: types.CoveredFields{ 146 FileContractRevisions: []uint64{0}, 147 }, 148 } 149 txn := types.Transaction{ 150 FileContractRevisions: []types.FileContractRevision{fcr}, 151 TransactionSignatures: []types.TransactionSignature{renterSig, hostSig}, 152 } 153 sigHash := txn.SigHash(1) 154 encodedSig := crypto.SignHash(sigHash, secretKey) 155 txn.TransactionSignatures[1].Signature = encodedSig[:] 156 err := modules.VerifyFileContractRevisionTransactionSignatures(fcr, txn.TransactionSignatures, blockHeight) 157 if err != nil { 158 return types.Transaction{}, err 159 } 160 return txn, nil 161 } 162 163 // managedFinalizeContract will take a file contract, add the host's 164 // collateral, and then try submitting the file contract to the transaction 165 // pool. If there is no error, the completed transaction set will be returned 166 // to the caller. 167 func (h *Host) managedFinalizeContract(builder modules.TransactionBuilder, renterPK crypto.PublicKey, renterSignatures []types.TransactionSignature, renterRevisionSignature types.TransactionSignature, initialSectorRoots []crypto.Hash, hostCollateral, hostInitialRevenue, hostInitialRisk types.Currency, settings modules.HostExternalSettings) ([]types.TransactionSignature, types.TransactionSignature, types.FileContractID, error) { 168 for _, sig := range renterSignatures { 169 builder.AddTransactionSignature(sig) 170 } 171 fullTxnSet, err := builder.Sign(true) 172 if err != nil { 173 builder.Drop() 174 return nil, types.TransactionSignature{}, types.FileContractID{}, err 175 } 176 177 // Verify that the signature for the revision from the renter is correct. 178 h.mu.RLock() 179 blockHeight := h.blockHeight 180 hostSPK := h.publicKey 181 hostSK := h.secretKey 182 h.mu.RUnlock() 183 contractTxn := fullTxnSet[len(fullTxnSet)-1] 184 fc := contractTxn.FileContracts[0] 185 noOpRevision := types.FileContractRevision{ 186 ParentID: contractTxn.FileContractID(0), 187 UnlockConditions: types.UnlockConditions{ 188 PublicKeys: []types.SiaPublicKey{ 189 types.Ed25519PublicKey(renterPK), 190 hostSPK, 191 }, 192 SignaturesRequired: 2, 193 }, 194 NewRevisionNumber: fc.RevisionNumber + 1, 195 196 NewFileSize: fc.FileSize, 197 NewFileMerkleRoot: fc.FileMerkleRoot, 198 NewWindowStart: fc.WindowStart, 199 NewWindowEnd: fc.WindowEnd, 200 NewValidProofOutputs: fc.ValidProofOutputs, 201 NewMissedProofOutputs: fc.MissedProofOutputs, 202 NewUnlockHash: fc.UnlockHash, 203 } 204 // createRevisionSignature will also perform validation on the result, 205 // returning an error if the renter provided an incorrect signature. 206 revisionTransaction, err := createRevisionSignature(noOpRevision, renterRevisionSignature, hostSK, blockHeight) 207 if err != nil { 208 return nil, types.TransactionSignature{}, types.FileContractID{}, err 209 } 210 211 // Create and add the storage obligation for this file contract. 212 fullTxn, _ := builder.View() 213 so := storageObligation{ 214 SectorRoots: initialSectorRoots, 215 216 ContractCost: settings.ContractPrice, 217 LockedCollateral: hostCollateral, 218 PotentialStorageRevenue: hostInitialRevenue, 219 RiskedCollateral: hostInitialRisk, 220 221 OriginTransactionSet: fullTxnSet, 222 RevisionTransactionSet: []types.Transaction{revisionTransaction}, 223 } 224 225 // Get a lock on the storage obligation. 226 lockErr := h.managedTryLockStorageObligation(so.id()) 227 if lockErr != nil { 228 build.Critical("failed to get a lock on a brand new storage obligation") 229 return nil, types.TransactionSignature{}, types.FileContractID{}, lockErr 230 } 231 defer func() { 232 if err != nil { 233 h.managedUnlockStorageObligation(so.id()) 234 } 235 }() 236 237 // addStorageObligation will submit the transaction to the transaction 238 // pool, and will only do so if there was not some error in creating the 239 // storage obligation. If the transaction pool returns a consensus 240 // conflict, wait 30 seconds and try again. 241 err = func() error { 242 // Try adding the storage obligation. If there's an error, wait a few 243 // seconds and try again. Eventually time out. It should be noted that 244 // the storage obligation locking is both crappy and incomplete, and 245 // that I'm not sure how this timeout plays with the overall host 246 // timeouts. 247 // 248 // The storage obligation locks should occur at the highest level, not 249 // just when the actual modification is happening. 250 i := 0 251 for { 252 err = h.managedAddStorageObligation(so) 253 if err == nil { 254 return nil 255 } 256 if err != nil && i > 4 { 257 h.log.Println(err) 258 builder.Drop() 259 return err 260 } 261 262 i++ 263 if build.Release == "standard" { 264 time.Sleep(time.Second * 15) 265 } 266 } 267 }() 268 if err != nil { 269 return nil, types.TransactionSignature{}, types.FileContractID{}, err 270 } 271 272 // Get the host's transaction signatures from the builder. 273 var hostTxnSignatures []types.TransactionSignature 274 _, _, _, txnSigIndices := builder.ViewAdded() 275 for _, sigIndex := range txnSigIndices { 276 hostTxnSignatures = append(hostTxnSignatures, fullTxn.TransactionSignatures[sigIndex]) 277 } 278 return hostTxnSignatures, revisionTransaction.TransactionSignatures[1], so.id(), nil 279 }