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