gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/proto/renew.go (about)

     1  package proto
     2  
     3  import (
     4  	"net"
     5  	"time"
     6  
     7  	"gitlab.com/SiaPrime/SiaPrime/build"
     8  	"gitlab.com/SiaPrime/SiaPrime/crypto"
     9  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    10  	"gitlab.com/SiaPrime/SiaPrime/modules"
    11  	"gitlab.com/SiaPrime/SiaPrime/types"
    12  
    13  	"gitlab.com/NebulousLabs/errors"
    14  )
    15  
    16  // Renew negotiates a new contract for data already stored with a host, and
    17  // submits the new contract transaction to tpool. The new contract is added to
    18  // the ContractSet and its metadata is returned.
    19  func (cs *ContractSet) Renew(oldContract *SafeContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) {
    20  	// use the new renter-host protocol for hosts that support it.
    21  	//
    22  	// NOTE: due to a bug, we use the old protocol even for v1.4.0 hosts.
    23  	if build.VersionCmp(params.Host.Version, "1.4.1") >= 0 {
    24  		return cs.newRenew(oldContract, params, txnBuilder, tpool, hdb, cancel)
    25  	}
    26  	return cs.oldRenew(oldContract, params, txnBuilder, tpool, hdb, cancel)
    27  }
    28  
    29  func (cs *ContractSet) oldRenew(oldContract *SafeContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) {
    30  	// for convenience
    31  	contract := oldContract.header
    32  
    33  	// Extract vars from params, for convenience.
    34  	allowance, host, funding, startHeight, endHeight, refundAddress := params.Allowance, params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress
    35  	ourSK := contract.SecretKey
    36  	lastRev := contract.LastRevision()
    37  
    38  	// Calculate additional basePrice and baseCollateral. If the contract height
    39  	// did not increase, basePrice and baseCollateral are zero.
    40  	var basePrice, baseCollateral types.Currency
    41  	if endHeight+host.WindowSize > lastRev.NewWindowEnd {
    42  		timeExtension := uint64((endHeight + host.WindowSize) - lastRev.NewWindowEnd)
    43  		basePrice = host.StoragePrice.Mul64(lastRev.NewFileSize).Mul64(timeExtension)    // cost of already uploaded data that needs to be covered by the renewed contract.
    44  		baseCollateral = host.Collateral.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // same as basePrice.
    45  	}
    46  
    47  	// Calculate the anticipated transaction fee.
    48  	_, maxFee := tpool.FeeEstimation()
    49  	txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize)
    50  
    51  	// Calculate the payouts for the renter, host, and whole contract.
    52  	period := endHeight - startHeight
    53  	renterPayout, hostPayout, hostCollateral, err := modules.RenterPayoutsPreTax(host, funding, txnFee, basePrice, baseCollateral, period, allowance.ExpectedStorage/allowance.Hosts)
    54  	if err != nil {
    55  		return modules.RenterContract{}, err
    56  	}
    57  	totalPayout := renterPayout.Add(hostPayout)
    58  
    59  	// check for negative currency
    60  	if hostCollateral.Cmp(baseCollateral) < 0 {
    61  		baseCollateral = hostCollateral
    62  	}
    63  	if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 {
    64  		return modules.RenterContract{}, errors.New("insufficient funds to pay both siafund fee and also host payout")
    65  	}
    66  
    67  	// create file contract
    68  	fc := types.FileContract{
    69  		FileSize:       lastRev.NewFileSize,
    70  		FileMerkleRoot: lastRev.NewFileMerkleRoot,
    71  		WindowStart:    endHeight,
    72  		WindowEnd:      endHeight + host.WindowSize,
    73  		Payout:         totalPayout,
    74  		UnlockHash:     lastRev.NewUnlockHash,
    75  		RevisionNumber: 0,
    76  		ValidProofOutputs: []types.SiacoinOutput{
    77  			// renter
    78  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress},
    79  			// host
    80  			{Value: hostPayout, UnlockHash: host.UnlockHash},
    81  		},
    82  		MissedProofOutputs: []types.SiacoinOutput{
    83  			// renter
    84  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress},
    85  			// host gets its unused collateral back, plus the contract price
    86  			{Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash},
    87  			// void gets the spent storage fees, plus the collateral being risked
    88  			{Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}},
    89  		},
    90  	}
    91  
    92  	// build transaction containing fc
    93  	err = txnBuilder.FundSiacoins(funding)
    94  	if err != nil {
    95  		return modules.RenterContract{}, err
    96  	}
    97  	txnBuilder.AddFileContract(fc)
    98  	// add miner fee
    99  	txnBuilder.AddMinerFee(txnFee)
   100  	// Add FileContract identifier.
   101  	fcTxn, _ := txnBuilder.View()
   102  	si, hk := PrefixedSignedIdentifier(params.RenterSeed, fcTxn, host.PublicKey)
   103  	_ = txnBuilder.AddArbitraryData(append(si[:], hk[:]...))
   104  
   105  	// Create initial transaction set.
   106  	txn, parentTxns := txnBuilder.View()
   107  	unconfirmedParents, err := txnBuilder.UnconfirmedParents()
   108  	if err != nil {
   109  		return modules.RenterContract{}, err
   110  	}
   111  	txnSet := append(unconfirmedParents, append(parentTxns, txn)...)
   112  
   113  	// Increase Successful/Failed interactions accordingly
   114  	defer func() {
   115  		// A revision mismatch might not be the host's fault.
   116  		if err != nil && !IsRevisionMismatch(err) {
   117  			hdb.IncrementFailedInteractions(contract.HostPublicKey())
   118  			err = errors.Extend(err, modules.ErrHostFault)
   119  		} else if err == nil {
   120  			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   121  		}
   122  	}()
   123  
   124  	// initiate connection
   125  	dialer := &net.Dialer{
   126  		Cancel:  cancel,
   127  		Timeout: connTimeout,
   128  	}
   129  	conn, err := dialer.Dial("tcp", string(host.NetAddress))
   130  	if err != nil {
   131  		return modules.RenterContract{}, err
   132  	}
   133  	defer func() { _ = conn.Close() }()
   134  
   135  	// allot time for sending RPC ID, verifyRecentRevision, and verifySettings
   136  	extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime)
   137  	if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil {
   138  		return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error())
   139  	}
   140  	// verify that both parties are renewing the same contract
   141  	err = verifyRecentRevision(conn, oldContract, host.Version)
   142  	if err != nil {
   143  		return modules.RenterContract{}, err
   144  	}
   145  
   146  	// verify the host's settings and confirm its identity
   147  	host, err = verifySettings(conn, host)
   148  	if err != nil {
   149  		return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error())
   150  	}
   151  	if !host.AcceptingContracts {
   152  		return modules.RenterContract{}, errors.New("host is not accepting contracts")
   153  	}
   154  
   155  	// allot time for negotiation
   156  	numSectors := fc.FileSize / modules.SectorSize
   157  	extendDeadline(conn, modules.NegotiateRenewContractTime+(time.Duration(numSectors)*10*time.Millisecond))
   158  
   159  	// send acceptance, txn signed by us, and pubkey
   160  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   161  		return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error())
   162  	}
   163  	if err = encoding.WriteObject(conn, txnSet); err != nil {
   164  		return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
   165  	}
   166  	if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil {
   167  		return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error())
   168  	}
   169  
   170  	// read acceptance and txn signed by host
   171  	if err = modules.ReadNegotiationAcceptance(conn); err != nil {
   172  		return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error())
   173  	}
   174  	// host now sends any new parent transactions, inputs and outputs that
   175  	// were added to the transaction
   176  	var newParents []types.Transaction
   177  	var newInputs []types.SiacoinInput
   178  	var newOutputs []types.SiacoinOutput
   179  	if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil {
   180  		return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error())
   181  	}
   182  	if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil {
   183  		return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error())
   184  	}
   185  	if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil {
   186  		return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error())
   187  	}
   188  
   189  	// merge txnAdditions with txnSet
   190  	txnBuilder.AddParents(newParents)
   191  	for _, input := range newInputs {
   192  		txnBuilder.AddSiacoinInput(input)
   193  	}
   194  	for _, output := range newOutputs {
   195  		txnBuilder.AddSiacoinOutput(output)
   196  	}
   197  
   198  	// sign the txn
   199  	signedTxnSet, err := txnBuilder.Sign(true)
   200  	if err != nil {
   201  		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error()))
   202  	}
   203  
   204  	// calculate signatures added by the transaction builder
   205  	var addedSignatures []types.TransactionSignature
   206  	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
   207  	for _, i := range addedSignatureIndices {
   208  		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
   209  	}
   210  
   211  	// create initial (no-op) revision, transaction, and signature
   212  	initRevision := types.FileContractRevision{
   213  		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
   214  		UnlockConditions:  lastRev.UnlockConditions,
   215  		NewRevisionNumber: 1,
   216  
   217  		NewFileSize:           fc.FileSize,
   218  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   219  		NewWindowStart:        fc.WindowStart,
   220  		NewWindowEnd:          fc.WindowEnd,
   221  		NewValidProofOutputs:  fc.ValidProofOutputs,
   222  		NewMissedProofOutputs: fc.MissedProofOutputs,
   223  		NewUnlockHash:         fc.UnlockHash,
   224  	}
   225  	renterRevisionSig := types.TransactionSignature{
   226  		ParentID:       crypto.Hash(initRevision.ParentID),
   227  		PublicKeyIndex: 0,
   228  		CoveredFields: types.CoveredFields{
   229  			FileContractRevisions: []uint64{0},
   230  		},
   231  	}
   232  	revisionTxn := types.Transaction{
   233  		FileContractRevisions: []types.FileContractRevision{initRevision},
   234  		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
   235  	}
   236  	encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK)
   237  	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]
   238  
   239  	// Send acceptance and signatures
   240  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   241  		return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error())
   242  	}
   243  	if err = encoding.WriteObject(conn, addedSignatures); err != nil {
   244  		return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error())
   245  	}
   246  	if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil {
   247  		return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error())
   248  	}
   249  
   250  	// Read the host acceptance and signatures.
   251  	err = modules.ReadNegotiationAcceptance(conn)
   252  	if err != nil {
   253  		return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error())
   254  	}
   255  	var hostSigs []types.TransactionSignature
   256  	if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil {
   257  		return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error())
   258  	}
   259  	for _, sig := range hostSigs {
   260  		txnBuilder.AddTransactionSignature(sig)
   261  	}
   262  	var hostRevisionSig types.TransactionSignature
   263  	if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil {
   264  		return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error())
   265  	}
   266  	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig)
   267  
   268  	// Construct the final transaction.
   269  	txn, parentTxns = txnBuilder.View()
   270  	txnSet = append(parentTxns, txn)
   271  
   272  	// Submit to blockchain.
   273  	err = tpool.AcceptTransactionSet(txnSet)
   274  	if err == modules.ErrDuplicateTransactionSet {
   275  		// as long as it made it into the transaction pool, we're good
   276  		err = nil
   277  	}
   278  	if err != nil {
   279  		return modules.RenterContract{}, err
   280  	}
   281  
   282  	// Construct contract header.
   283  	header := contractHeader{
   284  		Transaction:     revisionTxn,
   285  		SecretKey:       ourSK,
   286  		StartHeight:     startHeight,
   287  		TotalCost:       funding,
   288  		ContractFee:     host.ContractPrice,
   289  		TxnFee:          txnFee,
   290  		SiafundFee:      types.Tax(startHeight, fc.Payout),
   291  		StorageSpending: basePrice,
   292  		Utility: modules.ContractUtility{
   293  			GoodForUpload: true,
   294  			GoodForRenew:  true,
   295  		},
   296  	}
   297  
   298  	// Get old roots
   299  	oldRoots, err := oldContract.merkleRoots.merkleRoots()
   300  	if err != nil {
   301  		return modules.RenterContract{}, err
   302  	}
   303  
   304  	// Add contract to set.
   305  	meta, err := cs.managedInsertContract(header, oldRoots)
   306  	if err != nil {
   307  		return modules.RenterContract{}, err
   308  	}
   309  	return meta, nil
   310  }
   311  
   312  func (cs *ContractSet) newRenew(oldContract *SafeContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) {
   313  	// for convenience
   314  	contract := oldContract.header
   315  
   316  	// Extract vars from params, for convenience.
   317  	allowance, host, funding, startHeight, endHeight, refundAddress := params.Allowance, params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress
   318  	ourSK := contract.SecretKey
   319  	lastRev := contract.LastRevision()
   320  
   321  	// Calculate additional basePrice and baseCollateral. If the contract height
   322  	// did not increase, basePrice and baseCollateral are zero.
   323  	var basePrice, baseCollateral types.Currency
   324  	if endHeight+host.WindowSize > lastRev.NewWindowEnd {
   325  		timeExtension := uint64((endHeight + host.WindowSize) - lastRev.NewWindowEnd)
   326  		basePrice = host.StoragePrice.Mul64(lastRev.NewFileSize).Mul64(timeExtension)    // cost of already uploaded data that needs to be covered by the renewed contract.
   327  		baseCollateral = host.Collateral.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // same as basePrice.
   328  	}
   329  
   330  	// Calculate the anticipated transaction fee.
   331  	_, maxFee := tpool.FeeEstimation()
   332  	txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize)
   333  
   334  	// Calculate the payouts for the renter, host, and whole contract.
   335  	period := endHeight - startHeight
   336  	renterPayout, hostPayout, hostCollateral, err := modules.RenterPayoutsPreTax(host, funding, txnFee, basePrice, baseCollateral, period, allowance.ExpectedStorage/allowance.Hosts)
   337  	if err != nil {
   338  		return modules.RenterContract{}, err
   339  	}
   340  	totalPayout := renterPayout.Add(hostPayout)
   341  
   342  	// check for negative currency
   343  	if hostCollateral.Cmp(baseCollateral) < 0 {
   344  		baseCollateral = hostCollateral
   345  	}
   346  	if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 {
   347  		return modules.RenterContract{}, errors.New("insufficient funds to pay both siafund fee and also host payout")
   348  	}
   349  
   350  	// create file contract
   351  	fc := types.FileContract{
   352  		FileSize:       lastRev.NewFileSize,
   353  		FileMerkleRoot: lastRev.NewFileMerkleRoot,
   354  		WindowStart:    endHeight,
   355  		WindowEnd:      endHeight + host.WindowSize,
   356  		Payout:         totalPayout,
   357  		UnlockHash:     lastRev.NewUnlockHash,
   358  		RevisionNumber: 0,
   359  		ValidProofOutputs: []types.SiacoinOutput{
   360  			// renter
   361  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress},
   362  			// host
   363  			{Value: hostPayout, UnlockHash: host.UnlockHash},
   364  		},
   365  		MissedProofOutputs: []types.SiacoinOutput{
   366  			// renter
   367  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress},
   368  			// host gets its unused collateral back, plus the contract price
   369  			{Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash},
   370  			// void gets the spent storage fees, plus the collateral being risked
   371  			{Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}},
   372  		},
   373  	}
   374  
   375  	// build transaction containing fc
   376  	err = txnBuilder.FundSiacoins(funding)
   377  	if err != nil {
   378  		return modules.RenterContract{}, err
   379  	}
   380  	txnBuilder.AddFileContract(fc)
   381  	// add miner fee
   382  	txnBuilder.AddMinerFee(txnFee)
   383  	// Add FileContract identifier.
   384  	fcTxn, _ := txnBuilder.View()
   385  	si, hk := PrefixedSignedIdentifier(params.RenterSeed, fcTxn, host.PublicKey)
   386  	_ = txnBuilder.AddArbitraryData(append(si[:], hk[:]...))
   387  
   388  	// Create initial transaction set.
   389  	txn, parentTxns := txnBuilder.View()
   390  	unconfirmedParents, err := txnBuilder.UnconfirmedParents()
   391  	if err != nil {
   392  		return modules.RenterContract{}, err
   393  	}
   394  	txnSet := append(unconfirmedParents, append(parentTxns, txn)...)
   395  
   396  	// Increase Successful/Failed interactions accordingly
   397  	defer func() {
   398  		// A revision mismatch might not be the host's fault.
   399  		if err != nil && !IsRevisionMismatch(err) {
   400  			hdb.IncrementFailedInteractions(contract.HostPublicKey())
   401  			err = errors.Extend(err, modules.ErrHostFault)
   402  		} else if err == nil {
   403  			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   404  		}
   405  	}()
   406  
   407  	// Initiate protocol.
   408  	s, err := cs.NewRawSession(host, startHeight, hdb, cancel)
   409  	if err != nil {
   410  		return modules.RenterContract{}, err
   411  	}
   412  	defer s.Close()
   413  	// Lock the contract and resynchronize if necessary
   414  	rev, sigs, err := s.Lock(contract.ID(), contract.SecretKey)
   415  	if err != nil {
   416  		return modules.RenterContract{}, err
   417  	} else if err := oldContract.managedSyncRevision(rev, sigs); err != nil {
   418  		return modules.RenterContract{}, err
   419  	}
   420  
   421  	// Send the RenewContract request.
   422  	req := modules.LoopRenewContractRequest{
   423  		Transactions: txnSet,
   424  		RenterKey:    lastRev.UnlockConditions.PublicKeys[0],
   425  	}
   426  	if err := s.writeRequest(modules.RPCLoopRenewContract, req); err != nil {
   427  		return modules.RenterContract{}, err
   428  	}
   429  
   430  	// Read the host's response.
   431  	var resp modules.LoopContractAdditions
   432  	if err := s.readResponse(&resp, modules.RPCMinLen); err != nil {
   433  		return modules.RenterContract{}, err
   434  	}
   435  
   436  	// Incorporate host's modifications.
   437  	txnBuilder.AddParents(resp.Parents)
   438  	for _, input := range resp.Inputs {
   439  		txnBuilder.AddSiacoinInput(input)
   440  	}
   441  	for _, output := range resp.Outputs {
   442  		txnBuilder.AddSiacoinOutput(output)
   443  	}
   444  
   445  	// sign the txn
   446  	signedTxnSet, err := txnBuilder.Sign(true)
   447  	if err != nil {
   448  		err = errors.New("failed to sign transaction: " + err.Error())
   449  		modules.WriteRPCResponse(s.conn, s.aead, nil, err)
   450  		return modules.RenterContract{}, err
   451  	}
   452  
   453  	// calculate signatures added by the transaction builder
   454  	var addedSignatures []types.TransactionSignature
   455  	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
   456  	for _, i := range addedSignatureIndices {
   457  		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
   458  	}
   459  
   460  	// create initial (no-op) revision, transaction, and signature
   461  	initRevision := types.FileContractRevision{
   462  		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
   463  		UnlockConditions:  lastRev.UnlockConditions,
   464  		NewRevisionNumber: 1,
   465  
   466  		NewFileSize:           fc.FileSize,
   467  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   468  		NewWindowStart:        fc.WindowStart,
   469  		NewWindowEnd:          fc.WindowEnd,
   470  		NewValidProofOutputs:  fc.ValidProofOutputs,
   471  		NewMissedProofOutputs: fc.MissedProofOutputs,
   472  		NewUnlockHash:         fc.UnlockHash,
   473  	}
   474  	renterRevisionSig := types.TransactionSignature{
   475  		ParentID:       crypto.Hash(initRevision.ParentID),
   476  		PublicKeyIndex: 0,
   477  		CoveredFields: types.CoveredFields{
   478  			FileContractRevisions: []uint64{0},
   479  		},
   480  	}
   481  	revisionTxn := types.Transaction{
   482  		FileContractRevisions: []types.FileContractRevision{initRevision},
   483  		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
   484  	}
   485  	encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK)
   486  	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]
   487  
   488  	// Send acceptance and signatures
   489  	renterSigs := modules.LoopContractSignatures{
   490  		ContractSignatures: addedSignatures,
   491  		RevisionSignature:  revisionTxn.TransactionSignatures[0],
   492  	}
   493  	if err := modules.WriteRPCResponse(s.conn, s.aead, renterSigs, nil); err != nil {
   494  		return modules.RenterContract{}, err
   495  	}
   496  
   497  	// Read the host acceptance and signatures.
   498  	var hostSigs modules.LoopContractSignatures
   499  	if err := s.readResponse(&hostSigs, modules.RPCMinLen); err != nil {
   500  		return modules.RenterContract{}, err
   501  	}
   502  	for _, sig := range hostSigs.ContractSignatures {
   503  		txnBuilder.AddTransactionSignature(sig)
   504  	}
   505  	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostSigs.RevisionSignature)
   506  
   507  	// Construct the final transaction.
   508  	txn, parentTxns = txnBuilder.View()
   509  	txnSet = append(parentTxns, txn)
   510  
   511  	// Submit to blockchain.
   512  	err = tpool.AcceptTransactionSet(txnSet)
   513  	if err == modules.ErrDuplicateTransactionSet {
   514  		// as long as it made it into the transaction pool, we're good
   515  		err = nil
   516  	}
   517  	if err != nil {
   518  		return modules.RenterContract{}, err
   519  	}
   520  
   521  	// Construct contract header.
   522  	header := contractHeader{
   523  		Transaction:     revisionTxn,
   524  		SecretKey:       ourSK,
   525  		StartHeight:     startHeight,
   526  		TotalCost:       funding,
   527  		ContractFee:     host.ContractPrice,
   528  		TxnFee:          txnFee,
   529  		SiafundFee:      types.Tax(startHeight, fc.Payout),
   530  		StorageSpending: basePrice,
   531  		Utility: modules.ContractUtility{
   532  			GoodForUpload: true,
   533  			GoodForRenew:  true,
   534  		},
   535  	}
   536  
   537  	// Get old roots
   538  	oldRoots, err := oldContract.merkleRoots.merkleRoots()
   539  	if err != nil {
   540  		return modules.RenterContract{}, err
   541  	}
   542  
   543  	// Add contract to set.
   544  	meta, err := cs.managedInsertContract(header, oldRoots)
   545  	if err != nil {
   546  		return modules.RenterContract{}, err
   547  	}
   548  	return meta, nil
   549  }