gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/proto/renew.go (about)

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