github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/renew.go (about) 1 package proto 2 3 import ( 4 "net" 5 6 "SiaPrime/crypto" 7 "SiaPrime/encoding" 8 "SiaPrime/modules" 9 "SiaPrime/types" 10 11 "gitlab.com/NebulousLabs/errors" 12 ) 13 14 // Renew negotiates a new contract for data already stored with a host, and 15 // submits the new contract transaction to tpool. The new contract is added to 16 // the ContractSet and its metadata is returned. 17 func (cs *ContractSet) Renew(oldContract *SafeContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) { 18 // for convenience 19 contract := oldContract.header 20 21 // Extract vars from params, for convenience. 22 host, funding, startHeight, endHeight, refundAddress := params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress 23 ourSK := contract.SecretKey 24 lastRev := contract.LastRevision() 25 26 // Calculate additional basePrice and baseCollateral. If the contract height 27 // did not increase, basePrice and baseCollateral are zero. 28 var basePrice, baseCollateral types.Currency 29 if endHeight+host.WindowSize > lastRev.NewWindowEnd { 30 timeExtension := uint64((endHeight + host.WindowSize) - lastRev.NewWindowEnd) 31 basePrice = host.StoragePrice.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // cost of data already covered by contract, i.e. lastrevision.Filesize 32 baseCollateral = host.Collateral.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // same but collateral 33 } 34 35 // Calculate the anticipated transaction fee. 36 _, maxFee := tpool.FeeEstimation() 37 txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize) 38 39 // Calculate the payouts for the renter, host, and whole contract. 40 period := endHeight - startHeight 41 expectedStorage := modules.DefaultUsageGuideLines.ExpectedStorage 42 renterPayout, hostPayout, hostCollateral, err := modules.RenterPayoutsPreTax(host, funding, txnFee, basePrice, period, expectedStorage) 43 if err != nil { 44 return modules.RenterContract{}, err 45 } 46 totalPayout := renterPayout.Add(hostPayout) 47 48 // check for negative currency 49 if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 { 50 return modules.RenterContract{}, errors.New("insufficient funds to pay both siafund fee and also host payout") 51 } else if hostCollateral.Cmp(baseCollateral) < 0 { 52 return modules.RenterContract{}, errors.New("new collateral smaller than base collateral") 53 } 54 55 // create file contract 56 fc := types.FileContract{ 57 FileSize: lastRev.NewFileSize, 58 FileMerkleRoot: lastRev.NewFileMerkleRoot, 59 WindowStart: endHeight, 60 WindowEnd: endHeight + host.WindowSize, 61 Payout: totalPayout, 62 UnlockHash: lastRev.NewUnlockHash, 63 RevisionNumber: 0, 64 ValidProofOutputs: []types.SiacoinOutput{ 65 // renter 66 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, 67 // host 68 {Value: hostPayout, UnlockHash: host.UnlockHash}, 69 }, 70 MissedProofOutputs: []types.SiacoinOutput{ 71 // renter 72 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, 73 // host gets its unused collateral back, plus the contract price 74 {Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash}, 75 // void gets the spent storage fees, plus the collateral being risked 76 {Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}}, 77 }, 78 } 79 80 // build transaction containing fc 81 err = txnBuilder.FundSiacoins(funding) 82 if err != nil { 83 return modules.RenterContract{}, err 84 } 85 txnBuilder.AddFileContract(fc) 86 // add miner fee 87 txnBuilder.AddMinerFee(txnFee) 88 89 // Create initial transaction set. 90 txn, parentTxns := txnBuilder.View() 91 unconfirmedParents, err := txnBuilder.UnconfirmedParents() 92 if err != nil { 93 return modules.RenterContract{}, err 94 } 95 txnSet := append(unconfirmedParents, append(parentTxns, txn)...) 96 97 // Increase Successful/Failed interactions accordingly 98 defer func() { 99 // A revision mismatch might not be the host's fault. 100 if err != nil && !IsRevisionMismatch(err) { 101 hdb.IncrementFailedInteractions(contract.HostPublicKey()) 102 err = errors.Extend(err, modules.ErrHostFault) 103 } else if err == nil { 104 hdb.IncrementSuccessfulInteractions(contract.HostPublicKey()) 105 } 106 }() 107 108 // initiate connection 109 dialer := &net.Dialer{ 110 Cancel: cancel, 111 Timeout: connTimeout, 112 } 113 conn, err := dialer.Dial("tcp", string(host.NetAddress)) 114 if err != nil { 115 return modules.RenterContract{}, err 116 } 117 defer func() { _ = conn.Close() }() 118 119 // allot time for sending RPC ID, verifyRecentRevision, and verifySettings 120 extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime) 121 if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil { 122 return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error()) 123 } 124 // verify that both parties are renewing the same contract 125 err = verifyRecentRevision(conn, oldContract, host.Version) 126 if err != nil { 127 return modules.RenterContract{}, err 128 } 129 130 // verify the host's settings and confirm its identity 131 host, err = verifySettings(conn, host) 132 if err != nil { 133 return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error()) 134 } 135 if !host.AcceptingContracts { 136 return modules.RenterContract{}, errors.New("host is not accepting contracts") 137 } 138 139 // allot time for negotiation 140 extendDeadline(conn, modules.NegotiateRenewContractTime) 141 142 // send acceptance, txn signed by us, and pubkey 143 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 144 return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error()) 145 } 146 if err = encoding.WriteObject(conn, txnSet); err != nil { 147 return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error()) 148 } 149 if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil { 150 return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error()) 151 } 152 153 // read acceptance and txn signed by host 154 if err = modules.ReadNegotiationAcceptance(conn); err != nil { 155 return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error()) 156 } 157 // host now sends any new parent transactions, inputs and outputs that 158 // were added to the transaction 159 var newParents []types.Transaction 160 var newInputs []types.SiacoinInput 161 var newOutputs []types.SiacoinOutput 162 if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil { 163 return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error()) 164 } 165 if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil { 166 return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error()) 167 } 168 if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil { 169 return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error()) 170 } 171 172 // merge txnAdditions with txnSet 173 txnBuilder.AddParents(newParents) 174 for _, input := range newInputs { 175 txnBuilder.AddSiacoinInput(input) 176 } 177 for _, output := range newOutputs { 178 txnBuilder.AddSiacoinOutput(output) 179 } 180 181 // sign the txn 182 signedTxnSet, err := txnBuilder.Sign(true) 183 if err != nil { 184 return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error())) 185 } 186 187 // calculate signatures added by the transaction builder 188 var addedSignatures []types.TransactionSignature 189 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 190 for _, i := range addedSignatureIndices { 191 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 192 } 193 194 // create initial (no-op) revision, transaction, and signature 195 initRevision := types.FileContractRevision{ 196 ParentID: signedTxnSet[len(signedTxnSet)-1].FileContractID(0), 197 UnlockConditions: lastRev.UnlockConditions, 198 NewRevisionNumber: 1, 199 200 NewFileSize: fc.FileSize, 201 NewFileMerkleRoot: fc.FileMerkleRoot, 202 NewWindowStart: fc.WindowStart, 203 NewWindowEnd: fc.WindowEnd, 204 NewValidProofOutputs: fc.ValidProofOutputs, 205 NewMissedProofOutputs: fc.MissedProofOutputs, 206 NewUnlockHash: fc.UnlockHash, 207 } 208 renterRevisionSig := types.TransactionSignature{ 209 ParentID: crypto.Hash(initRevision.ParentID), 210 PublicKeyIndex: 0, 211 CoveredFields: types.CoveredFields{ 212 FileContractRevisions: []uint64{0}, 213 }, 214 } 215 revisionTxn := types.Transaction{ 216 FileContractRevisions: []types.FileContractRevision{initRevision}, 217 TransactionSignatures: []types.TransactionSignature{renterRevisionSig}, 218 } 219 encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK) 220 revisionTxn.TransactionSignatures[0].Signature = encodedSig[:] 221 222 // Send acceptance and signatures 223 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 224 return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error()) 225 } 226 if err = encoding.WriteObject(conn, addedSignatures); err != nil { 227 return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error()) 228 } 229 if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil { 230 return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error()) 231 } 232 233 // Read the host acceptance and signatures. 234 err = modules.ReadNegotiationAcceptance(conn) 235 if err != nil { 236 return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error()) 237 } 238 var hostSigs []types.TransactionSignature 239 if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil { 240 return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error()) 241 } 242 for _, sig := range hostSigs { 243 txnBuilder.AddTransactionSignature(sig) 244 } 245 var hostRevisionSig types.TransactionSignature 246 if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil { 247 return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error()) 248 } 249 revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig) 250 251 // Construct the final transaction. 252 txn, parentTxns = txnBuilder.View() 253 txnSet = append(parentTxns, txn) 254 255 // Submit to blockchain. 256 err = tpool.AcceptTransactionSet(txnSet) 257 if err == modules.ErrDuplicateTransactionSet { 258 // as long as it made it into the transaction pool, we're good 259 err = nil 260 } 261 if err != nil { 262 return modules.RenterContract{}, err 263 } 264 265 // Construct contract header. 266 header := contractHeader{ 267 Transaction: revisionTxn, 268 SecretKey: ourSK, 269 StartHeight: startHeight, 270 TotalCost: funding, 271 ContractFee: host.ContractPrice, 272 TxnFee: txnFee, 273 SiafundFee: types.Tax(startHeight, fc.Payout), 274 StorageSpending: basePrice, 275 Utility: modules.ContractUtility{ 276 GoodForUpload: true, 277 GoodForRenew: true, 278 }, 279 } 280 281 // Get old roots 282 oldRoots, err := oldContract.merkleRoots.merkleRoots() 283 if err != nil { 284 return modules.RenterContract{}, err 285 } 286 287 // Add contract to set. 288 meta, err := cs.managedInsertContract(header, oldRoots) 289 if err != nil { 290 return modules.RenterContract{}, err 291 } 292 return meta, nil 293 }