gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/renew.go (about) 1 package proto 2 3 import ( 4 "math" 5 "net" 6 7 "gitlab.com/NebulousLabs/errors" 8 9 "gitlab.com/SkynetLabs/skyd/build" 10 "gitlab.com/SkynetLabs/skyd/skymodules" 11 "go.sia.tech/siad/crypto" 12 "go.sia.tech/siad/modules" 13 "go.sia.tech/siad/types" 14 "go.sia.tech/siad/types/typesutil" 15 ) 16 17 // FileContractTxnEstimateMultiplier is a multiplier for the estimation of the 18 // size of a renew txn including a file contract and revision. 19 const FileContractTxnEstimateMultiplier = 3 20 21 // Renew negotiates a new contract for data already stored with a host, and 22 // submits the new contract transaction to tpool. The new contract is added to 23 // the ContractSet and its metadata is returned. 24 func (cs *ContractSet) Renew(oldContract *SafeContract, params skymodules.ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc skymodules.RenterContract, formationTxnSet []types.Transaction, err error) { 25 // Check that the host version is high enough as belt-and-suspenders. This 26 // should never happen, because hosts with old versions should be blacklisted 27 // by the contractor. 28 if build.VersionCmp(params.Host.Version, "1.4.4") < 0 { 29 return skymodules.RenterContract{}, nil, ErrBadHostVersion 30 } 31 return cs.managedNewRenewAndClear(oldContract, params, txnBuilder, tpool, hdb, cancel) 32 } 33 34 // managedNewRenewAndClear uses the new RPC to renew a contract, creating a new 35 // contract that is identical to the old one, and then clears the old one to be 36 // empty. 37 func (cs *ContractSet) managedNewRenewAndClear(oldContract *SafeContract, params skymodules.ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc skymodules.RenterContract, formationTxnSet []types.Transaction, err error) { 38 // for convenience 39 contract := oldContract.header 40 41 // Extract vars from params, for convenience. 42 fcTxn, _ := txnBuilder.View() 43 host, funding, startHeight, endHeight := params.Host, params.Funding, params.StartHeight, params.EndHeight 44 ourSKOld := contract.SecretKey 45 ourSKNew, ourPKNew := skymodules.GenerateContractKeyPair(params.RenterSeed, fcTxn) 46 lastRev := contract.LastRevision() 47 48 // Calculate the anticipated transaction fee. 49 _, maxFee := tpool.FeeEstimation() 50 txnFee := maxFee.Mul64(skymodules.EstimatedFileContractTransactionSetSize) 51 52 // Calculate the base cost. 53 basePrice, baseCollateral := rhp2BaseCosts(lastRev, host, endHeight) 54 55 // Create file contract and add it together with the fee to the builder. 56 uc := createFileContractUnlockConds(host.PublicKey, ourPKNew) 57 uh := uc.UnlockHash() 58 fc, err := createRenewedContract(lastRev, uh, params, txnFee, basePrice, baseCollateral, tpool) 59 if err != nil { 60 return skymodules.RenterContract{}, nil, err 61 } 62 txnBuilder.AddFileContract(fc) 63 txnBuilder.AddMinerFee(txnFee) 64 65 // Add FileContract identifier. 66 si, hk := skymodules.PrefixedSignedIdentifier(params.RenterSeed, fcTxn, host.PublicKey) 67 _ = txnBuilder.AddArbitraryData(append(si[:], hk[:]...)) 68 69 // Create initial transaction set. 70 txnSet, err := prepareTransactionSet(txnBuilder) 71 if err != nil { 72 return skymodules.RenterContract{}, nil, err 73 } 74 75 // Increase Successful/Failed interactions accordingly 76 defer func() { 77 // A revision mismatch might not be the host's fault. 78 if err != nil && !IsRevisionMismatch(err) { 79 hdb.IncrementFailedInteractions(contract.HostPublicKey()) 80 err = errors.Extend(err, skymodules.ErrHostFault) 81 } else if err == nil { 82 hdb.IncrementSuccessfulInteractions(contract.HostPublicKey()) 83 } 84 }() 85 86 // Initiate protocol. 87 s, err := cs.NewRawSession(host, startHeight, hdb, cancel) 88 if err != nil { 89 return skymodules.RenterContract{}, nil, err 90 } 91 defer func() { 92 err = errors.Compose(err, s.Close()) 93 }() 94 95 // Lock the contract and resynchronize if necessary 96 rev, sigs, err := s.Lock(contract.ID(), ourSKOld) 97 if err != nil { 98 return skymodules.RenterContract{}, nil, err 99 } else if err := oldContract.managedSyncRevision(rev, sigs); err != nil { 100 return skymodules.RenterContract{}, nil, err 101 } 102 103 // Create the final revision of the old contract. 104 bandwidthCost := host.BaseRPCPrice 105 finalRev, err := prepareFinalRevision(contract, bandwidthCost) 106 if err != nil { 107 return skymodules.RenterContract{}, nil, errors.AddContext(err, "Unable to create final revision") 108 } 109 110 // Create the RenewContract request. 111 req := modules.LoopRenewAndClearContractRequest{ 112 Transactions: txnSet, 113 RenterKey: types.Ed25519PublicKey(ourPKNew), 114 } 115 for _, vpo := range finalRev.NewValidProofOutputs { 116 req.FinalValidProofValues = append(req.FinalValidProofValues, vpo.Value) 117 } 118 for _, mpo := range finalRev.NewMissedProofOutputs { 119 req.FinalMissedProofValues = append(req.FinalMissedProofValues, mpo.Value) 120 } 121 122 // Send the request. 123 if err := s.writeRequest(modules.RPCLoopRenewClearContract, req); err != nil { 124 return skymodules.RenterContract{}, nil, err 125 } 126 127 // Record the changes we are about to make to the contract. 128 walTxn, err := oldContract.managedRecordClearContractIntent(finalRev, bandwidthCost) 129 if err != nil { 130 return skymodules.RenterContract{}, nil, err 131 } 132 133 // Read the host's response. 134 var resp modules.LoopContractAdditions 135 if err := s.readResponse(&resp, modules.RPCMinLen); err != nil { 136 return skymodules.RenterContract{}, nil, err 137 } 138 139 // Incorporate host's modifications. 140 txnBuilder.AddParents(resp.Parents) 141 for _, input := range resp.Inputs { 142 txnBuilder.AddSiacoinInput(input) 143 } 144 for _, output := range resp.Outputs { 145 txnBuilder.AddSiacoinOutput(output) 146 } 147 148 // sign the final revision of the old contract. 149 rev.NewFileMerkleRoot = crypto.Hash{} 150 finalRevTxn := types.Transaction{ 151 FileContractRevisions: []types.FileContractRevision{finalRev}, 152 TransactionSignatures: []types.TransactionSignature{ 153 { 154 ParentID: crypto.Hash(finalRev.ParentID), 155 CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, 156 PublicKeyIndex: 0, // renter key is always first -- see formContract 157 }, 158 { 159 ParentID: crypto.Hash(finalRev.ParentID), 160 PublicKeyIndex: 1, 161 CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, 162 Signature: nil, // to be provided by host 163 }, 164 }, 165 } 166 finalRevSig := crypto.SignHash(finalRevTxn.SigHash(0, s.height), ourSKOld) 167 finalRevTxn.TransactionSignatures[0].Signature = finalRevSig[:] 168 169 // sign the txn 170 signedTxnSet, err := txnBuilder.Sign(true) 171 if err != nil { 172 err = errors.New("failed to sign transaction: " + err.Error()) 173 modules.WriteRPCResponse(s.conn, s.aead, nil, err) 174 return skymodules.RenterContract{}, nil, err 175 } 176 177 // calculate signatures added by the transaction builder 178 var addedSignatures []types.TransactionSignature 179 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 180 for _, i := range addedSignatureIndices { 181 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 182 } 183 184 // create initial (no-op) revision, transaction, and signature 185 revisionTxn := prepareInitRevisionTxn(lastRev, uc, fc, startHeight, ourSKNew, signedTxnSet[len(signedTxnSet)-1].FileContractID(0)) 186 187 // Send acceptance and signatures 188 renterSigs := modules.LoopRenewAndClearContractSignatures{ 189 ContractSignatures: addedSignatures, 190 RevisionSignature: revisionTxn.TransactionSignatures[0], 191 192 FinalRevisionSignature: finalRevSig[:], 193 } 194 if err := modules.WriteRPCResponse(s.conn, s.aead, renterSigs, nil); err != nil { 195 return skymodules.RenterContract{}, nil, err 196 } 197 198 // Read the host acceptance and signatures. 199 var hostSigs modules.LoopRenewAndClearContractSignatures 200 if err := s.readResponse(&hostSigs, modules.RPCMinLen); err != nil { 201 return skymodules.RenterContract{}, nil, err 202 } 203 for _, sig := range hostSigs.ContractSignatures { 204 txnBuilder.AddTransactionSignature(sig) 205 } 206 revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostSigs.RevisionSignature) 207 finalRevTxn.TransactionSignatures[1].Signature = hostSigs.FinalRevisionSignature 208 209 // Construct the final transaction. 210 txnSet, err = prepareTransactionSet(txnBuilder) 211 if err != nil { 212 return skymodules.RenterContract{}, nil, err 213 } 214 215 // Submit to blockchain. 216 err = tpool.AcceptTransactionSet(txnSet) 217 if errors.Contains(err, modules.ErrDuplicateTransactionSet) { 218 // As long as it made it into the transaction pool, we're good. 219 err = nil 220 } 221 if err != nil { 222 return skymodules.RenterContract{}, nil, err 223 } 224 err = tpool.AcceptTransactionSet([]types.Transaction{finalRevTxn}) 225 if errors.Contains(err, modules.ErrDuplicateTransactionSet) { 226 // As long as it made it into the transaction pool, we're good. 227 err = nil 228 } 229 if err != nil { 230 return skymodules.RenterContract{}, nil, err 231 } 232 233 // Construct contract header. 234 header := contractHeader{ 235 Transaction: revisionTxn, 236 SecretKey: ourSKNew, 237 StartHeight: startHeight, 238 TotalCost: funding, 239 ContractFee: host.ContractPrice, 240 TxnFee: txnFee, 241 SiafundFee: types.Tax(startHeight, fc.Payout), 242 StorageSpending: basePrice, 243 Utility: skymodules.ContractUtility{ 244 GoodForUpload: true, 245 GoodForRefresh: true, 246 GoodForRenew: true, 247 }, 248 } 249 250 // Get old roots 251 oldRoots, err := oldContract.merkleRoots.merkleRoots() 252 if err != nil { 253 return skymodules.RenterContract{}, nil, err 254 } 255 256 // Add contract to set. 257 meta, err := cs.managedInsertContract(header, oldRoots) 258 if err != nil { 259 return skymodules.RenterContract{}, nil, err 260 } 261 // Commit changes to old contract. 262 if err := oldContract.managedCommitClearContract(walTxn, finalRevTxn, bandwidthCost); err != nil { 263 return skymodules.RenterContract{}, nil, err 264 } 265 return meta, txnSet, nil 266 } 267 268 // rhp2BaseCosts computes the base costs for renewing a contract. 269 func rhp2BaseCosts(lastRev types.FileContractRevision, host skymodules.HostDBEntry, endHeight types.BlockHeight) (basePrice, baseCollateral types.Currency) { 270 // If the contract height did not increase, basePrice and baseCollateral are 271 // zero. 272 if endHeight+host.WindowSize > lastRev.NewWindowEnd { 273 timeExtension := uint64((endHeight + host.WindowSize) - lastRev.NewWindowEnd) 274 basePrice = host.StoragePrice.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // cost of already uploaded data that needs to be covered by the renewed contract. 275 baseCollateral = host.Collateral.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // same as basePrice. 276 } 277 return 278 } 279 280 // prepareInitRevisionTxn creates the initRevision, places it in a transaction and 281 // adds the signature or the revision to the transaction. 282 func prepareInitRevisionTxn(lastRev types.FileContractRevision, uc types.UnlockConditions, newContract types.FileContract, startHeight types.BlockHeight, renterSK crypto.SecretKey, parentID types.FileContractID) types.Transaction { 283 initRevision := types.FileContractRevision{ 284 ParentID: parentID, 285 UnlockConditions: uc, 286 NewRevisionNumber: 1, 287 288 NewFileSize: newContract.FileSize, 289 NewFileMerkleRoot: newContract.FileMerkleRoot, 290 NewWindowStart: newContract.WindowStart, 291 NewWindowEnd: newContract.WindowEnd, 292 NewValidProofOutputs: newContract.ValidProofOutputs, 293 NewMissedProofOutputs: newContract.MissedProofOutputs, 294 NewUnlockHash: newContract.UnlockHash, 295 } 296 297 renterRevisionSig := types.TransactionSignature{ 298 ParentID: crypto.Hash(initRevision.ParentID), 299 PublicKeyIndex: 0, 300 CoveredFields: types.CoveredFields{ 301 FileContractRevisions: []uint64{0}, 302 }, 303 } 304 305 revisionTxn := types.Transaction{ 306 FileContractRevisions: []types.FileContractRevision{initRevision}, 307 TransactionSignatures: []types.TransactionSignature{renterRevisionSig}, 308 } 309 310 encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), renterSK) 311 revisionTxn.TransactionSignatures[0].Signature = encodedSig[:] 312 return revisionTxn 313 } 314 315 // prepareFinalRevision creates a new revision for a contract which transfers 316 // the given amount of payment, clears the contract and sets the missed outputs 317 // to equal the valid outputs. 318 func prepareFinalRevision(contract contractHeader, payment types.Currency) (types.FileContractRevision, error) { 319 finalRev, err := contract.LastRevision().PaymentRevision(payment) 320 if err != nil { 321 return types.FileContractRevision{}, err 322 } 323 324 // Clear the revision. 325 finalRev.NewFileSize = 0 326 finalRev.NewFileMerkleRoot = crypto.Hash{} 327 finalRev.NewRevisionNumber = math.MaxUint64 328 329 // The valid proof outputs become the missed ones since the host won't need 330 // to provide a storage proof. 331 finalRev.NewMissedProofOutputs = finalRev.NewValidProofOutputs 332 return finalRev, nil 333 } 334 335 // PrepareTransactionSet prepares a transaction set from the given builder to be 336 // sent to the host. It includes all unconfirmed parents and has all 337 // non-essential txns trimmed from it. 338 func prepareTransactionSet(txnBuilder transactionBuilder) ([]types.Transaction, error) { 339 txn, parentTxns := txnBuilder.View() 340 unconfirmedParents, err := txnBuilder.UnconfirmedParents() 341 if err != nil { 342 return nil, err 343 } 344 txnSet := append(unconfirmedParents, parentTxns...) 345 txnSet = typesutil.MinimumTransactionSet([]types.Transaction{txn}, txnSet) 346 return txnSet, nil 347 } 348 349 // createRenewedContract creates a new contract from another contract's last 350 // revision given some additional renewal parameters. 351 func createRenewedContract(lastRev types.FileContractRevision, uh types.UnlockHash, params skymodules.ContractParams, txnFee, basePrice, baseCollateral types.Currency, tpool transactionPool) (types.FileContract, error) { 352 allowance, startHeight, endHeight, host, funding := params.Allowance, params.StartHeight, params.EndHeight, params.Host, params.Funding 353 354 // Calculate the payouts for the renter, host, and whole contract. 355 period := endHeight - startHeight 356 renterPayout, hostPayout, hostCollateral, err := skymodules.RenterPayoutsPreTax(host, funding, txnFee, basePrice, baseCollateral, period, allowance.ExpectedStorage/allowance.Hosts) 357 if err != nil { 358 return types.FileContract{}, err 359 } 360 totalPayout := renterPayout.Add(hostPayout) 361 362 // check for negative currency 363 if hostCollateral.Cmp(baseCollateral) < 0 { 364 baseCollateral = hostCollateral 365 } 366 if types.PostTax(params.StartHeight, totalPayout).Cmp(hostPayout) < 0 { 367 return types.FileContract{}, errors.New("insufficient funds to pay both siafund fee and also host payout") 368 } 369 370 fc := types.FileContract{ 371 FileSize: lastRev.NewFileSize, 372 FileMerkleRoot: lastRev.NewFileMerkleRoot, 373 WindowStart: params.EndHeight, 374 WindowEnd: params.EndHeight + params.Host.WindowSize, 375 Payout: totalPayout, 376 UnlockHash: uh, 377 RevisionNumber: 0, 378 ValidProofOutputs: []types.SiacoinOutput{ 379 // renter 380 {Value: types.PostTax(params.StartHeight, totalPayout).Sub(hostPayout), UnlockHash: params.RefundAddress}, 381 // host 382 {Value: hostPayout, UnlockHash: params.Host.UnlockHash}, 383 }, 384 MissedProofOutputs: []types.SiacoinOutput{ 385 // renter 386 {Value: types.PostTax(params.StartHeight, totalPayout).Sub(hostPayout), UnlockHash: params.RefundAddress}, 387 // host gets its unused collateral back, plus the contract price 388 {Value: hostCollateral.Sub(baseCollateral).Add(params.Host.ContractPrice), UnlockHash: params.Host.UnlockHash}, 389 // void gets the spent storage fees, plus the collateral being risked 390 {Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}}, 391 }, 392 } 393 return fc, nil 394 } 395 396 // RenewContract takes an established connection to a host and renews the 397 // contract with that host. 398 func (cs *ContractSet) RenewContract(conn net.Conn, fcid types.FileContractID, params skymodules.ContractParams, txnBuilder modules.TransactionBuilder, tpool modules.TransactionPool, hdb hostDB, pt *modules.RPCPriceTable) (_ skymodules.RenterContract, _ []types.Transaction, err error) { 399 // Fetch the contract. 400 oldSC, ok := cs.Acquire(fcid) 401 if !ok { 402 return skymodules.RenterContract{}, nil, errors.New("RenewContract: failed to acquire contract to renew") 403 } 404 defer cs.Return(oldSC) 405 oldContract := oldSC.header 406 oldRev := oldContract.LastRevision() 407 408 // Extract vars from params, for convenience. 409 fcTxn, _ := txnBuilder.View() 410 host, funding, startHeight, endHeight := params.Host, params.Funding, params.StartHeight, params.EndHeight 411 ourSKOld := oldContract.SecretKey 412 ourSKNew, ourPKNew := skymodules.GenerateContractKeyPair(params.RenterSeed, fcTxn) 413 414 // Pay the host an insufficient amount. 415 if cs.staticDeps.Disrupt("DefaultRenewSettings") { 416 ptNew := *pt 417 ptNew.WriteLengthCost = ptNew.WriteLengthCost.Sub64(1) 418 pt = &ptNew 419 } 420 421 // RHP3 contains both the contract and final revision. So we double the 422 // estimation. 423 txnFee := pt.TxnFeeMaxRecommended.Mul64(FileContractTxnEstimateMultiplier * skymodules.EstimatedFileContractTransactionSetSize) 424 425 // Calculate the base cost. This includes the RPC cost. 426 basePrice, baseCollateral := skymodules.RenewBaseCosts(oldRev, pt, endHeight) 427 428 // Create the final revision of the old contract. 429 renewCost := types.ZeroCurrency 430 finalRev, err := prepareFinalRevision(oldContract, renewCost) 431 if err != nil { 432 return skymodules.RenterContract{}, nil, errors.AddContext(err, "Unable to create final revision") 433 } 434 435 // Record the changes we are about to make to the contract. 436 walTxn, err := oldSC.managedRecordClearContractIntent(finalRev, renewCost) 437 if err != nil { 438 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to record clear contract intent") 439 } 440 441 // Create the new file contract. 442 uc := createFileContractUnlockConds(host.PublicKey, ourPKNew) 443 uh := uc.UnlockHash() 444 fc, err := createRenewedContract(oldRev, uh, params, txnFee, basePrice, baseCollateral, tpool) 445 if err != nil { 446 return skymodules.RenterContract{}, nil, errors.AddContext(err, "Unable to create new contract") 447 } 448 449 // Add both the new final revision and the new contract to the same 450 // transaction. 451 txnBuilder.AddFileContractRevision(finalRev) 452 txnBuilder.AddFileContract(fc) 453 454 // Add the fee to the transaction. 455 txnBuilder.AddMinerFee(txnFee) 456 457 // Add FileContract identifier. 458 si, hk := skymodules.PrefixedSignedIdentifier(params.RenterSeed, fcTxn, host.PublicKey) 459 _ = txnBuilder.AddArbitraryData(append(si[:], hk[:]...)) 460 461 // Create transaction set. 462 txnSet, err := prepareTransactionSet(txnBuilder) 463 if err != nil { 464 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to prepare txnSet with finalRev and new contract") 465 } 466 467 // Increase Successful/Failed interactions accordingly 468 defer func() { 469 if err != nil { 470 hdb.IncrementFailedInteractions(host.PublicKey) 471 err = errors.Compose(err, skymodules.ErrHostFault) 472 } else if err == nil { 473 hdb.IncrementSuccessfulInteractions(host.PublicKey) 474 } 475 }() 476 477 // Sign the final revision. 478 finalRevRenterSig := types.TransactionSignature{ 479 ParentID: crypto.Hash(finalRev.ParentID), 480 PublicKeyIndex: 0, // renter key is first 481 CoveredFields: types.CoveredFields{ 482 FileContracts: []uint64{0}, 483 FileContractRevisions: []uint64{0}, 484 }, 485 } 486 finalRevTxn, _ := txnBuilder.View() 487 finalRevTxn.TransactionSignatures = append(finalRevTxn.TransactionSignatures, finalRevRenterSig) 488 finalRevRenterSigRaw := crypto.SignHash(finalRevTxn.SigHash(0, pt.HostBlockHeight), ourSKOld) 489 finalRevRenterSig.Signature = finalRevRenterSigRaw[:] 490 // Write the request. 491 err = modules.RPCWrite(conn, modules.RPCRenewContractRequest{ 492 TSet: txnSet, 493 RenterPK: types.Ed25519PublicKey(ourPKNew), 494 FinalRevSig: finalRevRenterSigRaw, 495 }) 496 if err != nil { 497 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to write RPCRenewContractRequest") 498 } 499 500 // Read the response. It contains the host's final revision sig and any 501 // additions it made. 502 var resp modules.RPCRenewContractCollateralResponse 503 err = modules.RPCReadMaxLen(conn, &resp, modules.RenewDecodeMaxLen) 504 if err != nil { 505 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to read RPCRenewContractCollateralResponse") 506 } 507 508 // Incorporate host's modifications. 509 txnBuilder.AddParents(resp.NewParents) 510 for _, input := range resp.NewInputs { 511 txnBuilder.AddSiacoinInput(input) 512 } 513 for _, output := range resp.NewOutputs { 514 txnBuilder.AddSiacoinOutput(output) 515 } 516 517 // Create the host sig for the final revision. 518 finalRevHostSigRaw := resp.FinalRevSig 519 finalRevHostSig := types.TransactionSignature{ 520 ParentID: crypto.Hash(finalRev.ParentID), 521 PublicKeyIndex: 1, 522 CoveredFields: types.CoveredFields{ 523 FileContracts: []uint64{0}, 524 FileContractRevisions: []uint64{0}, 525 }, 526 Signature: finalRevHostSigRaw[:], 527 } 528 529 // Add the revision signatures to the transaction set and sign it. 530 _ = txnBuilder.AddTransactionSignature(finalRevRenterSig) 531 _ = txnBuilder.AddTransactionSignature(finalRevHostSig) 532 signedTxnSet, err := txnBuilder.Sign(true) 533 if err != nil { 534 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to sign transaction set") 535 } 536 537 // Calculate signatures added by the transaction builder 538 var addedSignatures []types.TransactionSignature 539 _, _, _, addedSignatureIndices := txnBuilder.ViewAdded() 540 for _, i := range addedSignatureIndices { 541 addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i]) 542 } 543 544 // Create initial (no-op) revision, transaction, and signature 545 noOpRevTxn := prepareInitRevisionTxn(oldRev, uc, fc, startHeight, ourSKNew, signedTxnSet[len(signedTxnSet)-1].FileContractID(0)) 546 // Send transaction signatures and no-op revision signature to host. 547 err = modules.RPCWrite(conn, modules.RPCRenewContractRenterSignatures{ 548 RenterNoOpRevisionSig: noOpRevTxn.RenterSignature(), 549 RenterTxnSigs: addedSignatures, 550 }) 551 if err != nil { 552 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to send RPCRenewContractRenterSignatures to host") 553 } 554 555 // Read the host's signatures and add them to the transactions. 556 var hostSignatureResp modules.RPCRenewContractHostSignatures 557 err = modules.RPCReadMaxLen(conn, &hostSignatureResp, modules.RenewDecodeMaxLen) 558 if err != nil { 559 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to read RPCRenewContractHostSignatures from host") 560 } 561 for _, sig := range hostSignatureResp.ContractSignatures { 562 _ = txnBuilder.AddTransactionSignature(sig) 563 } 564 noOpRevTxn.TransactionSignatures = append(noOpRevTxn.TransactionSignatures, hostSignatureResp.NoOpRevisionSignature) 565 566 // Construct the final transaction. 567 txnSet, err = prepareTransactionSet(txnBuilder) 568 if err != nil { 569 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to prepare txnSet with finalRev and new contract") 570 } 571 572 // Submit the txn set with the final revision and new contract to the blockchain. 573 err = tpool.AcceptTransactionSet(txnSet) 574 if err == modules.ErrDuplicateTransactionSet { 575 // As long as it made it into the transaction pool, we're good. 576 err = nil 577 } 578 if err != nil { 579 return skymodules.RenterContract{}, nil, errors.AddContext(err, "failed to submit txnSet for renewal to blockchain") 580 } 581 582 // Construct contract header. 583 header := contractHeader{ 584 Transaction: noOpRevTxn, 585 SecretKey: ourSKNew, 586 StartHeight: startHeight, 587 TotalCost: funding, 588 ContractFee: pt.ContractPrice, 589 TxnFee: txnFee, 590 SiafundFee: types.Tax(startHeight, fc.Payout), 591 StorageSpending: basePrice, 592 Utility: skymodules.ContractUtility{ 593 // keep the old contract's gfu utility. We don't want to 594 // accidentally increase the number of gfu contracts 595 // beyond the max. So contracts should only be marked as 596 // gfu by the contract maintenance. 597 GoodForUpload: oldContract.Utility.GoodForUpload, 598 599 // keep the old contract's goodForRenew and 600 // goodForRefresh utilities. We don't know if the 601 // contract got refreshed or renewed but since we 602 // renewed it one of both must the true. 603 GoodForRefresh: oldContract.Utility.GoodForRefresh, 604 GoodForRenew: oldContract.Utility.GoodForRenew, 605 }, 606 } 607 608 // Get old roots 609 oldRoots, err := oldSC.merkleRoots.merkleRoots() 610 if err != nil { 611 return skymodules.RenterContract{}, nil, err 612 } 613 614 // Add contract to set. 615 newContract, err := cs.managedInsertContract(header, oldRoots) 616 if err != nil { 617 return skymodules.RenterContract{}, nil, err 618 } 619 620 // Commit changes to old contract. 621 if err := oldSC.managedCommitClearContract(walTxn, finalRevTxn, renewCost); err != nil { 622 return skymodules.RenterContract{}, nil, err 623 } 624 return newContract, txnSet, nil 625 } 626 627 // createFileContractUnlockConds is a helper method to create unlock conditions 628 // for forming and renewing a contract. 629 func createFileContractUnlockConds(hpk types.SiaPublicKey, ourPK crypto.PublicKey) types.UnlockConditions { 630 return types.UnlockConditions{ 631 PublicKeys: []types.SiaPublicKey{ 632 types.Ed25519PublicKey(ourPK), 633 hpk, 634 }, 635 SignaturesRequired: 2, 636 } 637 }