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 }