github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/host/negotiatedownload.go (about)

     1  package host
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/encoding"
     9  	"github.com/NebulousLabs/Sia/modules"
    10  	"github.com/NebulousLabs/Sia/types"
    11  )
    12  
    13  var (
    14  	// errDownloadBadHostValidOutputs is returned if the renter requests a
    15  	// download and pays an insufficient amount to the host valid addresses.
    16  	errDownloadBadHostValidOutputs = errors.New("download request rejected for bad host valid outputs")
    17  
    18  	// errDownloadBadNewFileMerkleRoot is returned if the renter requests a
    19  	// download and changes the file merkle root in the payment revision.
    20  	errDownloadBadNewFileMerkleRoot = errors.New("download request rejected for bad file merkle root")
    21  
    22  	// errDownloadBadNewFileSize is returned if the renter requests a download
    23  	// and changes the file size in the payment revision.
    24  	errDownloadBadNewFileSize = errors.New("download request rejected for bad file size")
    25  
    26  	// errDownloadBadHostMissedOutputs is returned if the renter requests a
    27  	// download and changes the host missed outputs in the payment revision.
    28  	errDownloadBadHostMissedOutputs = errors.New("download request rejected for bad host missed outputs")
    29  
    30  	// errDownloadBadNewWindowEnd is returned if the renter requests a download
    31  	// and changes the window end in the payment revision.
    32  	errDownloadBadNewWindowEnd = errors.New("download request rejected for bad new window end")
    33  
    34  	// errDownloadBadNewWindowStart is returned if the renter requests a
    35  	// download and changes the window start in the payment revision.
    36  	errDownloadBadNewWindowStart = errors.New("download request rejected for bad new window start")
    37  
    38  	// errDownloadBadNewUnlockHash is returned if the renter requests a
    39  	// download and changes the unlock hash in the payment revision.
    40  	errDownloadBadNewUnlockHash = errors.New("download request rejected for bad new unlock hash")
    41  
    42  	// errDownloadBadParentID is returned if the renter requests a download and
    43  	// provides the wrong parent id in the payment revision.
    44  	errDownloadBadParentID = errors.New("download request rejected for bad parent id")
    45  
    46  	// errDownloadBadRenterMissedOutputs is returned if the renter requests a
    47  	// download and deducts an insufficient amount from the renter missed
    48  	// outputs in the payment revision.
    49  	errDownloadBadRenterMissedOutputs = errors.New("download request rejected for bad renter missed outputs")
    50  
    51  	// errDownloadBadRenterValidOutputs is returned if the renter requests a
    52  	// download and deducts an insufficient amount from the renter valid
    53  	// outputs in the payment revision.
    54  	errDownloadBadRenterValidOutputs = errors.New("download request rejected for bad renter valid outputs")
    55  
    56  	// errDownloadBadRevision number is returned if the renter requests a
    57  	// download and does not increase the revision number in the payment
    58  	// revision.
    59  	errDownloadBadRevisionNumber = errors.New("download request rejected for bad revision number")
    60  
    61  	// errDownloadBadUnlockConditions is returned if the renter requests a
    62  	// download and does not provide the right unlock conditions in the payment
    63  	// revision.
    64  	errDownloadBadUnlockConditions = errors.New("download request rejected for bad unlock conditions")
    65  
    66  	// errDownloadBadVoidOutputs is returned if the renter requests a download
    67  	// and does not add sufficient payment to the void outputs in the payment
    68  	// revision.
    69  	errDownloadBadVoidOutputs = errors.New("download request rejected for bad void outputs")
    70  
    71  	// errLargeDownloadBatch is returned if the renter requests a download
    72  	// batch that exceeds the maximum batch size that the host will
    73  	// accomondate.
    74  	errLargeDownloadBatch = errors.New("download request exceeded maximum batch size")
    75  
    76  	// errRequestOutOfBounds is returned when a download request is made which
    77  	// asks for elements of a sector which do not exist.
    78  	errRequestOutOfBounds = errors.New("download request has invalid sector bounds")
    79  )
    80  
    81  // managedDownloadIteration is responsible for managing a single iteration of
    82  // the download loop for RPCDownload.
    83  func (h *Host) managedDownloadIteration(conn net.Conn, so *storageObligation) error {
    84  	// Exchange settings with the renter.
    85  	err := h.managedRPCSettings(conn)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	// Extend the deadline for the download.
    91  	conn.SetDeadline(time.Now().Add(modules.NegotiateDownloadTime))
    92  
    93  	// The renter will either accept or reject the host's settings.
    94  	err = modules.ReadNegotiationAcceptance(conn)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	// Grab a set of variables that will be useful later in the function.
   100  	h.mu.RLock()
   101  	blockHeight := h.blockHeight
   102  	secretKey := h.secretKey
   103  	settings := h.settings
   104  	h.mu.RUnlock()
   105  
   106  	// Read the download requests, followed by the file contract revision that
   107  	// pays for them.
   108  	var requests []modules.DownloadAction
   109  	var paymentRevision types.FileContractRevision
   110  	err = encoding.ReadObject(conn, &requests, modules.NegotiateMaxDownloadActionRequestSize)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	err = encoding.ReadObject(conn, &paymentRevision, modules.NegotiateMaxFileContractRevisionSize)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	// Verify that the request is acceptable, and then fetch all of the data
   120  	// for the renter.
   121  	existingRevision := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0]
   122  	var payload [][]byte
   123  	err = func() error {
   124  		// Check that the length of each file is in-bounds, and that the total
   125  		// size being requested is acceptable.
   126  		var totalSize uint64
   127  		for _, request := range requests {
   128  			if request.Length > modules.SectorSize || request.Offset+request.Length > modules.SectorSize {
   129  				return errRequestOutOfBounds
   130  			}
   131  			totalSize += request.Length
   132  		}
   133  		if totalSize > settings.MaxDownloadBatchSize {
   134  			return errLargeDownloadBatch
   135  		}
   136  
   137  		// Verify that the correct amount of money has been moved from the
   138  		// renter's contract funds to the host's contract funds.
   139  		expectedTransfer := settings.MinimumDownloadBandwidthPrice.Mul64(totalSize)
   140  		err = verifyPaymentRevision(existingRevision, paymentRevision, blockHeight, expectedTransfer)
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		// Load the sectors and build the data payload.
   146  		for _, request := range requests {
   147  			sectorData, err := h.ReadSector(request.MerkleRoot)
   148  			if err != nil {
   149  				return err
   150  			}
   151  			payload = append(payload, sectorData[request.Offset:request.Offset+request.Length])
   152  		}
   153  		return nil
   154  	}()
   155  	if err != nil {
   156  		return modules.WriteNegotiationRejection(conn, err)
   157  	}
   158  	// Revision is acceptable, write acceptance.
   159  	err = modules.WriteNegotiationAcceptance(conn)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	// Renter will send a transaction siganture for the file contract revision.
   165  	var renterSignature types.TransactionSignature
   166  	err = encoding.ReadObject(conn, &renterSignature, modules.NegotiateMaxTransactionSignatureSize)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	txn, err := createRevisionSignature(paymentRevision, renterSignature, secretKey, blockHeight)
   171  
   172  	// Update the storage obligation.
   173  	paymentTransfer := existingRevision.NewValidProofOutputs[0].Value.Sub(paymentRevision.NewValidProofOutputs[0].Value)
   174  	so.PotentialDownloadRevenue = so.PotentialDownloadRevenue.Add(paymentTransfer)
   175  	so.RevisionTransactionSet = []types.Transaction{{
   176  		FileContractRevisions: []types.FileContractRevision{paymentRevision},
   177  		TransactionSignatures: []types.TransactionSignature{renterSignature, txn.TransactionSignatures[1]},
   178  	}}
   179  	err = h.modifyStorageObligation(so, nil, nil, nil)
   180  	if err != nil {
   181  		return modules.WriteNegotiationRejection(conn, err)
   182  	}
   183  
   184  	// Write acceptance to the renter - the data request can be fulfilled by
   185  	// the host, the payment is satisfactory, signature is correct. Then send
   186  	// the host signature and all of the data.
   187  	err = modules.WriteNegotiationAcceptance(conn)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	err = encoding.WriteObject(conn, txn.TransactionSignatures[1])
   192  	if err != nil {
   193  		return err
   194  	}
   195  	return encoding.WriteObject(conn, payload)
   196  }
   197  
   198  // verifyPaymentRevision verifies that the revision being provided to pay for
   199  // the data has transferred the expected amount of money from the renter to the
   200  // host.
   201  func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractRevision, blockHeight types.BlockHeight, expectedTransfer types.Currency) error {
   202  	// Check that the revision is well-formed.
   203  	if len(paymentRevision.NewValidProofOutputs) != 2 || len(paymentRevision.NewMissedProofOutputs) != 3 {
   204  		return errInsaneFileContractRevisionOutputCounts
   205  	}
   206  
   207  	// Check that the time to finalize and submit the file contract revision
   208  	// has not already passed.
   209  	if existingRevision.NewWindowStart-revisionSubmissionBuffer <= blockHeight {
   210  		return errLateRevision
   211  	}
   212  
   213  	// The new revenue comes out of the renter's valid outputs.
   214  	if paymentRevision.NewValidProofOutputs[0].Value.Add(expectedTransfer).Cmp(existingRevision.NewValidProofOutputs[0].Value) > 0 {
   215  		return errDownloadBadRenterValidOutputs
   216  	}
   217  	// The new revenue goes into the host's valid outputs.
   218  	if existingRevision.NewValidProofOutputs[1].Value.Add(expectedTransfer).Cmp(paymentRevision.NewValidProofOutputs[1].Value) < 0 {
   219  		return errDownloadBadHostValidOutputs
   220  	}
   221  	// The new revenue comes out of the renter's missed outputs.
   222  	if paymentRevision.NewMissedProofOutputs[0].Value.Add(expectedTransfer).Cmp(existingRevision.NewMissedProofOutputs[0].Value) > 0 {
   223  		return errDownloadBadRenterMissedOutputs
   224  	}
   225  	// The new revenue goes into the void outputs.
   226  	if existingRevision.NewMissedProofOutputs[2].Value.Add(expectedTransfer).Cmp(paymentRevision.NewMissedProofOutputs[2].Value) < 0 {
   227  		return errDownloadBadVoidOutputs
   228  	}
   229  	// Check that the revision count has increased.
   230  	if paymentRevision.NewRevisionNumber <= existingRevision.NewRevisionNumber {
   231  		return errDownloadBadRevisionNumber
   232  	}
   233  
   234  	// Check that all of the non-volatile fields are the same.
   235  	if paymentRevision.ParentID != existingRevision.ParentID {
   236  		return errDownloadBadParentID
   237  	}
   238  	if paymentRevision.UnlockConditions.UnlockHash() != existingRevision.UnlockConditions.UnlockHash() {
   239  		return errDownloadBadUnlockConditions
   240  	}
   241  	if paymentRevision.NewFileSize != existingRevision.NewFileSize {
   242  		return errDownloadBadNewFileSize
   243  	}
   244  	if paymentRevision.NewFileMerkleRoot != existingRevision.NewFileMerkleRoot {
   245  		return errDownloadBadNewFileMerkleRoot
   246  	}
   247  	if paymentRevision.NewWindowStart != existingRevision.NewWindowStart {
   248  		return errDownloadBadNewWindowStart
   249  	}
   250  	if paymentRevision.NewWindowEnd != existingRevision.NewWindowEnd {
   251  		return errDownloadBadNewWindowEnd
   252  	}
   253  	if paymentRevision.NewUnlockHash != existingRevision.NewUnlockHash {
   254  		return errDownloadBadNewUnlockHash
   255  	}
   256  	if paymentRevision.NewMissedProofOutputs[1].Value.Cmp(existingRevision.NewMissedProofOutputs[1].Value) != 0 {
   257  		return errDownloadBadHostMissedOutputs
   258  	}
   259  	return nil
   260  }
   261  
   262  // managedRPCDownload is responsible for handling an RPC request from the
   263  // renter to download data.
   264  func (h *Host) managedRPCDownload(conn net.Conn) error {
   265  	// Get the start time to limit the length of the whole connection.
   266  	startTime := time.Now()
   267  	// Perform the file contract revision exchange, giving the renter the most
   268  	// recent file contract revision and getting the storage obligation that
   269  	// will be used to pay for the data.
   270  	_, so, err := h.managedRPCRecentRevision(conn)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	// Lock the storage obligation during the revision.
   276  	h.mu.Lock()
   277  	err = h.lockStorageObligation(so)
   278  	h.mu.Unlock()
   279  	if err != nil {
   280  		return err
   281  	}
   282  	defer func() {
   283  		h.mu.Lock()
   284  		err = h.unlockStorageObligation(so)
   285  		h.mu.Unlock()
   286  		if err != nil {
   287  			h.log.Critical(err)
   288  		}
   289  	}()
   290  
   291  	// Perform a loop that will allow downloads to happen until the maximum
   292  	// time for a single connection has been reached.
   293  	for time.Now().Before(startTime.Add(iteratedConnectionTime)) {
   294  		err := h.managedDownloadIteration(conn, so)
   295  		if err == modules.ErrStopResponse {
   296  			// The renter has indicated that it has finished downloading the
   297  			// data, therefore there is no error. Return nil.
   298  			return nil
   299  		} else if err != nil {
   300  			return err
   301  		}
   302  	}
   303  	return nil
   304  }