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