github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/proto/renew.go (about) 1 package proto 2 3 import ( 4 "errors" 5 "net" 6 "time" 7 8 "github.com/NebulousLabs/Sia/crypto" 9 "github.com/NebulousLabs/Sia/encoding" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/types" 12 ) 13 14 // Renew negotiates a new contract for data already stored with a host, and 15 // submits the new contract transaction to tpool. 16 func Renew(contract modules.RenterContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool) (modules.RenterContract, error) { 17 // extract vars from params, for convenience 18 host, filesize, startHeight, endHeight, refundAddress := params.Host, params.Filesize, params.StartHeight, params.EndHeight, params.RefundAddress 19 ourSK := contract.SecretKey 20 21 // calculate cost to renter and cost to host 22 storageAllocation := host.StoragePrice.Mul64(filesize).Mul64(uint64(endHeight - startHeight)) 23 hostCollateral := host.Collateral.Mul64(filesize).Mul64(uint64(endHeight - startHeight)) 24 if hostCollateral.Cmp(host.MaxCollateral) > 0 { 25 // TODO: if we have to cap the collateral, it probably means we shouldn't be using this host 26 // (ok within a factor of 2) 27 hostCollateral = host.MaxCollateral 28 } 29 30 timeExtension := uint64((endHeight + host.WindowSize) - contract.LastRevision.NewWindowEnd) 31 basePrice := host.StoragePrice.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension) // cost of data already covered by contract, i.e. lastrevision.Filesize 32 baseCollateral := host.Collateral.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension) // same but collateral 33 34 hostPayout := hostCollateral.Add(host.ContractPrice).Add(basePrice) 35 36 payout := storageAllocation.Add(hostCollateral.Add(host.ContractPrice)).Mul64(10406).Div64(10000) // renter covers siafund fee 37 renterCost := payout.Sub(hostCollateral) 38 39 // create file contract 40 fc := types.FileContract{ 41 FileSize: contract.LastRevision.NewFileSize, 42 FileMerkleRoot: contract.LastRevision.NewFileMerkleRoot, 43 WindowStart: endHeight, 44 WindowEnd: endHeight + host.WindowSize, 45 Payout: payout, 46 UnlockHash: contract.LastRevision.NewUnlockHash, 47 RevisionNumber: 0, 48 ValidProofOutputs: []types.SiacoinOutput{ 49 // renter 50 {Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress}, 51 // host 52 {Value: hostPayout, UnlockHash: host.UnlockHash}, 53 }, 54 MissedProofOutputs: []types.SiacoinOutput{ 55 // renter 56 {Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress}, 57 // host gets its unused collateral back, plus the contract price 58 {Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash}, 59 // void gets the spent storage fees, plus the collateral being risked 60 {Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}}, 61 }, 62 } 63 64 // calculate transaction fee 65 _, maxFee := tpool.FeeEstimation() 66 fee := maxFee.Mul64(estTxnSize) 67 68 // build transaction containing fc 69 err := txnBuilder.FundSiacoins(renterCost.Add(fee)) 70 if err != nil { 71 return modules.RenterContract{}, err 72 } 73 txnBuilder.AddFileContract(fc) 74 75 // add miner fee 76 txnBuilder.AddMinerFee(fee) 77 78 // create initial transaction set 79 txn, parentTxns := txnBuilder.View() 80 txnSet := append(parentTxns, txn) 81 82 // initiate connection 83 conn, err := net.DialTimeout("tcp", string(host.NetAddress), 15*time.Second) 84 if err != nil { 85 return modules.RenterContract{}, err 86 } 87 defer func() { _ = conn.Close() }() 88 89 // allot time for sending RPC ID, verifyRecentRevision, and verifySettings 90 extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime) 91 if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil { 92 return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error()) 93 } 94 // verify that both parties are renewing the same contract 95 if err = verifyRecentRevision(conn, contract); err != nil { 96 return modules.RenterContract{}, errors.New("revision exchange failed: " + err.Error()) 97 } 98 // verify the host's settings and confirm its identity 99 host, err = verifySettings(conn, host) 100 if err != nil { 101 return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error()) 102 } 103 if !host.AcceptingContracts { 104 return modules.RenterContract{}, errors.New("host is not accepting contracts") 105 } 106 107 // allot time for negotiation 108 extendDeadline(conn, modules.NegotiateRenewContractTime) 109 110 // send acceptance, txn signed by us, and pubkey 111 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 112 return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error()) 113 } 114 if err = encoding.WriteObject(conn, txnSet); err != nil { 115 return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error()) 116 } 117 if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil { 118 return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error()) 119 } 120 121 // read acceptance and txn signed by host 122 if err = modules.ReadNegotiationAcceptance(conn); err != nil { 123 return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error()) 124 } 125 // host now sends any new parent transactions, inputs and outputs that 126 // were added to the transaction 127 var newParents []types.Transaction 128 var newInputs []types.SiacoinInput 129 var newOutputs []types.SiacoinOutput 130 if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil { 131 return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error()) 132 } 133 if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil { 134 return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error()) 135 } 136 if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil { 137 return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error()) 138 } 139 140 // merge txnAdditions with txnSet 141 txnBuilder.AddParents(newParents) 142 for _, input := range newInputs { 143 txnBuilder.AddSiacoinInput(input) 144 } 145 for _, output := range newOutputs { 146 txnBuilder.AddSiacoinOutput(output) 147 } 148 149 // sign the txn 150 signedTxnSet, err := txnBuilder.Sign(true) 151 if err != nil { 152 return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error())) 153 } 154 155 // calculate signatures added by the transaction builder 156 var addedSignatures []types.TransactionSignature 157 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 158 for _, i := range addedSignatureIndices { 159 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 160 } 161 162 // create initial (no-op) revision, transaction, and signature 163 initRevision := types.FileContractRevision{ 164 ParentID: signedTxnSet[len(signedTxnSet)-1].FileContractID(0), 165 UnlockConditions: contract.LastRevision.UnlockConditions, 166 NewRevisionNumber: 1, 167 168 NewFileSize: fc.FileSize, 169 NewFileMerkleRoot: fc.FileMerkleRoot, 170 NewWindowStart: fc.WindowStart, 171 NewWindowEnd: fc.WindowEnd, 172 NewValidProofOutputs: fc.ValidProofOutputs, 173 NewMissedProofOutputs: fc.MissedProofOutputs, 174 NewUnlockHash: fc.UnlockHash, 175 } 176 renterRevisionSig := types.TransactionSignature{ 177 ParentID: crypto.Hash(initRevision.ParentID), 178 PublicKeyIndex: 0, 179 CoveredFields: types.CoveredFields{ 180 FileContractRevisions: []uint64{0}, 181 }, 182 } 183 revisionTxn := types.Transaction{ 184 FileContractRevisions: []types.FileContractRevision{initRevision}, 185 TransactionSignatures: []types.TransactionSignature{renterRevisionSig}, 186 } 187 encodedSig, err := crypto.SignHash(revisionTxn.SigHash(0), ourSK) 188 if err != nil { 189 return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign revision transaction: "+err.Error())) 190 } 191 revisionTxn.TransactionSignatures[0].Signature = encodedSig[:] 192 193 // Send acceptance and signatures 194 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 195 return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error()) 196 } 197 if err = encoding.WriteObject(conn, addedSignatures); err != nil { 198 return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error()) 199 } 200 if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil { 201 return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error()) 202 } 203 204 // Read the host acceptance and signatures. 205 err = modules.ReadNegotiationAcceptance(conn) 206 if err != nil { 207 return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error()) 208 } 209 var hostSigs []types.TransactionSignature 210 if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil { 211 return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error()) 212 } 213 for _, sig := range hostSigs { 214 txnBuilder.AddTransactionSignature(sig) 215 } 216 var hostRevisionSig types.TransactionSignature 217 if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil { 218 return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error()) 219 } 220 revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig) 221 222 // Construct the final transaction. 223 txn, parentTxns = txnBuilder.View() 224 txnSet = append(parentTxns, txn) 225 226 // Submit to blockchain. 227 err = tpool.AcceptTransactionSet(txnSet) 228 if err == modules.ErrDuplicateTransactionSet { 229 // as long as it made it into the transaction pool, we're good 230 err = nil 231 } 232 if err != nil { 233 return modules.RenterContract{}, err 234 } 235 236 // calculate contract ID 237 fcid := txn.FileContractID(0) 238 239 return modules.RenterContract{ 240 FileContract: fc, 241 ID: fcid, 242 LastRevision: initRevision, 243 LastRevisionTxn: revisionTxn, 244 NetAddress: host.NetAddress, 245 SecretKey: ourSK, 246 }, nil 247 }