github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/formcontract.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  // FormContract forms a contract with a host and submits the contract
    15  // transaction to tpool. The contract is added to the ContractSet and its
    16  // metadata is returned.
    17  func (cs *ContractSet) FormContract(params ContractParams, txnBuilder transactionBuilder, tpool transactionPool, hdb hostDB, cancel <-chan struct{}) (rc modules.RenterContract, err error) {
    18  	// Extract vars from params, for convenience.
    19  	host, funding, startHeight, endHeight, refundAddress := params.Host, params.Funding, params.StartHeight, params.EndHeight, params.RefundAddress
    20  
    21  	// Create our key.
    22  	ourSK, ourPK := crypto.GenerateKeyPair()
    23  	// Create unlock conditions.
    24  	uc := types.UnlockConditions{
    25  		PublicKeys: []types.SiaPublicKey{
    26  			types.Ed25519PublicKey(ourPK),
    27  			host.PublicKey,
    28  		},
    29  		SignaturesRequired: 2,
    30  	}
    31  
    32  	// Calculate the anticipated transaction fee.
    33  	_, maxFee := tpool.FeeEstimation()
    34  	txnFee := maxFee.Mul64(modules.EstimatedFileContractTransactionSetSize)
    35  
    36  	// Calculate the payouts for the renter, host, and whole contract.
    37  	period := endHeight - startHeight
    38  	expectedStorage := modules.DefaultUsageGuideLines.ExpectedStorage
    39  	renterPayout, hostPayout, _, err := modules.RenterPayoutsPreTax(host, funding, txnFee, types.ZeroCurrency, period, expectedStorage)
    40  	if err != nil {
    41  		return modules.RenterContract{}, err
    42  	}
    43  	totalPayout := renterPayout.Add(hostPayout)
    44  
    45  	// Check for negative currency.
    46  	if types.PostTax(startHeight, totalPayout).Cmp(hostPayout) < 0 {
    47  		return modules.RenterContract{}, errors.New("not enough money to pay both siafund fee and also host payout")
    48  	}
    49  	// Create file contract.
    50  	fc := types.FileContract{
    51  		FileSize:       0,
    52  		FileMerkleRoot: crypto.Hash{}, // no proof possible without data
    53  		WindowStart:    endHeight,
    54  		WindowEnd:      endHeight + host.WindowSize,
    55  		Payout:         totalPayout,
    56  		UnlockHash:     uc.UnlockHash(),
    57  		RevisionNumber: 0,
    58  		ValidProofOutputs: []types.SiacoinOutput{
    59  			// Outputs need to account for tax.
    60  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress}, // This is the renter payout, but with tax applied.
    61  			// Collateral is returned to host.
    62  			{Value: hostPayout, UnlockHash: host.UnlockHash},
    63  		},
    64  		MissedProofOutputs: []types.SiacoinOutput{
    65  			// Same as above.
    66  			{Value: types.PostTax(startHeight, totalPayout).Sub(hostPayout), UnlockHash: refundAddress},
    67  			// Same as above.
    68  			{Value: hostPayout, UnlockHash: host.UnlockHash},
    69  			// Once we start doing revisions, we'll move some coins to the host and some to the void.
    70  			{Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}},
    71  		},
    72  	}
    73  
    74  	// Build transaction containing fc, e.g. the File Contract.
    75  	err = txnBuilder.FundSiacoins(funding)
    76  	if err != nil {
    77  		return modules.RenterContract{}, err
    78  	}
    79  	txnBuilder.AddFileContract(fc)
    80  	// Add miner fee.
    81  	txnBuilder.AddMinerFee(txnFee)
    82  
    83  	// Create initial transaction set.
    84  	txn, parentTxns := txnBuilder.View()
    85  	unconfirmedParents, err := txnBuilder.UnconfirmedParents()
    86  	if err != nil {
    87  		return modules.RenterContract{}, err
    88  	}
    89  	txnSet := append(unconfirmedParents, append(parentTxns, txn)...)
    90  
    91  	// Increase Successful/Failed interactions accordingly
    92  	defer func() {
    93  		if err != nil {
    94  			hdb.IncrementFailedInteractions(host.PublicKey)
    95  			err = errors.Extend(err, modules.ErrHostFault)
    96  		} else {
    97  			hdb.IncrementSuccessfulInteractions(host.PublicKey)
    98  		}
    99  	}()
   100  
   101  	// Initiate connection.
   102  	dialer := &net.Dialer{
   103  		Cancel:  cancel,
   104  		Timeout: connTimeout,
   105  	}
   106  	conn, err := dialer.Dial("tcp", string(host.NetAddress))
   107  	if err != nil {
   108  		return modules.RenterContract{}, err
   109  	}
   110  	defer func() { _ = conn.Close() }()
   111  
   112  	// Allot time for sending RPC ID + verifySettings.
   113  	extendDeadline(conn, modules.NegotiateSettingsTime)
   114  	if err = encoding.WriteObject(conn, modules.RPCFormContract); err != nil {
   115  		return modules.RenterContract{}, err
   116  	}
   117  
   118  	// Verify the host's settings and confirm its identity.
   119  	host, err = verifySettings(conn, host)
   120  	if err != nil {
   121  		return modules.RenterContract{}, err
   122  	}
   123  	if !host.AcceptingContracts {
   124  		return modules.RenterContract{}, errors.New("host is not accepting contracts")
   125  	}
   126  
   127  	// Allot time for negotiation.
   128  	extendDeadline(conn, modules.NegotiateFileContractTime)
   129  
   130  	// Send acceptance, txn signed by us, and pubkey.
   131  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   132  		return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error())
   133  	}
   134  	if err = encoding.WriteObject(conn, txnSet); err != nil {
   135  		return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
   136  	}
   137  	if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil {
   138  		return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error())
   139  	}
   140  
   141  	// Read acceptance and txn signed by host.
   142  	if err = modules.ReadNegotiationAcceptance(conn); err != nil {
   143  		return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error())
   144  	}
   145  	// Host now sends any new parent transactions, inputs and outputs that
   146  	// were added to the transaction.
   147  	var newParents []types.Transaction
   148  	var newInputs []types.SiacoinInput
   149  	var newOutputs []types.SiacoinOutput
   150  	if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil {
   151  		return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error())
   152  	}
   153  	if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil {
   154  		return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error())
   155  	}
   156  	if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil {
   157  		return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error())
   158  	}
   159  
   160  	// Merge txnAdditions with txnSet.
   161  	txnBuilder.AddParents(newParents)
   162  	for _, input := range newInputs {
   163  		txnBuilder.AddSiacoinInput(input)
   164  	}
   165  	for _, output := range newOutputs {
   166  		txnBuilder.AddSiacoinOutput(output)
   167  	}
   168  
   169  	// Sign the txn.
   170  	signedTxnSet, err := txnBuilder.Sign(true)
   171  	if err != nil {
   172  		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error()))
   173  	}
   174  
   175  	// Calculate signatures added by the transaction builder.
   176  	var addedSignatures []types.TransactionSignature
   177  	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
   178  	for _, i := range addedSignatureIndices {
   179  		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
   180  	}
   181  
   182  	// create initial (no-op) revision, transaction, and signature
   183  	initRevision := types.FileContractRevision{
   184  		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
   185  		UnlockConditions:  uc,
   186  		NewRevisionNumber: 1,
   187  
   188  		NewFileSize:           fc.FileSize,
   189  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   190  		NewWindowStart:        fc.WindowStart,
   191  		NewWindowEnd:          fc.WindowEnd,
   192  		NewValidProofOutputs:  fc.ValidProofOutputs,
   193  		NewMissedProofOutputs: fc.MissedProofOutputs,
   194  		NewUnlockHash:         fc.UnlockHash,
   195  	}
   196  	renterRevisionSig := types.TransactionSignature{
   197  		ParentID:       crypto.Hash(initRevision.ParentID),
   198  		PublicKeyIndex: 0,
   199  		CoveredFields: types.CoveredFields{
   200  			FileContractRevisions: []uint64{0},
   201  		},
   202  	}
   203  	revisionTxn := types.Transaction{
   204  		FileContractRevisions: []types.FileContractRevision{initRevision},
   205  		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
   206  	}
   207  	encodedSig := crypto.SignHash(revisionTxn.SigHash(0, startHeight), ourSK)
   208  	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]
   209  
   210  	// Send acceptance and signatures.
   211  	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
   212  		return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error())
   213  	}
   214  	if err = encoding.WriteObject(conn, addedSignatures); err != nil {
   215  		return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error())
   216  	}
   217  	if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil {
   218  		return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error())
   219  	}
   220  
   221  	// Read the host acceptance and signatures.
   222  	err = modules.ReadNegotiationAcceptance(conn)
   223  	if err != nil {
   224  		return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error())
   225  	}
   226  	var hostSigs []types.TransactionSignature
   227  	if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil {
   228  		return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error())
   229  	}
   230  	for _, sig := range hostSigs {
   231  		txnBuilder.AddTransactionSignature(sig)
   232  	}
   233  	var hostRevisionSig types.TransactionSignature
   234  	if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil {
   235  		return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error())
   236  	}
   237  	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig)
   238  
   239  	// Construct the final transaction.
   240  	txn, parentTxns = txnBuilder.View()
   241  	txnSet = append(parentTxns, txn)
   242  
   243  	// Submit to blockchain.
   244  	err = tpool.AcceptTransactionSet(txnSet)
   245  	if err == modules.ErrDuplicateTransactionSet {
   246  		// As long as it made it into the transaction pool, we're good.
   247  		err = nil
   248  	}
   249  	if err != nil {
   250  		return modules.RenterContract{}, err
   251  	}
   252  
   253  	// Construct contract header.
   254  	header := contractHeader{
   255  		Transaction: revisionTxn,
   256  		SecretKey:   ourSK,
   257  		StartHeight: startHeight,
   258  		TotalCost:   funding,
   259  		ContractFee: host.ContractPrice,
   260  		TxnFee:      txnFee,
   261  		SiafundFee:  types.Tax(startHeight, fc.Payout),
   262  		Utility: modules.ContractUtility{
   263  			GoodForUpload: true,
   264  			GoodForRenew:  true,
   265  		},
   266  	}
   267  
   268  	// Add contract to set.
   269  	meta, err := cs.managedInsertContract(header, nil) // no Merkle roots yet
   270  	if err != nil {
   271  		return modules.RenterContract{}, err
   272  	}
   273  	return meta, nil
   274  }