github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/upload/uploadstore.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package upload 6 7 import ( 8 "context" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "runtime" 13 "strconv" 14 "time" 15 16 "github.com/ethersphere/bee/v2/pkg/encryption" 17 storage "github.com/ethersphere/bee/v2/pkg/storage" 18 "github.com/ethersphere/bee/v2/pkg/storage/storageutil" 19 "github.com/ethersphere/bee/v2/pkg/storer/internal" 20 "github.com/ethersphere/bee/v2/pkg/storer/internal/chunkstamp" 21 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 22 "github.com/ethersphere/bee/v2/pkg/swarm" 23 "golang.org/x/sync/errgroup" 24 ) 25 26 // now returns the current time.Time; used in testing. 27 var now = time.Now 28 29 var ( 30 // errPushItemMarshalAddressIsZero is returned when trying 31 // to marshal a pushItem with an address that is zero. 32 errPushItemMarshalAddressIsZero = errors.New("marshal pushItem: address is zero") 33 // errPushItemMarshalBatchInvalid is returned when trying to 34 // marshal a pushItem with invalid batch 35 errPushItemMarshalBatchInvalid = errors.New("marshal pushItem: batch is invalid") 36 // errPushItemUnmarshalInvalidSize is returned when trying 37 // to unmarshal buffer that is not of size pushItemSize. 38 errPushItemUnmarshalInvalidSize = errors.New("unmarshal pushItem: invalid size") 39 ) 40 41 // pushItemSize is the size of a marshaled pushItem. 42 const pushItemSize = 8 + 2*swarm.HashSize + 8 43 44 const uploadScope = "upload" 45 46 var _ storage.Item = (*pushItem)(nil) 47 48 // pushItem is an store.Item that represents data relevant to push. 49 // The key is a combination of Timestamp, Address and postage stamp, where the 50 // Timestamp provides an order to iterate. 51 type pushItem struct { 52 Timestamp int64 53 Address swarm.Address 54 BatchID []byte 55 TagID uint64 56 } 57 58 // ID implements the storage.Item interface. 59 func (i pushItem) ID() string { 60 return fmt.Sprintf("%d/%s/%s", i.Timestamp, i.Address.ByteString(), string(i.BatchID)) 61 } 62 63 // Namespace implements the storage.Item interface. 64 func (i pushItem) Namespace() string { 65 return "pushIndex" 66 } 67 68 // Marshal implements the storage.Item interface. 69 // If the Address is zero, an error is returned. 70 func (i pushItem) Marshal() ([]byte, error) { 71 if i.Address.IsZero() { 72 return nil, errPushItemMarshalAddressIsZero 73 } 74 if len(i.BatchID) != swarm.HashSize { 75 return nil, errPushItemMarshalBatchInvalid 76 } 77 buf := make([]byte, pushItemSize) 78 binary.LittleEndian.PutUint64(buf, uint64(i.Timestamp)) 79 copy(buf[8:], i.Address.Bytes()) 80 copy(buf[8+swarm.HashSize:8+2*swarm.HashSize], i.BatchID) 81 binary.LittleEndian.PutUint64(buf[8+2*swarm.HashSize:], i.TagID) 82 return buf, nil 83 } 84 85 // Unmarshal implements the storage.Item interface. 86 // If the buffer is not of size pushItemSize, an error is returned. 87 func (i *pushItem) Unmarshal(bytes []byte) error { 88 if len(bytes) != pushItemSize { 89 return errPushItemUnmarshalInvalidSize 90 } 91 ni := new(pushItem) 92 ni.Timestamp = int64(binary.LittleEndian.Uint64(bytes)) 93 ni.Address = swarm.NewAddress(append(make([]byte, 0, swarm.HashSize), bytes[8:8+swarm.HashSize]...)) 94 ni.BatchID = append(make([]byte, 0, swarm.HashSize), bytes[8+swarm.HashSize:8+2*swarm.HashSize]...) 95 ni.TagID = binary.LittleEndian.Uint64(bytes[8+2*swarm.HashSize:]) 96 *i = *ni 97 return nil 98 } 99 100 // Clone implements the storage.Item interface. 101 func (i *pushItem) Clone() storage.Item { 102 if i == nil { 103 return nil 104 } 105 return &pushItem{ 106 Timestamp: i.Timestamp, 107 Address: i.Address.Clone(), 108 BatchID: append([]byte(nil), i.BatchID...), 109 TagID: i.TagID, 110 } 111 } 112 113 // String implements the fmt.Stringer interface. 114 func (i pushItem) String() string { 115 return storageutil.JoinFields(i.Namespace(), i.ID()) 116 } 117 118 var ( 119 // errTagIDAddressItemUnmarshalInvalidSize is returned when trying 120 // to unmarshal buffer that is not of size tagItemSize. 121 errTagItemUnmarshalInvalidSize = errors.New("unmarshal TagItem: invalid size") 122 ) 123 124 // tagItemSize is the size of a marshaled TagItem. 125 const tagItemSize = swarm.HashSize + 7*8 126 127 var _ storage.Item = (*TagItem)(nil) 128 129 // TagItem is an store.Item that stores information about a session of upload. 130 type TagItem struct { 131 TagID uint64 // unique identifier for the tag 132 Split uint64 // total no of chunks processed by the splitter for hashing 133 Seen uint64 // total no of chunks already seen 134 Stored uint64 // total no of chunks stored locally on the node 135 Sent uint64 // total no of chunks sent to the neighbourhood 136 Synced uint64 // total no of chunks synced with proof 137 Address swarm.Address // swarm.Address associated with this tag 138 StartedAt int64 // start timestamp 139 } 140 141 // ID implements the storage.Item interface. 142 func (i TagItem) ID() string { 143 return strconv.FormatUint(i.TagID, 10) 144 } 145 146 // Namespace implements the storage.Item interface. 147 func (i TagItem) Namespace() string { 148 return "tagItem" 149 } 150 151 // Marshal implements the storage.Item interface. 152 func (i TagItem) Marshal() ([]byte, error) { 153 buf := make([]byte, tagItemSize) 154 binary.LittleEndian.PutUint64(buf, i.TagID) 155 binary.LittleEndian.PutUint64(buf[8:], i.Split) 156 binary.LittleEndian.PutUint64(buf[16:], i.Seen) 157 binary.LittleEndian.PutUint64(buf[24:], i.Stored) 158 binary.LittleEndian.PutUint64(buf[32:], i.Sent) 159 binary.LittleEndian.PutUint64(buf[40:], i.Synced) 160 addrBytes := internal.AddressBytesOrZero(i.Address) 161 if len(addrBytes) == encryption.ReferenceSize { 162 // in case of encrypted reference we use the swarm hash as the address and 163 // avoid storing the encryption key 164 addrBytes = addrBytes[:swarm.HashSize] 165 } 166 copy(buf[48:], addrBytes) 167 binary.LittleEndian.PutUint64(buf[48+swarm.HashSize:], uint64(i.StartedAt)) 168 return buf, nil 169 } 170 171 // Unmarshal implements the storage.Item interface. 172 // If the buffer is not of size tagItemSize, an error is returned. 173 func (i *TagItem) Unmarshal(bytes []byte) error { 174 if len(bytes) != tagItemSize { 175 return errTagItemUnmarshalInvalidSize 176 } 177 ni := new(TagItem) 178 ni.TagID = binary.LittleEndian.Uint64(bytes) 179 ni.Split = binary.LittleEndian.Uint64(bytes[8:]) 180 ni.Seen = binary.LittleEndian.Uint64(bytes[16:]) 181 ni.Stored = binary.LittleEndian.Uint64(bytes[24:]) 182 ni.Sent = binary.LittleEndian.Uint64(bytes[32:]) 183 ni.Synced = binary.LittleEndian.Uint64(bytes[40:]) 184 ni.Address = internal.AddressOrZero(bytes[48 : 48+swarm.HashSize]) 185 ni.StartedAt = int64(binary.LittleEndian.Uint64(bytes[48+swarm.HashSize:])) 186 *i = *ni 187 return nil 188 } 189 190 // Clone implements the storage.Item interface. 191 func (i *TagItem) Clone() storage.Item { 192 if i == nil { 193 return nil 194 } 195 return &TagItem{ 196 TagID: i.TagID, 197 Split: i.Split, 198 Seen: i.Seen, 199 Stored: i.Stored, 200 Sent: i.Sent, 201 Synced: i.Synced, 202 Address: i.Address.Clone(), 203 StartedAt: i.StartedAt, 204 } 205 } 206 207 // String implements the fmt.Stringer interface. 208 func (i TagItem) String() string { 209 return storageutil.JoinFields(i.Namespace(), i.ID()) 210 } 211 212 var ( 213 // errUploadItemMarshalAddressIsZero is returned when trying 214 // to marshal a uploadItem with an address that is zero. 215 errUploadItemMarshalAddressIsZero = errors.New("marshal uploadItem: address is zero") 216 // errUploadItemMarshalBatchInvalid is returned when trying to 217 // marshal a uploadItem with invalid batch 218 errUploadItemMarshalBatchInvalid = errors.New("marshal uploadItem: batch is invalid") 219 // errTagIDAddressItemUnmarshalInvalidSize is returned when trying 220 // to unmarshal buffer that is not of size uploadItemSize. 221 errUploadItemUnmarshalInvalidSize = errors.New("unmarshal uploadItem: invalid size") 222 ) 223 224 // uploadItemSize is the size of a marshaled uploadItem. 225 const uploadItemSize = 3 * 8 226 227 var _ storage.Item = (*uploadItem)(nil) 228 229 // uploadItem is an store.Item that stores addresses of already seen chunks. 230 type uploadItem struct { 231 Address swarm.Address 232 BatchID []byte 233 TagID uint64 234 Uploaded int64 235 Synced int64 236 237 // IdFunc overrides the ID method. 238 // This used to get the ID from the item where the address and batchID were not marshalled. 239 IdFunc func() string 240 } 241 242 // ID implements the storage.Item interface. 243 func (i uploadItem) ID() string { 244 if i.IdFunc != nil { 245 return i.IdFunc() 246 } 247 return storageutil.JoinFields(i.Address.ByteString(), string(i.BatchID)) 248 } 249 250 // Namespace implements the storage.Item interface. 251 func (i uploadItem) Namespace() string { 252 return "UploadItem" 253 } 254 255 // Marshal implements the storage.Item interface. 256 // If the Address is zero, an error is returned. 257 func (i uploadItem) Marshal() ([]byte, error) { 258 // Address and BatchID are not part of the marshaled payload. But they are used 259 // in they key and hence are required. The Marshaling is done when item is to 260 // be stored, so we return errors for these cases. 261 if i.Address.IsZero() { 262 return nil, errUploadItemMarshalAddressIsZero 263 } 264 if len(i.BatchID) != swarm.HashSize { 265 return nil, errUploadItemMarshalBatchInvalid 266 } 267 buf := make([]byte, uploadItemSize) 268 binary.LittleEndian.PutUint64(buf, i.TagID) 269 binary.LittleEndian.PutUint64(buf[8:], uint64(i.Uploaded)) 270 binary.LittleEndian.PutUint64(buf[16:], uint64(i.Synced)) 271 return buf, nil 272 } 273 274 // Unmarshal implements the storage.Item interface. 275 // If the buffer is not of size pushItemSize, an error is returned. 276 func (i *uploadItem) Unmarshal(bytes []byte) error { 277 if len(bytes) != uploadItemSize { 278 return errUploadItemUnmarshalInvalidSize 279 } 280 // The Address and BatchID are required for the key, so it is assumed that 281 // they will be filled already. We reuse them during unmarshaling. 282 i.TagID = binary.LittleEndian.Uint64(bytes[:8]) 283 i.Uploaded = int64(binary.LittleEndian.Uint64(bytes[8:16])) 284 i.Synced = int64(binary.LittleEndian.Uint64(bytes[16:])) 285 return nil 286 } 287 288 // Clone implements the storage.Item interface. 289 func (i *uploadItem) Clone() storage.Item { 290 if i == nil { 291 return nil 292 } 293 return &uploadItem{ 294 Address: i.Address.Clone(), 295 BatchID: append([]byte(nil), i.BatchID...), 296 TagID: i.TagID, 297 Uploaded: i.Uploaded, 298 Synced: i.Synced, 299 } 300 } 301 302 // String implements the fmt.Stringer interface. 303 func (i uploadItem) String() string { 304 return storageutil.JoinFields(i.Namespace(), i.ID()) 305 } 306 307 // dirtyTagItemUnmarshalInvalidSize is returned when trying 308 // to unmarshal buffer that is not of size dirtyTagItemSize. 309 var errDirtyTagItemUnmarshalInvalidSize = errors.New("unmarshal dirtyTagItem: invalid size") 310 311 // dirtyTagItemSize is the size of a marshaled dirtyTagItem. 312 const dirtyTagItemSize = 8 + 8 313 314 type dirtyTagItem struct { 315 TagID uint64 316 Started int64 317 } 318 319 // ID implements the storage.Item interface. 320 func (i dirtyTagItem) ID() string { 321 return strconv.FormatUint(i.TagID, 10) 322 } 323 324 // Namespace implements the storage.Item interface. 325 func (i dirtyTagItem) Namespace() string { 326 return "DirtyTagItem" 327 } 328 329 // Marshal implements the storage.Item interface. 330 func (i dirtyTagItem) Marshal() ([]byte, error) { 331 buf := make([]byte, dirtyTagItemSize) 332 binary.LittleEndian.PutUint64(buf, i.TagID) 333 binary.LittleEndian.PutUint64(buf[8:], uint64(i.Started)) 334 return buf, nil 335 } 336 337 // Unmarshal implements the storage.Item interface. 338 func (i *dirtyTagItem) Unmarshal(bytes []byte) error { 339 if len(bytes) != dirtyTagItemSize { 340 return errDirtyTagItemUnmarshalInvalidSize 341 } 342 i.TagID = binary.LittleEndian.Uint64(bytes[:8]) 343 i.Started = int64(binary.LittleEndian.Uint64(bytes[8:])) 344 return nil 345 } 346 347 // Clone implements the storage.Item interface. 348 func (i *dirtyTagItem) Clone() storage.Item { 349 if i == nil { 350 return nil 351 } 352 return &dirtyTagItem{ 353 TagID: i.TagID, 354 Started: i.Started, 355 } 356 } 357 358 // String implements the fmt.Stringer interface. 359 func (i dirtyTagItem) String() string { 360 return storageutil.JoinFields(i.Namespace(), i.ID()) 361 } 362 363 var ( 364 // errPutterAlreadyClosed is returned when trying to Put a new chunk 365 // after the putter has been closed. 366 errPutterAlreadyClosed = errors.New("upload store: putter already closed") 367 368 // errOverwriteOfImmutableBatch is returned when stamp index already 369 // exists and the batch is immutable. 370 errOverwriteOfImmutableBatch = errors.New("upload store: overwrite of existing immutable batch") 371 372 // errOverwriteOfNewerBatch is returned if a stamp index already exists 373 // and the existing chunk with the same stamp index has a newer timestamp. 374 errOverwriteOfNewerBatch = errors.New("upload store: overwrite of existing batch with newer timestamp") 375 ) 376 377 type uploadPutter struct { 378 tagID uint64 379 split uint64 380 seen uint64 381 closed bool 382 } 383 384 // NewPutter returns a new chunk putter associated with the tagID. 385 // Calls to the Putter must be mutex locked to prevent concurrent upload data races. 386 func NewPutter(s storage.IndexStore, tagID uint64) (internal.PutterCloserWithReference, error) { 387 ti := &TagItem{TagID: tagID} 388 has, err := s.Has(ti) 389 if err != nil { 390 return nil, err 391 } 392 if !has { 393 return nil, fmt.Errorf("upload store: tag %d not found: %w", tagID, storage.ErrNotFound) 394 } 395 err = s.Put(&dirtyTagItem{TagID: tagID, Started: now().UnixNano()}) 396 if err != nil { 397 return nil, err 398 } 399 return &uploadPutter{ 400 tagID: ti.TagID, 401 }, nil 402 } 403 404 // Put operation will do the following: 405 // 1.If upload store has already seen this chunk, it will update the tag and return 406 // 2.For a new chunk it will add: 407 // - uploadItem entry to keep track of this chunk. 408 // - pushItem entry to make it available for PushSubscriber 409 // - add chunk to the chunkstore till it is synced 410 // The user of the putter MUST mutex lock the call to prevent data-races across multiple upload sessions. 411 func (u *uploadPutter) Put(ctx context.Context, st transaction.Store, chunk swarm.Chunk) error { 412 if u.closed { 413 return errPutterAlreadyClosed 414 } 415 416 // Check if upload store has already seen this chunk 417 ui := &uploadItem{Address: chunk.Address(), BatchID: chunk.Stamp().BatchID()} 418 switch exists, err := st.IndexStore().Has(ui); { 419 case err != nil: 420 return fmt.Errorf("store has item %q call failed: %w", ui, err) 421 case exists: 422 u.seen++ 423 u.split++ 424 return nil 425 } 426 427 u.split++ 428 429 ui.Uploaded = now().UnixNano() 430 ui.TagID = u.tagID 431 432 pi := &pushItem{ 433 Timestamp: ui.Uploaded, 434 Address: chunk.Address(), 435 BatchID: chunk.Stamp().BatchID(), 436 TagID: u.tagID, 437 } 438 439 return errors.Join( 440 st.IndexStore().Put(ui), 441 st.IndexStore().Put(pi), 442 st.ChunkStore().Put(ctx, chunk), 443 chunkstamp.Store(st.IndexStore(), uploadScope, chunk), 444 ) 445 } 446 447 // Close provides the CloseWithReference interface where the session can be associated 448 // with a swarm reference. This can be useful while keeping track of uploads through 449 // the tags. It will update the tag. This will be filled with the Split and Seen count 450 // by the Putter. 451 func (u *uploadPutter) Close(s storage.IndexStore, addr swarm.Address) error { 452 if u.closed { 453 return nil 454 } 455 456 ti := &TagItem{TagID: u.tagID} 457 err := s.Get(ti) 458 if err != nil { 459 return fmt.Errorf("failed reading tag while closing: %w", err) 460 } 461 462 ti.Split += u.split 463 ti.Seen += u.seen 464 465 if !addr.IsZero() { 466 ti.Address = addr.Clone() 467 } 468 469 u.closed = true 470 471 return errors.Join( 472 s.Put(ti), 473 s.Delete(&dirtyTagItem{TagID: u.tagID}), 474 ) 475 } 476 477 func (u *uploadPutter) Cleanup(st transaction.Storage) error { 478 if u.closed { 479 return nil 480 } 481 482 itemsToDelete := make([]*pushItem, 0) 483 484 di := &dirtyTagItem{TagID: u.tagID} 485 err := st.IndexStore().Get(di) 486 if err != nil { 487 return fmt.Errorf("failed reading dirty tag while cleaning up: %w", err) 488 } 489 490 err = st.IndexStore().Iterate( 491 storage.Query{ 492 Factory: func() storage.Item { return &pushItem{} }, 493 PrefixAtStart: true, 494 Prefix: fmt.Sprintf("%d", di.Started), 495 }, 496 func(res storage.Result) (bool, error) { 497 pi := res.Entry.(*pushItem) 498 if pi.TagID == u.tagID { 499 itemsToDelete = append(itemsToDelete, pi) 500 } 501 return false, nil 502 }, 503 ) 504 if err != nil { 505 return fmt.Errorf("failed iterating over push items: %w", err) 506 } 507 508 var eg errgroup.Group 509 eg.SetLimit(runtime.NumCPU()) 510 511 for _, item := range itemsToDelete { 512 func(item *pushItem) { 513 eg.Go(func() error { 514 return st.Run(context.Background(), func(s transaction.Store) error { 515 ui := &uploadItem{Address: item.Address, BatchID: item.BatchID} 516 return errors.Join( 517 s.IndexStore().Delete(ui), 518 s.ChunkStore().Delete(context.Background(), item.Address), 519 chunkstamp.Delete(s.IndexStore(), uploadScope, item.Address, item.BatchID), 520 s.IndexStore().Delete(item), 521 ) 522 }) 523 }) 524 }(item) 525 } 526 527 return errors.Join( 528 eg.Wait(), 529 st.Run(context.Background(), func(s transaction.Store) error { 530 return s.IndexStore().Delete(&dirtyTagItem{TagID: u.tagID}) 531 }), 532 ) 533 } 534 535 // CleanupDirty does a best-effort cleanup of dirty tags. This is called on startup. 536 func CleanupDirty(st transaction.Storage) error { 537 dirtyTags := make([]*dirtyTagItem, 0) 538 539 err := st.IndexStore().Iterate( 540 storage.Query{ 541 Factory: func() storage.Item { return &dirtyTagItem{} }, 542 }, 543 func(res storage.Result) (bool, error) { 544 di := res.Entry.(*dirtyTagItem) 545 dirtyTags = append(dirtyTags, di) 546 return false, nil 547 }, 548 ) 549 if err != nil { 550 return fmt.Errorf("failed iterating dirty tags: %w", err) 551 } 552 553 for _, di := range dirtyTags { 554 err = errors.Join(err, (&uploadPutter{tagID: di.TagID}).Cleanup(st)) 555 } 556 557 return err 558 } 559 560 // Report is the implementation of the PushReporter interface. 561 func Report(ctx context.Context, st transaction.Store, chunk swarm.Chunk, state storage.ChunkState) error { 562 563 ui := &uploadItem{Address: chunk.Address(), BatchID: chunk.Stamp().BatchID()} 564 565 indexStore := st.IndexStore() 566 567 err := indexStore.Get(ui) 568 if err != nil { 569 // because of the nature of the feed mechanism of the uploadstore/pusher, a chunk that is in inflight may be sent more than once to the pusher. 570 // this is because the chunks are removed from the queue only when they are synced, not at the start of the upload 571 if errors.Is(err, storage.ErrNotFound) { 572 return nil 573 } 574 575 return fmt.Errorf("failed to read uploadItem %s: %w", ui, err) 576 } 577 578 ti := &TagItem{TagID: ui.TagID} 579 err = indexStore.Get(ti) 580 if err != nil { 581 return fmt.Errorf("failed getting tag: %w", err) 582 } 583 584 switch state { 585 case storage.ChunkSent: 586 ti.Sent++ 587 case storage.ChunkStored: 588 ti.Stored++ 589 // also mark it as synced 590 fallthrough 591 case storage.ChunkSynced: 592 ti.Synced++ 593 case storage.ChunkCouldNotSync: 594 break 595 } 596 597 err = indexStore.Put(ti) 598 if err != nil { 599 return fmt.Errorf("failed updating tag: %w", err) 600 } 601 602 if state == storage.ChunkSent { 603 return nil 604 } 605 606 // Once the chunk is stored/synced/failed to sync, it is deleted from the upload store as 607 // we no longer need to keep track of this chunk. We also need to cleanup 608 // the pushItem. 609 pi := &pushItem{ 610 Timestamp: ui.Uploaded, 611 Address: chunk.Address(), 612 BatchID: chunk.Stamp().BatchID(), 613 } 614 615 return errors.Join( 616 indexStore.Delete(pi), 617 chunkstamp.Delete(indexStore, uploadScope, pi.Address, pi.BatchID), 618 st.ChunkStore().Delete(ctx, chunk.Address()), 619 indexStore.Delete(ui), 620 ) 621 } 622 623 var ( 624 errNextTagIDUnmarshalInvalidSize = errors.New("unmarshal nextTagID: invalid size") 625 ) 626 627 // nextTagID is a storage.Item which stores a uint64 value in the store. 628 type nextTagID uint64 629 630 func (nextTagID) Namespace() string { return "upload" } 631 632 func (nextTagID) ID() string { return "nextTagID" } 633 634 func (n nextTagID) Marshal() ([]byte, error) { 635 buf := make([]byte, 8) 636 binary.LittleEndian.PutUint64(buf, uint64(n)) 637 return buf, nil 638 } 639 640 func (n *nextTagID) Unmarshal(buf []byte) error { 641 if len(buf) != 8 { 642 return errNextTagIDUnmarshalInvalidSize 643 } 644 645 *n = nextTagID(binary.LittleEndian.Uint64(buf)) 646 return nil 647 } 648 649 func (n *nextTagID) Clone() storage.Item { 650 if n == nil { 651 return nil 652 } 653 ni := *n 654 return &ni 655 } 656 657 func (n nextTagID) String() string { 658 return storageutil.JoinFields(n.Namespace(), n.ID()) 659 } 660 661 // NextTag returns the next tag ID to be used. It reads the last used ID and 662 // increments it by 1. This method needs to be called under lock by user as there 663 // is no guarantee for parallel updates. 664 func NextTag(st storage.IndexStore) (TagItem, error) { 665 var ( 666 tagID nextTagID 667 tag TagItem 668 ) 669 err := st.Get(&tagID) 670 if err != nil && !errors.Is(err, storage.ErrNotFound) { 671 return tag, err 672 } 673 674 tagID++ 675 err = st.Put(&tagID) 676 if err != nil { 677 return tag, err 678 } 679 680 tag.TagID = uint64(tagID) 681 tag.StartedAt = now().UnixNano() 682 683 return tag, st.Put(&tag) 684 } 685 686 // TagInfo returns the TagItem for this particular tagID. 687 func TagInfo(st storage.Reader, tagID uint64) (TagItem, error) { 688 ti := TagItem{TagID: tagID} 689 err := st.Get(&ti) 690 if err != nil { 691 return ti, fmt.Errorf("uploadstore: failed getting tag %d: %w", tagID, err) 692 } 693 694 return ti, nil 695 } 696 697 // ListAllTags returns all the TagItems in the store. 698 func ListAllTags(st storage.Reader) ([]TagItem, error) { 699 var tags []TagItem 700 err := st.Iterate(storage.Query{ 701 Factory: func() storage.Item { return new(TagItem) }, 702 }, func(r storage.Result) (bool, error) { 703 tags = append(tags, *r.Entry.(*TagItem)) 704 return false, nil 705 }) 706 if err != nil { 707 return nil, fmt.Errorf("uploadstore: failed to iterate tags: %w", err) 708 } 709 710 return tags, nil 711 } 712 713 func IteratePending(ctx context.Context, s transaction.ReadOnlyStore, consumerFn func(chunk swarm.Chunk) (bool, error)) error { 714 return s.IndexStore().Iterate(storage.Query{ 715 Factory: func() storage.Item { return &pushItem{} }, 716 }, func(r storage.Result) (bool, error) { 717 pi := r.Entry.(*pushItem) 718 has, err := s.IndexStore().Has(&dirtyTagItem{TagID: pi.TagID}) 719 if err != nil { 720 return true, err 721 } 722 if has { 723 return false, nil 724 } 725 chunk, err := s.ChunkStore().Get(ctx, pi.Address) 726 if err != nil { 727 return true, err 728 } 729 730 stamp, err := chunkstamp.LoadWithBatchID(s.IndexStore(), uploadScope, chunk.Address(), pi.BatchID) 731 if err != nil { 732 return true, err 733 } 734 735 chunk = chunk. 736 WithStamp(stamp). 737 WithTagID(uint32(pi.TagID)) 738 739 return consumerFn(chunk) 740 }) 741 } 742 743 // DeleteTag deletes TagItem associated with the given tagID. 744 func DeleteTag(st storage.Writer, tagID uint64) error { 745 if err := st.Delete(&TagItem{TagID: tagID}); err != nil { 746 return fmt.Errorf("uploadstore: failed to delete tag %d: %w", tagID, err) 747 } 748 return nil 749 } 750 751 func IterateAll(st storage.Reader, iterateFn func(item storage.Item) (bool, error)) error { 752 return st.Iterate( 753 storage.Query{ 754 Factory: func() storage.Item { return new(uploadItem) }, 755 }, 756 func(r storage.Result) (bool, error) { 757 ui := r.Entry.(*uploadItem) 758 ui.IdFunc = func() string { 759 return r.ID 760 } 761 return iterateFn(ui) 762 }, 763 ) 764 } 765 766 func IterateAllTagItems(st storage.Reader, cb func(ti *TagItem) (bool, error)) error { 767 return st.Iterate( 768 storage.Query{ 769 Factory: func() storage.Item { return new(TagItem) }, 770 }, 771 func(result storage.Result) (bool, error) { 772 ti := result.Entry.(*TagItem) 773 return cb(ti) 774 }, 775 ) 776 } 777 778 // BatchIDForChunk returns the first known batchID for the given chunk address. 779 func BatchIDForChunk(st storage.Reader, addr swarm.Address) ([]byte, error) { 780 var batchID []byte 781 782 err := st.Iterate( 783 storage.Query{ 784 Factory: func() storage.Item { return new(uploadItem) }, 785 Prefix: addr.ByteString(), 786 ItemProperty: storage.QueryItemID, 787 }, 788 func(r storage.Result) (bool, error) { 789 if len(r.ID) < 32 { 790 return false, nil 791 } 792 batchID = []byte(r.ID[len(r.ID)-32:]) 793 return false, nil 794 }, 795 ) 796 if err != nil { 797 return nil, err 798 } 799 800 if batchID == nil { 801 return nil, storage.ErrNotFound 802 } 803 804 return batchID, nil 805 }