github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/renew.go (about)

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