github.com/nebulouslabs/sia@v1.3.7/modules/renter/proto/renew.go (about)

     1  package proto
     2  
     3  import (
     4  	"net"
     5  
     6  	"github.com/NebulousLabs/Sia/crypto"
     7  	"github.com/NebulousLabs/Sia/encoding"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/types"
    10  
    11  	"github.com/NebulousLabs/errors"
    12  )
    13  
    14  // Renew negotiates a new contract for data already stored with a host, and
    15  // submits the new contract transaction to tpool. The new contract is added to
    16  // the ContractSet and its metadata is returned.
    17  func (cs *ContractSet) Renew(oldContract *SafeContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) {
    18  	// for convenience
    19  	contract := oldContract.header
    20  
    21  	// Extract vars from params, for convenience.
    22  	host, funding, startHeight, endHeight, refundAddress := params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress
    23  	ourSK := contract.SecretKey
    24  	lastRev := contract.LastRevision()
    25  
    26  	// Calculate additional basePrice and baseCollateral. If the contract height
    27  	// did not increase, basePrice and baseCollateral are zero.
    28  	var basePrice, baseCollateral types.Currency
    29  	if endHeight+host.WindowSize > lastRev.NewWindowEnd {
    30  		timeExtension := uint64((endHeight + host.WindowSize) - lastRev.NewWindowEnd)
    31  		basePrice = host.StoragePrice.Mul64(lastRev.NewFileSize).Mul64(timeExtension)    // cost of data already covered by contract, i.e. lastrevision.Filesize
    32  		baseCollateral = host.Collateral.Mul64(lastRev.NewFileSize).Mul64(timeExtension) // same but collateral
    33  	}
    34  
    35  	// Calculate the anticipated transaction fee.
    36  	_, maxFee := tpool.FeeEstimation()
    37  	txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize)
    38  
    39  	// Underflow check.
    40  	if funding.Cmp(host.ContractPrice.Add(txnFee).Add(basePrice)) <= 0 {
    41  		return modules.RenterContract{}, errors.New("insufficient funds to cover contract fee and transaction fee during contract renewal")
    42  	}
    43  	// Divide by zero check.
    44  	if host.StoragePrice.IsZero() {
    45  		host.StoragePrice = types.NewCurrency64(1)
    46  	}
    47  
    48  	// Calculate the payouts for the renter, host, and whole contract.
    49  	renterPayout := funding.Sub(host.ContractPrice).Sub(txnFee).Sub(basePrice) // renter payout is pre-tax
    50  	maxStorageSize := renterPayout.Div(host.StoragePrice)
    51  	hostCollateral := maxStorageSize.Mul(host.Collateral)
    52  	if hostCollateral.Cmp(host.MaxCollateral) > 0 {
    53  		hostCollateral = host.MaxCollateral
    54  	}
    55  
    56  	// Determine the host payout and the total payout for the contract.
    57  	hostPayout := hostCollateral.Add(host.ContractPrice).Add(basePrice)
    58  	totalPayout := renterPayout.Add(hostPayout)
    59  
    60  	// check for negative currency
    61  	if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 {
    62  		return modules.RenterContract{}, errors.New("insufficient funds to pay both siafund fee and also host payout")
    63  	} else if hostCollateral.Cmp(baseCollateral) < 0 {
    64  		return modules.RenterContract{}, errors.New("new collateral smaller than base collateral")
    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  
   101  	// Create initial transaction set.
   102  	txn, parentTxns := txnBuilder.View()
   103  	unconfirmedParents, err := txnBuilder.UnconfirmedParents()
   104  	if err != nil {
   105  		return modules.RenterContract{}, err
   106  	}
   107  	txnSet := append(unconfirmedParents, append(parentTxns, txn)...)
   108  
   109  	// Increase Successful/Failed interactions accordingly
   110  	defer func() {
   111  		// A revision mismatch might not be the host's fault.
   112  		if err != nil && !IsRevisionMismatch(err) {
   113  			hdb.IncrementFailedInteractions(contract.HostPublicKey())
   114  			err = errors.Extend(err, modules.ErrHostFault)
   115  		} else if err == nil {
   116  			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   117  		}
   118  	}()
   119  
   120  	// initiate connection
   121  	dialer := &net.Dialer{
   122  		Cancel:  cancel,
   123  		Timeout: connTimeout,
   124  	}
   125  	conn, err := dialer.Dial("tcp", string(host.NetAddress))
   126  	if err != nil {
   127  		return modules.RenterContract{}, err
   128  	}
   129  	defer func() { _ = conn.Close() }()
   130  
   131  	// allot time for sending RPC ID, verifyRecentRevision, and verifySettings
   132  	extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime)
   133  	if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil {
   134  		return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error())
   135  	}
   136  	// verify that both parties are renewing the same contract
   137  	if err = verifyRecentRevision(conn, contract, host.Version); err != nil {
   138  		// don't add context; want to preserve the original error type so that
   139  		// callers can check using IsRevisionMismatch
   140  		return modules.RenterContract{}, err
   141  	}
   142  	// verify the host's settings and confirm its identity
   143  	host, err = verifySettings(conn, host)
   144  	if err != nil {
   145  		return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error())
   146  	}
   147  	if !host.AcceptingContracts {
   148  		return modules.RenterContract{}, errors.New("host is not accepting contracts")
   149  	}
   150  
   151  	// allot time for negotiation
   152  	extendDeadline(conn, modules.NegotiateRenewContractTime)
   153  
   154  	// send acceptance, txn signed by us, and pubkey
   155  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   156  		return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error())
   157  	}
   158  	if err = encoding.WriteObject(conn, txnSet); err != nil {
   159  		return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
   160  	}
   161  	if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil {
   162  		return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error())
   163  	}
   164  
   165  	// read acceptance and txn signed by host
   166  	if err = modules.ReadNegotiationAcceptance(conn); err != nil {
   167  		return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error())
   168  	}
   169  	// host now sends any new parent transactions, inputs and outputs that
   170  	// were added to the transaction
   171  	var newParents []types.Transaction
   172  	var newInputs []types.SiacoinInput
   173  	var newOutputs []types.SiacoinOutput
   174  	if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil {
   175  		return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error())
   176  	}
   177  	if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil {
   178  		return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error())
   179  	}
   180  	if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil {
   181  		return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error())
   182  	}
   183  
   184  	// merge txnAdditions with txnSet
   185  	txnBuilder.AddParents(newParents)
   186  	for _, input := range newInputs {
   187  		txnBuilder.AddSiacoinInput(input)
   188  	}
   189  	for _, output := range newOutputs {
   190  		txnBuilder.AddSiacoinOutput(output)
   191  	}
   192  
   193  	// sign the txn
   194  	signedTxnSet, err := txnBuilder.Sign(true)
   195  	if err != nil {
   196  		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error()))
   197  	}
   198  
   199  	// calculate signatures added by the transaction builder
   200  	var addedSignatures []types.TransactionSignature
   201  	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
   202  	for _, i := range addedSignatureIndices {
   203  		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
   204  	}
   205  
   206  	// create initial (no-op) revision, transaction, and signature
   207  	initRevision := types.FileContractRevision{
   208  		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
   209  		UnlockConditions:  lastRev.UnlockConditions,
   210  		NewRevisionNumber: 1,
   211  
   212  		NewFileSize:           fc.FileSize,
   213  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   214  		NewWindowStart:        fc.WindowStart,
   215  		NewWindowEnd:          fc.WindowEnd,
   216  		NewValidProofOutputs:  fc.ValidProofOutputs,
   217  		NewMissedProofOutputs: fc.MissedProofOutputs,
   218  		NewUnlockHash:         fc.UnlockHash,
   219  	}
   220  	renterRevisionSig := types.TransactionSignature{
   221  		ParentID:       crypto.Hash(initRevision.ParentID),
   222  		PublicKeyIndex: 0,
   223  		CoveredFields: types.CoveredFields{
   224  			FileContractRevisions: []uint64{0},
   225  		},
   226  	}
   227  	revisionTxn := types.Transaction{
   228  		FileContractRevisions: []types.FileContractRevision{initRevision},
   229  		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
   230  	}
   231  	encodedSig := crypto.SignHash(revisionTxn.SigHash(0), ourSK)
   232  	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]
   233  
   234  	// Send acceptance and signatures
   235  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   236  		return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error())
   237  	}
   238  	if err = encoding.WriteObject(conn, addedSignatures); err != nil {
   239  		return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error())
   240  	}
   241  	if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil {
   242  		return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error())
   243  	}
   244  
   245  	// Read the host acceptance and signatures.
   246  	err = modules.ReadNegotiationAcceptance(conn)
   247  	if err != nil {
   248  		return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error())
   249  	}
   250  	var hostSigs []types.TransactionSignature
   251  	if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil {
   252  		return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error())
   253  	}
   254  	for _, sig := range hostSigs {
   255  		txnBuilder.AddTransactionSignature(sig)
   256  	}
   257  	var hostRevisionSig types.TransactionSignature
   258  	if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil {
   259  		return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error())
   260  	}
   261  	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig)
   262  
   263  	// Construct the final transaction.
   264  	txn, parentTxns = txnBuilder.View()
   265  	txnSet = append(parentTxns, txn)
   266  
   267  	// Submit to blockchain.
   268  	err = tpool.AcceptTransactionSet(txnSet)
   269  	if err == modules.ErrDuplicateTransactionSet {
   270  		// as long as it made it into the transaction pool, we're good
   271  		err = nil
   272  	}
   273  	if err != nil {
   274  		return modules.RenterContract{}, err
   275  	}
   276  
   277  	// Construct contract header.
   278  	header := contractHeader{
   279  		Transaction:     revisionTxn,
   280  		SecretKey:       ourSK,
   281  		StartHeight:     startHeight,
   282  		TotalCost:       funding,
   283  		ContractFee:     host.ContractPrice,
   284  		TxnFee:          txnFee,
   285  		SiafundFee:      types.Tax(startHeight, fc.Payout),
   286  		StorageSpending: basePrice,
   287  		Utility: modules.ContractUtility{
   288  			GoodForUpload: true,
   289  			GoodForRenew:  true,
   290  		},
   291  	}
   292  
   293  	// Get old roots
   294  	oldRoots, err := oldContract.merkleRoots.merkleRoots()
   295  	if err != nil {
   296  		return modules.RenterContract{}, err
   297  	}
   298  
   299  	// Add contract to set.
   300  	meta, err := cs.managedInsertContract(header, oldRoots)
   301  	if err != nil {
   302  		return modules.RenterContract{}, err
   303  	}
   304  	return meta, nil
   305  }