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