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