gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/formcontract.go (about) 1 package proto 2 3 import ( 4 "gitlab.com/NebulousLabs/errors" 5 6 "gitlab.com/SkynetLabs/skyd/build" 7 "gitlab.com/SkynetLabs/skyd/skymodules" 8 "go.sia.tech/siad/crypto" 9 "go.sia.tech/siad/modules" 10 "go.sia.tech/siad/types" 11 "go.sia.tech/siad/types/typesutil" 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 skymodules.ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc skymodules.RenterContract, formationTxnSet []types.Transaction, sweepTxn types.Transaction, sweepParents []types.Transaction, err error) { 18 // Check that the host version is high enough. This should never happen 19 // because hosts with old versions should be filtered / blocked by the 20 // contractor anyway. 21 if build.VersionCmp(params.Host.Version, modules.MinimumSupportedRenterHostProtocolVersion) < 0 { 22 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, ErrBadHostVersion 23 } 24 25 // Extract vars from params, for convenience. 26 allowance, host, funding, startHeight, endHeight, refundAddress := params.Allowance, params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress 27 28 // Calculate the anticipated transaction fee. 29 _, maxFee := tpool.FeeEstimation() 30 txnFee := maxFee.Mul64(skymodules.EstimatedFileContractTransactionSetSize) 31 32 // Calculate the payouts for the renter, host, and whole contract. 33 period := endHeight - startHeight 34 expectedStorage := allowance.ExpectedStorage / allowance.Hosts 35 renterPayout, hostPayout, _, err := skymodules.RenterPayoutsPreTax(host, funding, txnFee, types.ZeroCurrency, types.ZeroCurrency, period, expectedStorage) 36 if err != nil { 37 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 38 } 39 totalPayout := renterPayout.Add(hostPayout) 40 41 // Check for negative currency. 42 if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 { 43 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, errors.New("not enough money to pay both siafund fee and also host payout") 44 } 45 // Fund the transaction. 46 err = txnBuilder.FundSiacoins(funding) 47 if err != nil { 48 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 49 } 50 51 // Make a copy of the transaction builder so far, to be used to by the watchdog 52 // to double spend these inputs in case the contract never appears on chain. 53 sweepBuilder := txnBuilder.Copy() 54 // Add an output that sends all fund back to the refundAddress. 55 // Note that in order to send this transaction, a miner fee will have to be subtracted. 56 output := types.SiacoinOutput{ 57 Value: funding, 58 UnlockHash: refundAddress, 59 } 60 sweepBuilder.AddSiacoinOutput(output) 61 sweepTxn, sweepParents = sweepBuilder.View() 62 63 // Add FileContract identifier. 64 fcTxn, _ := txnBuilder.View() 65 si, hk := skymodules.PrefixedSignedIdentifier(params.RenterSeed, fcTxn, host.PublicKey) 66 _ = txnBuilder.AddArbitraryData(append(si[:], hk[:]...)) 67 // Create our key. 68 ourSK, ourPK := skymodules.GenerateContractKeyPair(params.RenterSeed, fcTxn) 69 // Create unlock conditions. 70 uc := types.UnlockConditions{ 71 PublicKeys: []types.SiaPublicKey{ 72 types.Ed25519PublicKey(ourPK), 73 host.PublicKey, 74 }, 75 SignaturesRequired: 2, 76 } 77 78 // Create file contract. 79 renterPostTaxPayout := types.PostTax(startHeight, totalPayout).Sub(hostPayout) 80 fc := types.FileContract{ 81 FileSize: 0, 82 FileMerkleRoot: crypto.Hash{}, // no proof possible without data 83 WindowStart: endHeight, 84 WindowEnd: endHeight + host.WindowSize, 85 Payout: totalPayout, 86 UnlockHash: uc.UnlockHash(), 87 RevisionNumber: 0, 88 ValidProofOutputs: []types.SiacoinOutput{ 89 // Outputs need to account for tax. 90 {Value: renterPostTaxPayout, UnlockHash: refundAddress}, // This is the renter payout, but with tax applied. 91 // Collateral is returned to host. 92 {Value: hostPayout, UnlockHash: host.UnlockHash}, 93 }, 94 MissedProofOutputs: []types.SiacoinOutput{ 95 // Same as above. 96 {Value: renterPostTaxPayout, UnlockHash: refundAddress}, 97 // Same as above. 98 {Value: hostPayout, UnlockHash: host.UnlockHash}, 99 // Once we start doing revisions, we'll move some coins to the host and some to the void. 100 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 101 }, 102 } 103 104 // Add file contract. 105 txnBuilder.AddFileContract(fc) 106 // Add miner fee. 107 txnBuilder.AddMinerFee(txnFee) 108 109 // Create initial transaction set. 110 txn, parentTxns := txnBuilder.View() 111 unconfirmedParents, err := txnBuilder.UnconfirmedParents() 112 if err != nil { 113 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 114 } 115 txnSet := append(unconfirmedParents, append(parentTxns, txn)...) 116 txnSet = typesutil.MinimumTransactionSet([]types.Transaction{txn}, txnSet) 117 118 // Increase Successful/Failed interactions accordingly 119 defer func() { 120 if err != nil { 121 hdb.IncrementFailedInteractions(host.PublicKey) 122 err = errors.Extend(err, skymodules.ErrHostFault) 123 } else { 124 hdb.IncrementSuccessfulInteractions(host.PublicKey) 125 } 126 }() 127 128 // Initiate protocol. 129 s, err := cs.NewRawSession(host, startHeight, hdb, cancel) 130 if err != nil { 131 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 132 } 133 defer func() { 134 err = errors.Compose(err, s.Close()) 135 }() 136 137 // Send the FormContract request. 138 req := modules.LoopFormContractRequest{ 139 Transactions: txnSet, 140 RenterKey: uc.PublicKeys[0], 141 } 142 if err := s.writeRequest(modules.RPCLoopFormContract, req); err != nil { 143 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 144 } 145 146 // Read the host's response. 147 var resp modules.LoopContractAdditions 148 if err := s.readResponse(&resp, modules.RPCMinLen); err != nil { 149 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 150 } 151 152 // Incorporate host's modifications. 153 txnBuilder.AddParents(resp.Parents) 154 for _, input := range resp.Inputs { 155 txnBuilder.AddSiacoinInput(input) 156 } 157 for _, output := range resp.Outputs { 158 txnBuilder.AddSiacoinOutput(output) 159 } 160 161 // Sign the txn. 162 signedTxnSet, err := txnBuilder.Sign(true) 163 if err != nil { 164 err = errors.New("failed to sign transaction: " + err.Error()) 165 modules.WriteRPCResponse(s.conn, s.aead, nil, err) 166 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 167 } 168 169 // Calculate signatures added by the transaction builder. 170 var addedSignatures []types.TransactionSignature 171 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 172 for _, i := range addedSignatureIndices { 173 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 174 } 175 176 // create initial (no-op) revision, transaction, and signature 177 initRevision := types.FileContractRevision{ 178 ParentID: signedTxnSet[len(signedTxnSet)-1].FileContractID(0), 179 UnlockConditions: uc, 180 NewRevisionNumber: 1, 181 182 NewFileSize: fc.FileSize, 183 NewFileMerkleRoot: fc.FileMerkleRoot, 184 NewWindowStart: fc.WindowStart, 185 NewWindowEnd: fc.WindowEnd, 186 NewValidProofOutputs: fc.ValidProofOutputs, 187 NewMissedProofOutputs: fc.MissedProofOutputs, 188 NewUnlockHash: fc.UnlockHash, 189 } 190 renterRevisionSig := types.TransactionSignature{ 191 ParentID: crypto.Hash(initRevision.ParentID), 192 PublicKeyIndex: 0, 193 CoveredFields: types.CoveredFields{ 194 FileContractRevisions: []uint64{0}, 195 }, 196 } 197 revisionTxn := types.Transaction{ 198 FileContractRevisions: []types.FileContractRevision{initRevision}, 199 TransactionSignatures: []types.TransactionSignature{renterRevisionSig}, 200 } 201 encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK) 202 revisionTxn.TransactionSignatures[0].Signature = encodedSig[:] 203 204 // Send acceptance and signatures. 205 renterSigs := modules.LoopContractSignatures{ 206 ContractSignatures: addedSignatures, 207 RevisionSignature: revisionTxn.TransactionSignatures[0], 208 } 209 if err := modules.WriteRPCResponse(s.conn, s.aead, renterSigs, nil); err != nil { 210 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 211 } 212 213 // Read the host acceptance and signatures. 214 var hostSigs modules.LoopContractSignatures 215 if err := s.readResponse(&hostSigs, modules.RPCMinLen); err != nil { 216 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 217 } 218 for _, sig := range hostSigs.ContractSignatures { 219 txnBuilder.AddTransactionSignature(sig) 220 } 221 revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostSigs.RevisionSignature) 222 223 // Construct the final transaction, and then grab the minimum necessary 224 // final set to submit to the transaction pool. Minimizing the set will 225 // greatly improve the chances of the transaction propagating through an 226 // actively attacked network. 227 txn, parentTxns = txnBuilder.View() 228 minSet := typesutil.MinimumTransactionSet([]types.Transaction{txn}, parentTxns) 229 230 // Submit to blockchain. 231 err = tpool.AcceptTransactionSet(minSet) 232 if errors.Contains(err, modules.ErrDuplicateTransactionSet) { 233 // As long as it made it into the transaction pool, we're good. 234 err = nil 235 } 236 if err != nil { 237 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 238 } 239 240 // Construct contract header. 241 header := contractHeader{ 242 Transaction: revisionTxn, 243 SecretKey: ourSK, 244 StartHeight: startHeight, 245 TotalCost: funding, 246 ContractFee: host.ContractPrice, 247 TxnFee: txnFee, 248 SiafundFee: types.Tax(startHeight, fc.Payout), 249 Utility: skymodules.ContractUtility{ 250 GoodForUpload: true, 251 GoodForRefresh: true, 252 GoodForRenew: true, 253 }, 254 } 255 256 // Add contract to set. 257 meta, err := cs.managedInsertContract(header, nil) // no Merkle roots yet 258 if err != nil { 259 return skymodules.RenterContract{}, nil, types.Transaction{}, nil, err 260 } 261 return meta, minSet, sweepTxn, sweepParents, nil 262 }