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