github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/formcontract.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 // FormContract forms a contract with a host and submits the contract 15 // transaction to tpool. The contract is added to the ContractSet and its 16 // metadata is returned. 17 func (cs *ContractSet) FormContract(params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) { 18 // Extract vars from params, for convenience. 19 host, funding, startHeight, endHeight, refundAddress := params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress 20 21 // Create our key. 22 ourSK, ourPK := crypto.GenerateKeyPair() 23 // Create unlock conditions. 24 uc := types.UnlockConditions{ 25 PublicKeys: []types.SiaPublicKey{ 26 types.Ed25519PublicKey(ourPK), 27 host.PublicKey, 28 }, 29 SignaturesRequired: 2, 30 } 31 32 // Calculate the anticipated transaction fee. 33 _, maxFee := tpool.FeeEstimation() 34 txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize) 35 36 // Calculate the payouts for the renter, host, and whole contract. 37 period := endHeight - startHeight 38 expectedStorage := modules.DefaultUsageGuideLines.ExpectedStorage 39 renterPayout, hostPayout, _, err := modules.RenterPayoutsPreTax(host, funding, txnFee, types.ZeroCurrency, period, expectedStorage) 40 if err != nil { 41 return modules.RenterContract{}, err 42 } 43 totalPayout := renterPayout.Add(hostPayout) 44 45 // Check for negative currency. 46 if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 { 47 return modules.RenterContract{}, errors.New("not enough money to pay both siafund fee and also host payout") 48 } 49 // Create file contract. 50 fc := types.FileContract{ 51 FileSize: 0, 52 FileMerkleRoot: crypto.Hash{}, // no proof possible without data 53 WindowStart: endHeight, 54 WindowEnd: endHeight + host.WindowSize, 55 Payout: totalPayout, 56 UnlockHash: uc.UnlockHash(), 57 RevisionNumber: 0, 58 ValidProofOutputs: []types.SiacoinOutput{ 59 // Outputs need to account for tax. 60 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, // This is the renter payout, but with tax applied. 61 // Collateral is returned to host. 62 {Value: hostPayout, UnlockHash: host.UnlockHash}, 63 }, 64 MissedProofOutputs: []types.SiacoinOutput{ 65 // Same as above. 66 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, 67 // Same as above. 68 {Value: hostPayout, UnlockHash: host.UnlockHash}, 69 // Once we start doing revisions, we'll move some coins to the host and some to the void. 70 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 71 }, 72 } 73 74 // Build transaction containing fc, e.g. the File Contract. 75 err = txnBuilder.FundSiacoins(funding) 76 if err != nil { 77 return modules.RenterContract{}, err 78 } 79 txnBuilder.AddFileContract(fc) 80 // Add miner fee. 81 txnBuilder.AddMinerFee(txnFee) 82 83 // Create initial transaction set. 84 txn, parentTxns := txnBuilder.View() 85 unconfirmedParents, err := txnBuilder.UnconfirmedParents() 86 if err != nil { 87 return modules.RenterContract{}, err 88 } 89 txnSet := append(unconfirmedParents, append(parentTxns, txn)...) 90 91 // Increase Successful/Failed interactions accordingly 92 defer func() { 93 if err != nil { 94 hdb.IncrementFailedInteractions(host.PublicKey) 95 err = errors.Extend(err, modules.ErrHostFault) 96 } else { 97 hdb.IncrementSuccessfulInteractions(host.PublicKey) 98 } 99 }() 100 101 // Initiate connection. 102 dialer := &net.Dialer{ 103 Cancel: cancel, 104 Timeout: connTimeout, 105 } 106 conn, err := dialer.Dial("tcp", string(host.NetAddress)) 107 if err != nil { 108 return modules.RenterContract{}, err 109 } 110 defer func() { _ = conn.Close() }() 111 112 // Allot time for sending RPC ID + verifySettings. 113 extendDeadline(conn, modules.NegotiateSettingsTime) 114 if err = encoding.WriteObject(conn, modules.RPCFormContract); err != nil { 115 return modules.RenterContract{}, err 116 } 117 118 // Verify the host's settings and confirm its identity. 119 host, err = verifySettings(conn, host) 120 if err != nil { 121 return modules.RenterContract{}, err 122 } 123 if !host.AcceptingContracts { 124 return modules.RenterContract{}, errors.New("host is not accepting contracts") 125 } 126 127 // Allot time for negotiation. 128 extendDeadline(conn, modules.NegotiateFileContractTime) 129 130 // Send acceptance, txn signed by us, and pubkey. 131 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 132 return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error()) 133 } 134 if err = encoding.WriteObject(conn, txnSet); err != nil { 135 return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error()) 136 } 137 if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil { 138 return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error()) 139 } 140 141 // Read acceptance and txn signed by host. 142 if err = modules.ReadNegotiationAcceptance(conn); err != nil { 143 return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error()) 144 } 145 // Host now sends any new parent transactions, inputs and outputs that 146 // were added to the transaction. 147 var newParents []types.Transaction 148 var newInputs []types.SiacoinInput 149 var newOutputs []types.SiacoinOutput 150 if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil { 151 return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error()) 152 } 153 if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil { 154 return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error()) 155 } 156 if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil { 157 return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error()) 158 } 159 160 // Merge txnAdditions with txnSet. 161 txnBuilder.AddParents(newParents) 162 for _, input := range newInputs { 163 txnBuilder.AddSiacoinInput(input) 164 } 165 for _, output := range newOutputs { 166 txnBuilder.AddSiacoinOutput(output) 167 } 168 169 // Sign the txn. 170 signedTxnSet, err := txnBuilder.Sign(true) 171 if err != nil { 172 return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error())) 173 } 174 175 // Calculate signatures added by the transaction builder. 176 var addedSignatures []types.TransactionSignature 177 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 178 for _, i := range addedSignatureIndices { 179 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 180 } 181 182 // create initial (no-op) revision, transaction, and signature 183 initRevision := types.FileContractRevision{ 184 ParentID: signedTxnSet[len(signedTxnSet)-1].FileContractID(0), 185 UnlockConditions: uc, 186 NewRevisionNumber: 1, 187 188 NewFileSize: fc.FileSize, 189 NewFileMerkleRoot: fc.FileMerkleRoot, 190 NewWindowStart: fc.WindowStart, 191 NewWindowEnd: fc.WindowEnd, 192 NewValidProofOutputs: fc.ValidProofOutputs, 193 NewMissedProofOutputs: fc.MissedProofOutputs, 194 NewUnlockHash: fc.UnlockHash, 195 } 196 renterRevisionSig := types.TransactionSignature{ 197 ParentID: crypto.Hash(initRevision.ParentID), 198 PublicKeyIndex: 0, 199 CoveredFields: types.CoveredFields{ 200 FileContractRevisions: []uint64{0}, 201 }, 202 } 203 revisionTxn := types.Transaction{ 204 FileContractRevisions: []types.FileContractRevision{initRevision}, 205 TransactionSignatures: []types.TransactionSignature{renterRevisionSig}, 206 } 207 encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK) 208 revisionTxn.TransactionSignatures[0].Signature = encodedSig[:] 209 210 // Send acceptance and signatures. 211 if err = modules.WriteNegotiationAcceptance(conn); err != nil { 212 return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error()) 213 } 214 if err = encoding.WriteObject(conn, addedSignatures); err != nil { 215 return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error()) 216 } 217 if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil { 218 return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error()) 219 } 220 221 // Read the host acceptance and signatures. 222 err = modules.ReadNegotiationAcceptance(conn) 223 if err != nil { 224 return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error()) 225 } 226 var hostSigs []types.TransactionSignature 227 if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil { 228 return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error()) 229 } 230 for _, sig := range hostSigs { 231 txnBuilder.AddTransactionSignature(sig) 232 } 233 var hostRevisionSig types.TransactionSignature 234 if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil { 235 return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error()) 236 } 237 revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig) 238 239 // Construct the final transaction. 240 txn, parentTxns = txnBuilder.View() 241 txnSet = append(parentTxns, txn) 242 243 // Submit to blockchain. 244 err = tpool.AcceptTransactionSet(txnSet) 245 if err == modules.ErrDuplicateTransactionSet { 246 // As long as it made it into the transaction pool, we're good. 247 err = nil 248 } 249 if err != nil { 250 return modules.RenterContract{}, err 251 } 252 253 // Construct contract header. 254 header := contractHeader{ 255 Transaction: revisionTxn, 256 SecretKey: ourSK, 257 StartHeight: startHeight, 258 TotalCost: funding, 259 ContractFee: host.ContractPrice, 260 TxnFee: txnFee, 261 SiafundFee: types.Tax(startHeight, fc.Payout), 262 Utility: modules.ContractUtility{ 263 GoodForUpload: true, 264 GoodForRenew: true, 265 }, 266 } 267 268 // Add contract to set. 269 meta, err := cs.managedInsertContract(header, nil) // no Merkle roots yet 270 if err != nil { 271 return modules.RenterContract{}, err 272 } 273 return meta, nil 274 }