storj.io/uplink@v1.13.0/private/piecestore/upload.go (about)

     1  // Copyright (C) 2019 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package piecestore
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"hash"
    10  	"io"
    11  	"time"
    12  
    13  	"github.com/spacemonkeygo/monkit/v3"
    14  	"github.com/zeebo/errs"
    15  
    16  	"storj.io/common/context2"
    17  	"storj.io/common/identity"
    18  	"storj.io/common/pb"
    19  	"storj.io/common/signing"
    20  	"storj.io/common/storj"
    21  	"storj.io/common/sync2"
    22  	"storj.io/drpc"
    23  )
    24  
    25  var mon = monkit.Package()
    26  
    27  // Upload implements uploading to the storage node.
    28  type upload struct {
    29  	client     *Client
    30  	limit      *pb.OrderLimit
    31  	privateKey storj.PiecePrivateKey
    32  	nodeID     storj.NodeID
    33  	stream     uploadStream
    34  
    35  	nextRequest *pb.PieceUploadRequest
    36  
    37  	hash          hash.Hash // TODO: use concrete implementation
    38  	hashAlgorithm pb.PieceHashAlgorithm
    39  	offset        int64
    40  	orderStep     int64
    41  
    42  	// when there's a send error then it will automatically close
    43  	finished bool
    44  }
    45  
    46  type uploadStream interface {
    47  	Context() context.Context
    48  	Close() error
    49  	Send(*pb.PieceUploadRequest) error
    50  	CloseAndRecv() (*pb.PieceUploadResponse, error)
    51  }
    52  
    53  // UploadReader uploads to the storage node.
    54  func (client *Client) UploadReader(ctx context.Context, limit *pb.OrderLimit, piecePrivateKey storj.PiecePrivateKey, data io.Reader) (hash *pb.PieceHash, err error) {
    55  	defer mon.Task()(&ctx, "node: "+limit.StorageNodeId.String()[0:8])(&err)
    56  
    57  	ctx, cancel := context2.WithCustomCancel(ctx)
    58  	defer cancel(context.Canceled)
    59  
    60  	var underlyingStream uploadStream
    61  	sync2.WithTimeout(client.config.MessageTimeout, func() {
    62  		if client.replaySafe != nil {
    63  			underlyingStream, err = client.replaySafe.Upload(ctx)
    64  		} else {
    65  			underlyingStream, err = client.client.Upload(ctx)
    66  		}
    67  	}, func() { cancel(errMessageTimeout) })
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	defer func() { _ = underlyingStream.Close() }()
    72  
    73  	stream := &timedUploadStream{
    74  		timeout: client.config.MessageTimeout,
    75  		stream:  underlyingStream,
    76  		cancel:  cancel,
    77  	}
    78  
    79  	nextRequest := &pb.PieceUploadRequest{
    80  		Limit:         limit,
    81  		HashAlgorithm: client.UploadHashAlgo,
    82  	}
    83  	if client.NodeURL().DebounceLimit > 0 {
    84  		// in this case, the storage node is running code late enough that it will be able to handle
    85  		// aggregated requests entirely. this is the best case and we don't need to use drpc stream
    86  		// corking. this is because storage nodes that advertise their debounce limit
    87  		// also have the change that support aggregated request limits.
    88  
    89  		// leave nextRequest alone, so nothing to do!
    90  	} else {
    91  		// okay, let's see if we can do drpc stream corking.
    92  		if streamGetter, ok := underlyingStream.(interface {
    93  			GetStream() drpc.Stream
    94  		}); ok {
    95  			if flusher, ok := streamGetter.GetStream().(interface {
    96  				SetManualFlush(bool)
    97  			}); ok {
    98  				// we can. let's send the next request and empty the nextRequest variable.
    99  				flusher.SetManualFlush(true)
   100  				err = stream.Send(nextRequest)
   101  				flusher.SetManualFlush(false)
   102  				nextRequest = nil
   103  				// err checking below.
   104  			}
   105  		}
   106  		if nextRequest != nil {
   107  			// okay here, we are not in the DebounceLimit > 0 case, but we did not discover
   108  			// we could do stream corking, so, give up I guess, just send as-is.
   109  			err = stream.Send(nextRequest)
   110  			nextRequest = nil
   111  			// err checking below.
   112  		}
   113  
   114  		if err != nil {
   115  			_, closeErr := stream.CloseAndRecv()
   116  			switch {
   117  			case !errors.Is(err, io.EOF) && closeErr != nil:
   118  				err = ErrProtocol.Wrap(errs.Combine(err, closeErr))
   119  			case closeErr != nil:
   120  				err = ErrProtocol.Wrap(closeErr)
   121  			}
   122  
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	upload := &upload{
   128  		client:        client,
   129  		limit:         limit,
   130  		privateKey:    piecePrivateKey,
   131  		nodeID:        limit.StorageNodeId,
   132  		stream:        stream,
   133  		hash:          pb.NewHashFromAlgorithm(client.UploadHashAlgo),
   134  		hashAlgorithm: client.UploadHashAlgo,
   135  		offset:        0,
   136  		orderStep:     client.config.InitialStep,
   137  		nextRequest:   nextRequest,
   138  	}
   139  
   140  	return upload.write(ctx, data)
   141  }
   142  
   143  // write sends all data to the storagenode allocating as necessary.
   144  func (client *upload) write(ctx context.Context, data io.Reader) (hash *pb.PieceHash, err error) {
   145  	defer mon.Task()(&ctx, "node: "+client.nodeID.String()[0:8])(&err)
   146  
   147  	defer func() {
   148  		if err != nil {
   149  			err = errs.Combine(err, client.cancel(ctx))
   150  			return
   151  		}
   152  	}()
   153  
   154  	// write the hash of the data sent to the server
   155  	data = io.TeeReader(data, client.hash)
   156  
   157  	// Some facts about uploads
   158  	//  * Signing orders are CPU intensive, so we don't want to do them too often.
   159  	//  * Buffering data in RAM is resource intensive, so we don't want to buffer
   160  	//    much.
   161  	//  * We don't pay for upload bandwidth, so there's not a ton of benefit to
   162  	//    even having upload orders other than making sure we can measure
   163  	//    user bandwidth usage well.
   164  	//  So, to address these things, we're going to read in a small increment
   165  	// (maybe config.InitialStep I guess) consistently, throughout the entire
   166  	// operation. We're going to keep track of how much we've written, and if
   167  	// the current write requires us to send an order with a larger amount in
   168  	// it, only then will we sign. Most writes won't include an order.
   169  
   170  	backingArray := make([]byte, client.client.config.UploadBufferSize)
   171  
   172  	var orderedSoFar int64
   173  
   174  	done := false
   175  	for !done {
   176  		// read the next amount
   177  		sendData := backingArray
   178  		n, readErr := tryReadFull(ctx, data, sendData)
   179  		if readErr != nil {
   180  			if !errors.Is(readErr, io.EOF) {
   181  				return nil, ErrInternal.Wrap(readErr)
   182  			}
   183  			done = true
   184  		}
   185  		if n <= 0 {
   186  			continue
   187  		}
   188  		sendData = sendData[:n]
   189  
   190  		req := client.nextRequest
   191  		client.nextRequest = nil
   192  		if req == nil {
   193  			req = &pb.PieceUploadRequest{}
   194  		}
   195  		req.Chunk = &pb.PieceUploadRequest_Chunk{
   196  			Offset: client.offset,
   197  			Data:   sendData,
   198  		}
   199  
   200  		if client.offset+int64(len(sendData)) > orderedSoFar {
   201  			// okay, create the next signed order.
   202  			// Note: it might be larger than we need! in the worst
   203  			// case, if there's only one byte here and we're at the
   204  			// max order step, we will overshoot by
   205  			// MaximumStepSize - 1.
   206  			// But that's okay. Upload bandwidth is free.
   207  
   208  			orderedSoFar = min(client.offset+client.orderStep, client.limit.Limit)
   209  
   210  			order, err := signing.SignUplinkOrder(ctx, client.privateKey, &pb.Order{
   211  				SerialNumber: client.limit.SerialNumber,
   212  				Amount:       orderedSoFar,
   213  			})
   214  			if err != nil {
   215  				return nil, ErrInternal.Wrap(err)
   216  			}
   217  			req.Order = order
   218  			// update order step, incrementally building trust
   219  			client.orderStep = client.client.nextOrderStep(client.orderStep)
   220  		}
   221  
   222  		// update our offset
   223  		client.offset += int64(len(sendData))
   224  
   225  		if done {
   226  			// combine the last request with the closing data.
   227  			return client.commit(ctx, req)
   228  		}
   229  
   230  		// send signed order + data
   231  		err = client.stream.Send(req)
   232  		if err != nil {
   233  			_, closeErr := client.stream.CloseAndRecv()
   234  			switch {
   235  			case !errors.Is(err, io.EOF) && closeErr != nil:
   236  				err = ErrProtocol.Wrap(errs.Combine(err, closeErr))
   237  			case closeErr != nil:
   238  				err = ErrProtocol.Wrap(closeErr)
   239  			}
   240  
   241  			return nil, err
   242  		}
   243  	}
   244  
   245  	return client.commit(ctx, &pb.PieceUploadRequest{})
   246  }
   247  
   248  // cancel cancels the uploading.
   249  func (client *upload) cancel(ctx context.Context) (err error) {
   250  	defer mon.Task()(&ctx)(&err)
   251  	if client.finished {
   252  		return io.EOF
   253  	}
   254  	client.finished = true
   255  	return Error.Wrap(client.stream.Close())
   256  }
   257  
   258  // commit finishes uploading by sending the piece-hash and retrieving the piece-hash.
   259  func (client *upload) commit(ctx context.Context, req *pb.PieceUploadRequest) (_ *pb.PieceHash, err error) {
   260  	defer mon.Task()(&ctx, "node: "+client.nodeID.String()[0:8])(&err)
   261  	if client.finished {
   262  		return nil, io.EOF
   263  	}
   264  	client.finished = true
   265  
   266  	// sign the hash for storage node
   267  	uplinkHash, err := signing.SignUplinkPieceHash(ctx, client.privateKey, &pb.PieceHash{
   268  		PieceId:       client.limit.PieceId,
   269  		PieceSize:     client.offset,
   270  		Hash:          client.hash.Sum(nil),
   271  		Timestamp:     client.limit.OrderCreation,
   272  		HashAlgorithm: client.hashAlgorithm,
   273  	})
   274  	if err != nil {
   275  		// failed to sign, let's close, no need to wait for a response
   276  		closeErr := client.stream.Close()
   277  		// closeErr being io.EOF doesn't inform us about anything
   278  		return nil, Error.Wrap(errs.Combine(err, ignoreEOF(closeErr)))
   279  	}
   280  
   281  	// exchange signed piece hashes
   282  	// 1. send our piece hash
   283  	req.Done = uplinkHash
   284  	sendErr := client.stream.Send(req)
   285  
   286  	// 2. wait for a piece hash as a response
   287  	response, closeErr := client.stream.CloseAndRecv()
   288  	if response == nil || response.Done == nil {
   289  		// combine all the errors from before
   290  		// sendErr is io.EOF when failed to send, so don't care
   291  		// closeErr is io.EOF when storage node closed before sending us a response
   292  		return nil, errs.Combine(ErrProtocol.New("expected piece hash"), ignoreEOF(sendErr), ignoreEOF(closeErr))
   293  	}
   294  
   295  	var peer *identity.PeerIdentity
   296  	if len(response.NodeCertchain) > 0 {
   297  		peer, err = identity.DecodePeerIdentity(ctx, response.NodeCertchain)
   298  	} else {
   299  		peer, err = client.client.GetPeerIdentity()
   300  	}
   301  	if err != nil {
   302  		return nil, errs.Combine(err, ignoreEOF(sendErr), ignoreEOF(closeErr))
   303  	}
   304  	if peer.ID != client.nodeID {
   305  		return nil, errs.Combine(ErrProtocol.New("mismatch node ids"), ignoreEOF(sendErr), ignoreEOF(closeErr))
   306  	}
   307  
   308  	// verification
   309  	verifyErr := client.client.VerifyPieceHash(ctx, peer, client.limit, response.Done, uplinkHash.Hash, uplinkHash.HashAlgorithm)
   310  
   311  	// combine all the errors from before
   312  	// sendErr is io.EOF when we failed to send
   313  	// closeErr is io.EOF when storage node closed properly
   314  	return response.Done, errs.Combine(verifyErr, ignoreEOF(sendErr), ignoreEOF(closeErr))
   315  }
   316  
   317  func tryReadFull(ctx context.Context, r io.Reader, buf []byte) (n int, err error) {
   318  	total := len(buf)
   319  
   320  	for n < total && err == nil {
   321  		if ctx.Err() != nil {
   322  			return n, ctx.Err()
   323  		}
   324  		var nn int
   325  		nn, err = r.Read(buf[n:])
   326  		n += nn
   327  	}
   328  
   329  	return n, err
   330  }
   331  
   332  // timedUploadStream wraps uploadStream and adds timeouts
   333  // to all operations.
   334  type timedUploadStream struct {
   335  	timeout time.Duration
   336  	stream  uploadStream
   337  	cancel  func(error)
   338  }
   339  
   340  func (stream *timedUploadStream) Context() context.Context {
   341  	return stream.stream.Context()
   342  }
   343  
   344  func (stream *timedUploadStream) cancelTimeout() {
   345  	stream.cancel(errMessageTimeout)
   346  }
   347  
   348  func (stream *timedUploadStream) Close() (err error) {
   349  	sync2.WithTimeout(stream.timeout, func() {
   350  		err = stream.stream.Close()
   351  	}, stream.cancelTimeout)
   352  	return CloseError.Wrap(err)
   353  }
   354  
   355  func (stream *timedUploadStream) Send(req *pb.PieceUploadRequest) (err error) {
   356  	sync2.WithTimeout(stream.timeout, func() {
   357  		err = stream.stream.Send(req)
   358  	}, stream.cancelTimeout)
   359  	return err
   360  }
   361  
   362  func (stream *timedUploadStream) CloseAndRecv() (resp *pb.PieceUploadResponse, err error) {
   363  	sync2.WithTimeout(stream.timeout, func() {
   364  		resp, err = stream.stream.CloseAndRecv()
   365  	}, stream.cancelTimeout)
   366  	return resp, err
   367  }
   368  
   369  func min(a, b int64) int64 {
   370  	if a < b {
   371  		return a
   372  	}
   373  	return b
   374  }