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  }