gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/negotiate.go (about)

     1  package host
     2  
     3  import (
     4  	"time"
     5  
     6  	"gitlab.com/SiaPrime/SiaPrime/build"
     7  	"gitlab.com/SiaPrime/SiaPrime/crypto"
     8  	"gitlab.com/SiaPrime/SiaPrime/modules"
     9  	"gitlab.com/SiaPrime/SiaPrime/types"
    10  )
    11  
    12  var (
    13  	// errBadContractOutputCounts is returned if the presented file contract
    14  	// revision has the wrong number of outputs for either the valid or the
    15  	// missed proof outputs.
    16  	errBadContractOutputCounts = ErrorCommunication("rejected for having an unexpected number of outputs")
    17  
    18  	// errBadContractParent is returned when a file contract revision is
    19  	// presented which has a parent id that doesn't match the file contract
    20  	// which is supposed to be getting revised.
    21  	errBadContractParent = ErrorCommunication("could not find contract's parent")
    22  
    23  	// errBadFileMerkleRoot is returned if the renter incorrectly updates the
    24  	// file merkle root during a file contract revision.
    25  	errBadFileMerkleRoot = ErrorCommunication("rejected for bad file merkle root")
    26  
    27  	// errBadFileSize is returned if the renter incorrectly download and
    28  	// changes the file size during a file contract revision.
    29  	errBadFileSize = ErrorCommunication("rejected for bad file size")
    30  
    31  	// errBadModificationIndex is returned if the renter requests a change on a
    32  	// sector root that is not in the file contract.
    33  	errBadModificationIndex = ErrorCommunication("renter has made a modification that points to a nonexistent sector")
    34  
    35  	// errBadParentID is returned if the renter incorrectly download and
    36  	// provides the wrong parent id during a file contract revision.
    37  	errBadParentID = ErrorCommunication("rejected for bad parent id")
    38  
    39  	// errBadPayoutUnlockHashes is returned if the renter incorrectly sets the
    40  	// payout unlock hashes during contract formation.
    41  	errBadPayoutUnlockHashes = ErrorCommunication("rejected for bad unlock hashes in the payout")
    42  
    43  	// errBadRevisionNumber number is returned if the renter incorrectly
    44  	// download and does not increase the revision number during a file
    45  	// contract revision.
    46  	errBadRevisionNumber = ErrorCommunication("rejected for bad revision number")
    47  
    48  	// errBadSectorSize is returned if the renter provides a sector to be
    49  	// inserted that is the wrong size.
    50  	errBadSectorSize = ErrorCommunication("renter has provided an incorrectly sized sector")
    51  
    52  	// errBadUnlockConditions is returned if the renter incorrectly download
    53  	// and does not provide the right unlock conditions in the payment
    54  	// revision.
    55  	errBadUnlockConditions = ErrorCommunication("rejected for bad unlock conditions")
    56  
    57  	// errBadUnlockHash is returned if the renter incorrectly updates the
    58  	// unlock hash during a file contract revision.
    59  	errBadUnlockHash = ErrorCommunication("rejected for bad new unlock hash")
    60  
    61  	// errBadWindowEnd is returned if the renter incorrectly download and
    62  	// changes the window end during a file contract revision.
    63  	errBadWindowEnd = ErrorCommunication("rejected for bad new window end")
    64  
    65  	// errBadWindowStart is returned if the renter incorrectly updates the
    66  	// window start during a file contract revision.
    67  	errBadWindowStart = ErrorCommunication("rejected for bad new window start")
    68  
    69  	// errEarlyWindow is returned if the file contract provided by the renter
    70  	// has a storage proof window that is starting too near in the future.
    71  	errEarlyWindow = ErrorCommunication("rejected for a window that starts too soon")
    72  
    73  	// errEmptyObject is returned if the renter sends an empty or nil object
    74  	// unexpectedly.
    75  	errEmptyObject = ErrorCommunication("renter has unexpectedly send an empty/nil object")
    76  
    77  	// errHighRenterMissedOutput is returned if the renter incorrectly download
    78  	// and deducts an insufficient amount from the renter missed outputs during
    79  	// a file contract revision.
    80  	errHighRenterMissedOutput = ErrorCommunication("rejected for high paying renter missed output")
    81  
    82  	// errHighRenterValidOutput is returned if the renter incorrectly download
    83  	// and deducts an insufficient amount from the renter valid outputs during
    84  	// a file contract revision.
    85  	errHighRenterValidOutput = ErrorCommunication("rejected for high paying renter valid output")
    86  
    87  	// errIllegalOffsetAndLength is returned if the renter tries perform a
    88  	// modify operation that uses a troublesome combination of offset and
    89  	// length.
    90  	errIllegalOffsetAndLength = ErrorCommunication("renter is trying to do a modify with an illegal offset and length")
    91  
    92  	// errLargeSector is returned if the renter sends a RevisionAction that has
    93  	// data which creates a sector that is larger than what the host uses.
    94  	errLargeSector = ErrorCommunication("renter has sent a sector that exceeds the host's sector size")
    95  
    96  	// errLateRevision is returned if the renter is attempting to revise a
    97  	// revision after the revision deadline. The host needs time to submit the
    98  	// final revision to the blockchain to guarantee payment, and therefore
    99  	// will not accept revisions once the window start is too close.
   100  	errLateRevision = ErrorCommunication("renter is requesting revision after the revision deadline")
   101  
   102  	// errLongDuration is returned if the renter proposes a file contract with
   103  	// an experation that is too far into the future according to the host's
   104  	// settings.
   105  	errLongDuration = ErrorCommunication("renter proposed a file contract with a too-long duration")
   106  
   107  	// errLowHostMissedOutput is returned if the renter incorrectly updates the
   108  	// host missed proof output during a file contract revision.
   109  	errLowHostMissedOutput = ErrorCommunication("rejected for low paying host missed output")
   110  
   111  	// errLowHostValidOutput is returned if the renter incorrectly updates the
   112  	// host valid proof output during a file contract revision.
   113  	errLowHostValidOutput = ErrorCommunication("rejected for low paying host valid output")
   114  
   115  	// errLowTransactionFees is returned if the renter provides a transaction
   116  	// that the host does not feel is able to make it onto the blockchain.
   117  	errLowTransactionFees = ErrorCommunication("rejected for including too few transaction fees")
   118  
   119  	// errLowVoidOutput is returned if the renter has not allocated enough
   120  	// funds to the void output.
   121  	errLowVoidOutput = ErrorCommunication("rejected for low value void output")
   122  
   123  	// errMismatchedHostPayouts is returned if the renter incorrectly sets the
   124  	// host valid and missed payouts to different values during contract
   125  	// formation.
   126  	errMismatchedHostPayouts = ErrorCommunication("rejected because host valid and missed payouts are not the same value")
   127  
   128  	// errSmallWindow is returned if the renter suggests a storage proof window
   129  	// that is too small.
   130  	errSmallWindow = ErrorCommunication("rejected for small window size")
   131  
   132  	// errUnknownModification is returned if the host receives a modification
   133  	// action from the renter that it does not understand.
   134  	errUnknownModification = ErrorCommunication("renter is attempting an action that the host does not understand")
   135  )
   136  
   137  // createRevisionSignature creates a signature for a file contract revision
   138  // that signs on the file contract revision. The renter should have already
   139  // provided the signature. createRevisionSignature will check to make sure that
   140  // the renter's signature is valid.
   141  func createRevisionSignature(fcr types.FileContractRevision, renterSig types.TransactionSignature, secretKey crypto.SecretKey, blockHeight types.BlockHeight) (types.Transaction, error) {
   142  	hostSig := types.TransactionSignature{
   143  		ParentID:       crypto.Hash(fcr.ParentID),
   144  		PublicKeyIndex: 1,
   145  		CoveredFields: types.CoveredFields{
   146  			FileContractRevisions: []uint64{0},
   147  		},
   148  	}
   149  	txn := types.Transaction{
   150  		FileContractRevisions: []types.FileContractRevision{fcr},
   151  		TransactionSignatures: []types.TransactionSignature{renterSig, hostSig},
   152  	}
   153  	sigHash := txn.SigHash(1, blockHeight)
   154  	encodedSig := crypto.SignHash(sigHash, secretKey)
   155  	txn.TransactionSignatures[1].Signature = encodedSig[:]
   156  	err := modules.VerifyFileContractRevisionTransactionSignatures(fcr, txn.TransactionSignatures, blockHeight)
   157  	if err != nil {
   158  		return types.Transaction{}, err
   159  	}
   160  	return txn, nil
   161  }
   162  
   163  // managedFinalizeContract will take a file contract, add the host's
   164  // collateral, and then try submitting the file contract to the transaction
   165  // pool. If there is no error, the completed transaction set will be returned
   166  // to the caller.
   167  func (h *Host) managedFinalizeContract(builder modules.TransactionBuilder, renterPK crypto.PublicKey, renterSignatures []types.TransactionSignature, renterRevisionSignature types.TransactionSignature, initialSectorRoots []crypto.Hash, hostCollateral, hostInitialRevenue, hostInitialRisk types.Currency, settings modules.HostExternalSettings) ([]types.TransactionSignature, types.TransactionSignature, types.FileContractID, error) {
   168  	for _, sig := range renterSignatures {
   169  		builder.AddTransactionSignature(sig)
   170  	}
   171  	fullTxnSet, err := builder.Sign(true)
   172  	if err != nil {
   173  		builder.Drop()
   174  		return nil, types.TransactionSignature{}, types.FileContractID{}, err
   175  	}
   176  
   177  	// Verify that the signature for the revision from the renter is correct.
   178  	h.mu.RLock()
   179  	blockHeight := h.blockHeight
   180  	hostSPK := h.publicKey
   181  	hostSK := h.secretKey
   182  	h.mu.RUnlock()
   183  	contractTxn := fullTxnSet[len(fullTxnSet)-1]
   184  	fc := contractTxn.FileContracts[0]
   185  	noOpRevision := types.FileContractRevision{
   186  		ParentID: contractTxn.FileContractID(0),
   187  		UnlockConditions: types.UnlockConditions{
   188  			PublicKeys: []types.SiaPublicKey{
   189  				types.Ed25519PublicKey(renterPK),
   190  				hostSPK,
   191  			},
   192  			SignaturesRequired: 2,
   193  		},
   194  		NewRevisionNumber: fc.RevisionNumber + 1,
   195  
   196  		NewFileSize:           fc.FileSize,
   197  		NewFileMerkleRoot:     fc.FileMerkleRoot,
   198  		NewWindowStart:        fc.WindowStart,
   199  		NewWindowEnd:          fc.WindowEnd,
   200  		NewValidProofOutputs:  fc.ValidProofOutputs,
   201  		NewMissedProofOutputs: fc.MissedProofOutputs,
   202  		NewUnlockHash:         fc.UnlockHash,
   203  	}
   204  	// createRevisionSignature will also perform validation on the result,
   205  	// returning an error if the renter provided an incorrect signature.
   206  	revisionTransaction, err := createRevisionSignature(noOpRevision, renterRevisionSignature, hostSK, blockHeight)
   207  	if err != nil {
   208  		return nil, types.TransactionSignature{}, types.FileContractID{}, err
   209  	}
   210  
   211  	// Create and add the storage obligation for this file contract.
   212  	fullTxn, _ := builder.View()
   213  	so := storageObligation{
   214  		SectorRoots: initialSectorRoots,
   215  
   216  		ContractCost:            settings.ContractPrice,
   217  		LockedCollateral:        hostCollateral,
   218  		PotentialStorageRevenue: hostInitialRevenue,
   219  		RiskedCollateral:        hostInitialRisk,
   220  
   221  		NegotiationHeight: blockHeight,
   222  
   223  		OriginTransactionSet:   fullTxnSet,
   224  		RevisionTransactionSet: []types.Transaction{revisionTransaction},
   225  	}
   226  
   227  	// Get a lock on the storage obligation.
   228  	lockErr := h.managedTryLockStorageObligation(so.id(), obligationLockTimeout)
   229  	if lockErr != nil {
   230  		build.Critical("failed to get a lock on a brand new storage obligation")
   231  		return nil, types.TransactionSignature{}, types.FileContractID{}, lockErr
   232  	}
   233  	defer func() {
   234  		if err != nil {
   235  			h.managedUnlockStorageObligation(so.id())
   236  		}
   237  	}()
   238  
   239  	// addStorageObligation will submit the transaction to the transaction
   240  	// pool, and will only do so if there was not some error in creating the
   241  	// storage obligation. If the transaction pool returns a consensus
   242  	// conflict, wait 30 seconds and try again.
   243  	err = func() error {
   244  		// Try adding the storage obligation. If there's an error, wait a few
   245  		// seconds and try again. Eventually time out. It should be noted that
   246  		// the storage obligation locking is both crappy and incomplete, and
   247  		// that I'm not sure how this timeout plays with the overall host
   248  		// timeouts.
   249  		//
   250  		// The storage obligation locks should occur at the highest level, not
   251  		// just when the actual modification is happening.
   252  		i := 0
   253  		for {
   254  			err = h.managedAddStorageObligation(so)
   255  			if err == nil {
   256  				return nil
   257  			}
   258  			if err != nil && i > 4 {
   259  				h.log.Println(err)
   260  				builder.Drop()
   261  				return err
   262  			}
   263  
   264  			i++
   265  			if build.Release == "standard" {
   266  				time.Sleep(time.Second * 15)
   267  			}
   268  		}
   269  	}()
   270  	if err != nil {
   271  		return nil, types.TransactionSignature{}, types.FileContractID{}, err
   272  	}
   273  
   274  	// Get the host's transaction signatures from the builder.
   275  	var hostTxnSignatures []types.TransactionSignature
   276  	_, _, _, txnSigIndices := builder.ViewAdded()
   277  	for _, sigIndex := range txnSigIndices {
   278  		hostTxnSignatures = append(hostTxnSignatures, fullTxn.TransactionSignatures[sigIndex])
   279  	}
   280  	return hostTxnSignatures, revisionTransaction.TransactionSignatures[1], so.id(), nil
   281  }