github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/proto/renew.go (about)

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/encoding"
    10  	"github.com/NebulousLabs/Sia/modules"
    11  	"github.com/NebulousLabs/Sia/types"
    12  )
    13  
    14  // Renew negotiates a new contract for data already stored with a host, and
    15  // submits the new contract transaction to tpool.
    16  func Renew(contract modules.RenterContract, params ContractParams, txnBuilder transactionBuilder, tpool transactionPool) (modules.RenterContract, error) {
    17  	// extract vars from params, for convenience
    18  	host, filesize, startHeight, endHeight, refundAddress := params.Host, params.Filesize, params.StartHeight, params.EndHeight, params.RefundAddress
    19  	ourSK := contract.SecretKey
    20  
    21  	// calculate cost to renter and cost to host
    22  	storageAllocation := host.StoragePrice.Mul64(filesize).Mul64(uint64(endHeight - startHeight))
    23  	hostCollateral := host.Collateral.Mul64(filesize).Mul64(uint64(endHeight - startHeight))
    24  	if hostCollateral.Cmp(host.MaxCollateral) > 0 {
    25  		// TODO: if we have to cap the collateral, it probably means we shouldn't be using this host
    26  		// (ok within a factor of 2)
    27  		hostCollateral = host.MaxCollateral
    28  	}
    29  
    30  	timeExtension := uint64((endHeight + host.WindowSize) - contract.LastRevision.NewWindowEnd)
    31  	basePrice := host.StoragePrice.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension)    // cost of data already covered by contract, i.e. lastrevision.Filesize
    32  	baseCollateral := host.Collateral.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension) // same but collateral
    33  
    34  	hostPayout := hostCollateral.Add(host.ContractPrice).Add(basePrice)
    35  
    36  	payout := storageAllocation.Add(hostCollateral.Add(host.ContractPrice)).Mul64(10406).Div64(10000) // renter covers siafund fee
    37  	renterCost := payout.Sub(hostCollateral)
    38  
    39  	// create file contract
    40  	fc := types.FileContract{
    41  		FileSize:       contract.LastRevision.NewFileSize,
    42  		FileMerkleRoot: contract.LastRevision.NewFileMerkleRoot,
    43  		WindowStart:    endHeight,
    44  		WindowEnd:      endHeight + host.WindowSize,
    45  		Payout:         payout,
    46  		UnlockHash:     contract.LastRevision.NewUnlockHash,
    47  		RevisionNumber: 0,
    48  		ValidProofOutputs: []types.SiacoinOutput{
    49  			// renter
    50  			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
    51  			// host
    52  			{Value: hostPayout, UnlockHash: host.UnlockHash},
    53  		},
    54  		MissedProofOutputs: []types.SiacoinOutput{
    55  			// renter
    56  			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
    57  			// host gets its unused collateral back, plus the contract price
    58  			{Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash},
    59  			// void gets the spent storage fees, plus the collateral being risked
    60  			{Value: basePrice.Add(baseCollateral), UnlockHash: types.UnlockHash{}},
    61  		},
    62  	}
    63  
    64  	// calculate transaction fee
    65  	_, maxFee := tpool.FeeEstimation()
    66  	fee := maxFee.Mul64(estTxnSize)
    67  
    68  	// build transaction containing fc
    69  	err := txnBuilder.FundSiacoins(renterCost.Add(fee))
    70  	if err != nil {
    71  		return modules.RenterContract{}, err
    72  	}
    73  	txnBuilder.AddFileContract(fc)
    74  
    75  	// add miner fee
    76  	txnBuilder.AddMinerFee(fee)
    77  
    78  	// create initial transaction set
    79  	txn, parentTxns := txnBuilder.View()
    80  	txnSet := append(parentTxns, txn)
    81  
    82  	// initiate connection
    83  	conn, err := net.DialTimeout("tcp", string(host.NetAddress), 15*time.Second)
    84  	if err != nil {
    85  		return modules.RenterContract{}, err
    86  	}
    87  	defer func() { _ = conn.Close() }()
    88  
    89  	// allot time for sending RPC ID, verifyRecentRevision, and verifySettings
    90  	extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime)
    91  	if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil {
    92  		return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error())
    93  	}
    94  	// verify that both parties are renewing the same contract
    95  	if err = verifyRecentRevision(conn, contract); err != nil {
    96  		return modules.RenterContract{}, errors.New("revision exchange failed: " + err.Error())
    97  	}
    98  	// verify the host's settings and confirm its identity
    99  	host, err = verifySettings(conn, host)
   100  	if err != nil {
   101  		return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error())
   102  	}
   103  	if !host.AcceptingContracts {
   104  		return modules.RenterContract{}, errors.New("host is not accepting contracts")
   105  	}
   106  
   107  	// allot time for negotiation
   108  	extendDeadline(conn, modules.NegotiateRenewContractTime)
   109  
   110  	// send acceptance, txn signed by us, and pubkey
   111  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   112  		return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error())
   113  	}
   114  	if err = encoding.WriteObject(conn, txnSet); err != nil {
   115  		return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
   116  	}
   117  	if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil {
   118  		return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error())
   119  	}
   120  
   121  	// read acceptance and txn signed by host
   122  	if err = modules.ReadNegotiationAcceptance(conn); err != nil {
   123  		return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error())
   124  	}
   125  	// host now sends any new parent transactions, inputs and outputs that
   126  	// were added to the transaction
   127  	var newParents []types.Transaction
   128  	var newInputs []types.SiacoinInput
   129  	var newOutputs []types.SiacoinOutput
   130  	if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil {
   131  		return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error())
   132  	}
   133  	if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil {
   134  		return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error())
   135  	}
   136  	if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil {
   137  		return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error())
   138  	}
   139  
   140  	// merge txnAdditions with txnSet
   141  	txnBuilder.AddParents(newParents)
   142  	for _, input := range newInputs {
   143  		txnBuilder.AddSiacoinInput(input)
   144  	}
   145  	for _, output := range newOutputs {
   146  		txnBuilder.AddSiacoinOutput(output)
   147  	}
   148  
   149  	// sign the txn
   150  	signedTxnSet, err := txnBuilder.Sign(true)
   151  	if err != nil {
   152  		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error()))
   153  	}
   154  
   155  	// calculate signatures added by the transaction builder
   156  	var addedSignatures []types.TransactionSignature
   157  	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
   158  	for _, i := range addedSignatureIndices {
   159  		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
   160  	}
   161  
   162  	// create initial (no-op) revision, transaction, and signature
   163  	initRevision := types.FileContractRevision{
   164  		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
   165  		UnlockConditions:  contract.LastRevision.UnlockConditions,
   166  		NewRevisionNumber: 1,
   167  
   168  		NewFileSize:           fc.FileSize,
   169  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   170  		NewWindowStart:        fc.WindowStart,
   171  		NewWindowEnd:          fc.WindowEnd,
   172  		NewValidProofOutputs:  fc.ValidProofOutputs,
   173  		NewMissedProofOutputs: fc.MissedProofOutputs,
   174  		NewUnlockHash:         fc.UnlockHash,
   175  	}
   176  	renterRevisionSig := types.TransactionSignature{
   177  		ParentID:       crypto.Hash(initRevision.ParentID),
   178  		PublicKeyIndex: 0,
   179  		CoveredFields: types.CoveredFields{
   180  			FileContractRevisions: []uint64{0},
   181  		},
   182  	}
   183  	revisionTxn := types.Transaction{
   184  		FileContractRevisions: []types.FileContractRevision{initRevision},
   185  		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
   186  	}
   187  	encodedSig, err := crypto.SignHash(revisionTxn.SigHash(0), ourSK)
   188  	if err != nil {
   189  		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign revision transaction: "+err.Error()))
   190  	}
   191  	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]
   192  
   193  	// Send acceptance and signatures
   194  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   195  		return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error())
   196  	}
   197  	if err = encoding.WriteObject(conn, addedSignatures); err != nil {
   198  		return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error())
   199  	}
   200  	if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil {
   201  		return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error())
   202  	}
   203  
   204  	// Read the host acceptance and signatures.
   205  	err = modules.ReadNegotiationAcceptance(conn)
   206  	if err != nil {
   207  		return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error())
   208  	}
   209  	var hostSigs []types.TransactionSignature
   210  	if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil {
   211  		return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error())
   212  	}
   213  	for _, sig := range hostSigs {
   214  		txnBuilder.AddTransactionSignature(sig)
   215  	}
   216  	var hostRevisionSig types.TransactionSignature
   217  	if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil {
   218  		return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error())
   219  	}
   220  	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig)
   221  
   222  	// Construct the final transaction.
   223  	txn, parentTxns = txnBuilder.View()
   224  	txnSet = append(parentTxns, txn)
   225  
   226  	// Submit to blockchain.
   227  	err = tpool.AcceptTransactionSet(txnSet)
   228  	if err == modules.ErrDuplicateTransactionSet {
   229  		// as long as it made it into the transaction pool, we're good
   230  		err = nil
   231  	}
   232  	if err != nil {
   233  		return modules.RenterContract{}, err
   234  	}
   235  
   236  	// calculate contract ID
   237  	fcid := txn.FileContractID(0)
   238  
   239  	return modules.RenterContract{
   240  		FileContract:    fc,
   241  		ID:              fcid,
   242  		LastRevision:    initRevision,
   243  		LastRevisionTxn: revisionTxn,
   244  		NetAddress:      host.NetAddress,
   245  		SecretKey:       ourSK,
   246  	}, nil
   247  }