gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/proto/formcontract.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 const ( 14 // estTxnSize is the estimated size of an encoded file contract 15 // transaction set. 16 estTxnSize = 2048 17 ) 18 19 // FormContract forms a contract with a host and submits the contract 20 // transaction to tpool. The contract is added to the ContractSet and its 21 // metadata is returned. 22 func (cs *ContractSet) FormContract(params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (modules.RenterContract, error) { 23 // Extract vars from params, for convenience. 24 host, funding, startHeight, endHeight, refundAddress := params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress 25 26 // Create our key. 27 ourSK, ourPK := crypto.GenerateKeyPair() 28 // Create unlock conditions. 29 uc := types.UnlockConditions{ 30 PublicKeys: []types.SiaPublicKey{ 31 types.Ed25519PublicKey(ourPK), 32 host.PublicKey, 33 }, 34 SignaturesRequired: 2, 35 } 36 37 // Calculate the anticipated transaction fee. 38 _, maxFee := tpool.FeeEstimation() 39 txnFee := maxFee.Mul64(estTxnSize) 40 41 // Underflow check. 42 if funding.Cmp(host.ContractPrice.Add(txnFee)) <= 0 { 43 return modules.RenterContract{}, errors.New("insufficient funds to cover contract fee and transaction fee during contract formation") 44 } 45 // Divide by zero check. 46 if host.StoragePrice.IsZero() { 47 host.StoragePrice = types.NewCurrency64(1) 48 } 49 50 // Calculate the payouts for the renter, host, and whole contract. 51 renterPayout := funding.Sub(host.ContractPrice).Sub(txnFee) // renter payout is pre-tax 52 maxStorageSize := renterPayout.Div(host.StoragePrice) 53 hostCollateral := maxStorageSize.Mul(host.Collateral) 54 if hostCollateral.Cmp(host.MaxCollateral) > 0 { 55 hostCollateral = host.MaxCollateral 56 } 57 // Calculate the initial host payout. 58 hostPayout := hostCollateral.Add(host.ContractPrice) 59 totalPayout := renterPayout.Add(hostPayout) 60 61 // Check for negative currency. 62 if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 { 63 return modules.RenterContract{}, errors.New("not enough money to pay both siafund fee and also host payout") 64 } 65 // Create file contract. 66 fc := types.FileContract{ 67 FileSize: 0, 68 FileMerkleRoot: crypto.Hash{}, // no proof possible without data 69 WindowStart: endHeight, 70 WindowEnd: endHeight + host.WindowSize, 71 Payout: totalPayout, 72 UnlockHash: uc.UnlockHash(), 73 RevisionNumber: 0, 74 ValidProofOutputs: []types.SiacoinOutput{ 75 // Outputs need to account for tax. 76 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, // This is the renter payout, but with tax applied. 77 // Collateral is returned to host. 78 {Value: hostPayout, UnlockHash: host.UnlockHash}, 79 }, 80 MissedProofOutputs: []types.SiacoinOutput{ 81 // Same as above. 82 {Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, 83 // Same as above. 84 {Value: hostPayout, UnlockHash: host.UnlockHash}, 85 // Once we start doing revisions, we'll move some coins to the host and some to the void. 86 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 87 }, 88 } 89 90 // Build transaction containing fc, e.g. the File Contract. 91 err := txnBuilder.FundSiacoins(funding) 92 if err != nil { 93 return modules.RenterContract{}, err 94 } 95 txnBuilder.AddFileContract(fc) 96 // Add miner fee. 97 txnBuilder.AddMinerFee(txnFee) 98 99 // Create initial transaction set. 100 txn, parentTxns := txnBuilder.View() 101 txnSet := append(parentTxns, txn) 102 103 // Increase Successful/Failed interactions accordingly 104 defer func() { 105 if err != nil { 106 hdb.IncrementFailedInteractions(host.PublicKey) 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 } 274 275 // Add contract to set. 276 meta, err := cs.managedInsertContract(header, nil) // no Merkle roots yet 277 if err != nil { 278 return modules.RenterContract{}, err 279 } 280 return meta, nil 281 }