github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/reserve/reserve.go (about)

     1  // Copyright 2023 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 reserve
     6  
     7  import (
     8  	"context"
     9  	"encoding/binary"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"runtime"
    14  	"strconv"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"github.com/ethersphere/bee/v2/pkg/log"
    19  	"github.com/ethersphere/bee/v2/pkg/postage"
    20  	"github.com/ethersphere/bee/v2/pkg/storage"
    21  	"github.com/ethersphere/bee/v2/pkg/storer/internal/chunkstamp"
    22  	"github.com/ethersphere/bee/v2/pkg/storer/internal/stampindex"
    23  	"github.com/ethersphere/bee/v2/pkg/storer/internal/transaction"
    24  	"github.com/ethersphere/bee/v2/pkg/swarm"
    25  	"github.com/ethersphere/bee/v2/pkg/topology"
    26  	"golang.org/x/sync/errgroup"
    27  	"resenje.org/multex"
    28  )
    29  
    30  const reserveScope = "reserve"
    31  
    32  type Reserve struct {
    33  	baseAddr     swarm.Address
    34  	radiusSetter topology.SetStorageRadiuser
    35  	logger       log.Logger
    36  
    37  	capacity int
    38  	size     atomic.Int64
    39  	radius   atomic.Uint32
    40  
    41  	multx *multex.Multex
    42  	st    transaction.Storage
    43  }
    44  
    45  func New(
    46  	baseAddr swarm.Address,
    47  	st transaction.Storage,
    48  	capacity int,
    49  	radiusSetter topology.SetStorageRadiuser,
    50  	logger log.Logger,
    51  ) (*Reserve, error) {
    52  
    53  	rs := &Reserve{
    54  		baseAddr:     baseAddr,
    55  		st:           st,
    56  		capacity:     capacity,
    57  		radiusSetter: radiusSetter,
    58  		logger:       logger.WithName(reserveScope).Register(),
    59  		multx:        multex.New(),
    60  	}
    61  
    62  	err := st.Run(context.Background(), func(s transaction.Store) error {
    63  		rItem := &radiusItem{}
    64  		err := s.IndexStore().Get(rItem)
    65  		if err != nil && !errors.Is(err, storage.ErrNotFound) {
    66  			return err
    67  		}
    68  		rs.radius.Store(uint32(rItem.Radius))
    69  
    70  		epochItem := &EpochItem{}
    71  		err = s.IndexStore().Get(epochItem)
    72  		if err != nil {
    73  			if errors.Is(err, storage.ErrNotFound) {
    74  				err := s.IndexStore().Put(&EpochItem{Timestamp: uint64(time.Now().Unix())})
    75  				if err != nil {
    76  					return err
    77  				}
    78  			} else {
    79  				return err
    80  			}
    81  		}
    82  
    83  		size, err := s.IndexStore().Count(&BatchRadiusItem{})
    84  		if err != nil {
    85  			return err
    86  		}
    87  		rs.size.Store(int64(size))
    88  		return nil
    89  	})
    90  
    91  	return rs, err
    92  }
    93  
    94  // Reserve Put has to handle multiple possible scenarios.
    95  //  1. Since the same chunk may belong to different postage batches, the reserve will support one chunk to many postage
    96  //     batches relationship.
    97  //  2. A new chunk that shares the same stamp index belonging to the same batch with an already stored chunk will overwrite
    98  //     the existing chunk if the new chunk has a higher stamp timestamp (regardless of batch type).
    99  //  3. A new chunk that has the same address belonging to the same batch with an already stored chunk will overwrite the existing chunk
   100  //     if the new chunk has a higher stamp timestamp (regardless of batch type and chunk type, eg CAC & SOC).
   101  func (r *Reserve) Put(ctx context.Context, chunk swarm.Chunk) error {
   102  
   103  	// batchID lock, Put vs Eviction
   104  	r.multx.Lock(string(chunk.Stamp().BatchID()))
   105  	defer r.multx.Unlock(string(chunk.Stamp().BatchID()))
   106  
   107  	stampHash, err := chunk.Stamp().Hash()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	// check if the chunk with the same batch, stamp timestamp and index is already stored
   113  	has, err := r.Has(chunk.Address(), chunk.Stamp().BatchID(), stampHash)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if has {
   118  		return nil
   119  	}
   120  
   121  	chunkType := storage.ChunkType(chunk)
   122  	bin := swarm.Proximity(r.baseAddr.Bytes(), chunk.Address().Bytes())
   123  
   124  	// bin lock
   125  	r.multx.Lock(strconv.Itoa(int(bin)))
   126  	defer r.multx.Unlock(strconv.Itoa(int(bin)))
   127  
   128  	var shouldIncReserveSize, shouldDecrReserveSize bool
   129  
   130  	err = r.st.Run(ctx, func(s transaction.Store) error {
   131  
   132  		oldStampIndex, loadedStampIndex, err := stampindex.LoadOrStore(s.IndexStore(), reserveScope, chunk)
   133  		if err != nil {
   134  			return fmt.Errorf("load or store stamp index for chunk %v has fail: %w", chunk, err)
   135  		}
   136  
   137  		sameAddressOldStamp, err := chunkstamp.LoadWithBatchID(s.IndexStore(), reserveScope, chunk.Address(), chunk.Stamp().BatchID())
   138  		if err != nil && !errors.Is(err, storage.ErrNotFound) {
   139  			return err
   140  		}
   141  
   142  		// same chunk address, same batch
   143  		if sameAddressOldStamp != nil {
   144  			sameAddressOldStampIndex, err := stampindex.Load(s.IndexStore(), reserveScope, sameAddressOldStamp)
   145  			if err != nil {
   146  				return err
   147  			}
   148  			prev := binary.BigEndian.Uint64(sameAddressOldStampIndex.StampTimestamp)
   149  			curr := binary.BigEndian.Uint64(chunk.Stamp().Timestamp())
   150  			if prev >= curr {
   151  				return fmt.Errorf("overwrite same chunk. prev %d cur %d batch %s: %w", prev, curr, hex.EncodeToString(chunk.Stamp().BatchID()), storage.ErrOverwriteNewerChunk)
   152  			}
   153  
   154  			// index collision with another chunk
   155  			if loadedStampIndex {
   156  				prev := binary.BigEndian.Uint64(oldStampIndex.StampTimestamp)
   157  				if prev >= curr {
   158  					return fmt.Errorf("overwrite same chunk. prev %d cur %d batch %s: %w", prev, curr, hex.EncodeToString(chunk.Stamp().BatchID()), storage.ErrOverwriteNewerChunk)
   159  				}
   160  				if !chunk.Address().Equal(oldStampIndex.ChunkAddress) {
   161  					r.logger.Debug(
   162  						"replacing chunk stamp index",
   163  						"old_chunk", oldStampIndex.ChunkAddress,
   164  						"new_chunk", chunk.Address(),
   165  						"batch_id", hex.EncodeToString(chunk.Stamp().BatchID()),
   166  					)
   167  					// remove index items and chunk data
   168  					err = r.removeChunk(ctx, s, oldStampIndex.ChunkAddress, oldStampIndex.BatchID, oldStampIndex.StampHash)
   169  					if err != nil {
   170  						return fmt.Errorf("failed removing older chunk %s: %w", oldStampIndex.ChunkAddress, err)
   171  					}
   172  					shouldDecrReserveSize = true
   173  				}
   174  			}
   175  
   176  			oldBatchRadiusItem := &BatchRadiusItem{
   177  				Bin:       bin,
   178  				Address:   chunk.Address(),
   179  				BatchID:   sameAddressOldStampIndex.BatchID,
   180  				StampHash: sameAddressOldStampIndex.StampHash,
   181  			}
   182  			// load item to get the binID
   183  			err = s.IndexStore().Get(oldBatchRadiusItem)
   184  			if err != nil {
   185  				return err
   186  			}
   187  
   188  			// delete old chunk index items
   189  			err = errors.Join(
   190  				s.IndexStore().Delete(oldBatchRadiusItem),
   191  				s.IndexStore().Delete(&ChunkBinItem{Bin: oldBatchRadiusItem.Bin, BinID: oldBatchRadiusItem.BinID}),
   192  				stampindex.Delete(s.IndexStore(), reserveScope, sameAddressOldStamp),
   193  				chunkstamp.DeleteWithStamp(s.IndexStore(), reserveScope, oldBatchRadiusItem.Address, sameAddressOldStamp),
   194  			)
   195  			if err != nil {
   196  				return err
   197  			}
   198  
   199  			binID, err := r.IncBinID(s.IndexStore(), bin)
   200  			if err != nil {
   201  				return err
   202  			}
   203  
   204  			err = errors.Join(
   205  				stampindex.Store(s.IndexStore(), reserveScope, chunk),
   206  				chunkstamp.Store(s.IndexStore(), reserveScope, chunk),
   207  				s.IndexStore().Put(&BatchRadiusItem{
   208  					Bin:       bin,
   209  					BinID:     binID,
   210  					Address:   chunk.Address(),
   211  					BatchID:   chunk.Stamp().BatchID(),
   212  					StampHash: stampHash,
   213  				}),
   214  				s.IndexStore().Put(&ChunkBinItem{
   215  					Bin:       bin,
   216  					BinID:     binID,
   217  					Address:   chunk.Address(),
   218  					BatchID:   chunk.Stamp().BatchID(),
   219  					ChunkType: chunkType,
   220  					StampHash: stampHash,
   221  				}),
   222  			)
   223  			if err != nil {
   224  				return err
   225  			}
   226  
   227  			if chunkType != swarm.ChunkTypeSingleOwner {
   228  				return nil
   229  			}
   230  
   231  			r.logger.Debug("replacing soc in chunkstore", "address", chunk.Address())
   232  			return s.ChunkStore().Replace(ctx, chunk)
   233  		}
   234  
   235  		// different address, same batch, index collision
   236  		if loadedStampIndex {
   237  			prev := binary.BigEndian.Uint64(oldStampIndex.StampTimestamp)
   238  			curr := binary.BigEndian.Uint64(chunk.Stamp().Timestamp())
   239  			if prev >= curr {
   240  				return fmt.Errorf("overwrite prev %d cur %d batch %s: %w", prev, curr, hex.EncodeToString(chunk.Stamp().BatchID()), storage.ErrOverwriteNewerChunk)
   241  			}
   242  			// An older (same or different) chunk with the same batchID and stamp index has been previously
   243  			// saved to the reserve. We must do the below before saving the new chunk:
   244  			// 1. Delete the old chunk from the chunkstore.
   245  			// 2. Delete the old chunk's stamp data.
   246  			// 3. Delete ALL old chunk related items from the reserve.
   247  			// 4. Update the stamp index.
   248  
   249  			err = r.removeChunk(ctx, s, oldStampIndex.ChunkAddress, oldStampIndex.BatchID, oldStampIndex.StampHash)
   250  			if err != nil {
   251  				return fmt.Errorf("failed removing older chunk %s: %w", oldStampIndex.ChunkAddress, err)
   252  			}
   253  
   254  			r.logger.Warning(
   255  				"replacing chunk stamp index",
   256  				"old_chunk", oldStampIndex.ChunkAddress,
   257  				"new_chunk", chunk.Address(),
   258  				"batch_id", hex.EncodeToString(chunk.Stamp().BatchID()),
   259  			)
   260  
   261  			// replace old stamp index.
   262  			err = stampindex.Store(s.IndexStore(), reserveScope, chunk)
   263  			if err != nil {
   264  				return fmt.Errorf("failed updating stamp index: %w", err)
   265  			}
   266  		}
   267  
   268  		binID, err := r.IncBinID(s.IndexStore(), bin)
   269  		if err != nil {
   270  			return err
   271  		}
   272  
   273  		err = errors.Join(
   274  			chunkstamp.Store(s.IndexStore(), reserveScope, chunk),
   275  			s.IndexStore().Put(&BatchRadiusItem{
   276  				Bin:       bin,
   277  				BinID:     binID,
   278  				Address:   chunk.Address(),
   279  				BatchID:   chunk.Stamp().BatchID(),
   280  				StampHash: stampHash,
   281  			}),
   282  			s.IndexStore().Put(&ChunkBinItem{
   283  				Bin:       bin,
   284  				BinID:     binID,
   285  				Address:   chunk.Address(),
   286  				BatchID:   chunk.Stamp().BatchID(),
   287  				ChunkType: chunkType,
   288  				StampHash: stampHash,
   289  			}),
   290  			s.ChunkStore().Put(ctx, chunk),
   291  		)
   292  		if err != nil {
   293  			return err
   294  		}
   295  
   296  		if !loadedStampIndex {
   297  			shouldIncReserveSize = true
   298  		}
   299  
   300  		return nil
   301  	})
   302  	if err != nil {
   303  		return err
   304  	}
   305  	if shouldIncReserveSize {
   306  		r.size.Add(1)
   307  	}
   308  	if shouldDecrReserveSize {
   309  		r.size.Add(-1)
   310  	}
   311  	return nil
   312  }
   313  
   314  func (r *Reserve) Has(addr swarm.Address, batchID []byte, stampHash []byte) (bool, error) {
   315  	item := &BatchRadiusItem{Bin: swarm.Proximity(r.baseAddr.Bytes(), addr.Bytes()), BatchID: batchID, Address: addr, StampHash: stampHash}
   316  	return r.st.IndexStore().Has(item)
   317  }
   318  
   319  func (r *Reserve) Get(ctx context.Context, addr swarm.Address, batchID []byte, stampHash []byte) (swarm.Chunk, error) {
   320  	r.multx.Lock(string(batchID))
   321  	defer r.multx.Unlock(string(batchID))
   322  
   323  	item := &BatchRadiusItem{Bin: swarm.Proximity(r.baseAddr.Bytes(), addr.Bytes()), BatchID: batchID, Address: addr, StampHash: stampHash}
   324  	err := r.st.IndexStore().Get(item)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	stamp, err := chunkstamp.LoadWithBatchID(r.st.IndexStore(), reserveScope, addr, item.BatchID)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	ch, err := r.st.ChunkStore().Get(ctx, addr)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	return ch.WithStamp(stamp), nil
   340  }
   341  
   342  // EvictBatchBin evicts all chunks from bins upto the bin provided.
   343  func (r *Reserve) EvictBatchBin(
   344  	ctx context.Context,
   345  	batchID []byte,
   346  	count int,
   347  	bin uint8,
   348  ) (int, error) {
   349  
   350  	r.multx.Lock(string(batchID))
   351  	defer r.multx.Unlock(string(batchID))
   352  
   353  	var evicteditems []*BatchRadiusItem
   354  
   355  	if count <= 0 {
   356  		return 0, nil
   357  	}
   358  
   359  	err := r.st.IndexStore().Iterate(storage.Query{
   360  		Factory: func() storage.Item { return &BatchRadiusItem{} },
   361  		Prefix:  string(batchID),
   362  	}, func(res storage.Result) (bool, error) {
   363  		batchRadius := res.Entry.(*BatchRadiusItem)
   364  		if batchRadius.Bin >= bin {
   365  			return true, nil
   366  		}
   367  		evicteditems = append(evicteditems, batchRadius)
   368  		count--
   369  		if count == 0 {
   370  			return true, nil
   371  		}
   372  		return false, nil
   373  	})
   374  	if err != nil {
   375  		return 0, err
   376  	}
   377  
   378  	eg, ctx := errgroup.WithContext(ctx)
   379  	eg.SetLimit(runtime.NumCPU())
   380  
   381  	var evicted atomic.Int64
   382  
   383  	for _, item := range evicteditems {
   384  		func(item *BatchRadiusItem) {
   385  			eg.Go(func() error {
   386  				err := r.st.Run(ctx, func(s transaction.Store) error {
   387  					return RemoveChunkWithItem(ctx, s, item)
   388  				})
   389  				if err != nil {
   390  					return err
   391  				}
   392  				evicted.Add(1)
   393  				return nil
   394  			})
   395  		}(item)
   396  	}
   397  
   398  	err = eg.Wait()
   399  
   400  	r.size.Add(-evicted.Load())
   401  
   402  	return int(evicted.Load()), err
   403  }
   404  
   405  func (r *Reserve) removeChunk(
   406  	ctx context.Context,
   407  	trx transaction.Store,
   408  	chunkAddress swarm.Address,
   409  	batchID []byte,
   410  	stampHash []byte,
   411  ) error {
   412  	item := &BatchRadiusItem{
   413  		Bin:       swarm.Proximity(r.baseAddr.Bytes(), chunkAddress.Bytes()),
   414  		BatchID:   batchID,
   415  		Address:   chunkAddress,
   416  		StampHash: stampHash,
   417  	}
   418  	err := trx.IndexStore().Get(item)
   419  	if err != nil {
   420  		return err
   421  	}
   422  	return RemoveChunkWithItem(ctx, trx, item)
   423  }
   424  
   425  func RemoveChunkWithItem(
   426  	ctx context.Context,
   427  	trx transaction.Store,
   428  	item *BatchRadiusItem,
   429  ) error {
   430  
   431  	var errs error
   432  
   433  	stamp, _ := chunkstamp.LoadWithBatchID(trx.IndexStore(), reserveScope, item.Address, item.BatchID)
   434  	if stamp != nil {
   435  		errs = errors.Join(
   436  			stampindex.Delete(trx.IndexStore(), reserveScope, stamp),
   437  			chunkstamp.DeleteWithStamp(trx.IndexStore(), reserveScope, item.Address, stamp),
   438  		)
   439  	}
   440  
   441  	return errors.Join(errs,
   442  		trx.IndexStore().Delete(item),
   443  		trx.IndexStore().Delete(&ChunkBinItem{Bin: item.Bin, BinID: item.BinID}),
   444  		trx.ChunkStore().Delete(ctx, item.Address),
   445  	)
   446  }
   447  
   448  func (r *Reserve) IterateBin(bin uint8, startBinID uint64, cb func(swarm.Address, uint64, []byte, []byte) (bool, error)) error {
   449  	err := r.st.IndexStore().Iterate(storage.Query{
   450  		Factory:       func() storage.Item { return &ChunkBinItem{} },
   451  		Prefix:        binIDToString(bin, startBinID),
   452  		PrefixAtStart: true,
   453  	}, func(res storage.Result) (bool, error) {
   454  		item := res.Entry.(*ChunkBinItem)
   455  		if item.Bin > bin {
   456  			return true, nil
   457  		}
   458  
   459  		stop, err := cb(item.Address, item.BinID, item.BatchID, item.StampHash)
   460  		if stop || err != nil {
   461  			return true, err
   462  		}
   463  
   464  		return false, nil
   465  	})
   466  
   467  	return err
   468  }
   469  
   470  func (r *Reserve) IterateChunks(startBin uint8, cb func(swarm.Chunk) (bool, error)) error {
   471  	err := r.st.IndexStore().Iterate(storage.Query{
   472  		Factory:       func() storage.Item { return &ChunkBinItem{} },
   473  		Prefix:        binIDToString(startBin, 0),
   474  		PrefixAtStart: true,
   475  	}, func(res storage.Result) (bool, error) {
   476  		item := res.Entry.(*ChunkBinItem)
   477  
   478  		chunk, err := r.st.ChunkStore().Get(context.Background(), item.Address)
   479  		if err != nil {
   480  			return false, err
   481  		}
   482  
   483  		stamp, err := chunkstamp.LoadWithBatchID(r.st.IndexStore(), reserveScope, item.Address, item.BatchID)
   484  		if err != nil {
   485  			return false, err
   486  		}
   487  
   488  		stop, err := cb(chunk.WithStamp(stamp))
   489  		if stop || err != nil {
   490  			return true, err
   491  		}
   492  		return false, nil
   493  	})
   494  
   495  	return err
   496  }
   497  
   498  func (r *Reserve) IterateChunksItems(startBin uint8, cb func(*ChunkBinItem) (bool, error)) error {
   499  	err := r.st.IndexStore().Iterate(storage.Query{
   500  		Factory:       func() storage.Item { return &ChunkBinItem{} },
   501  		Prefix:        binIDToString(startBin, 0),
   502  		PrefixAtStart: true,
   503  	}, func(res storage.Result) (bool, error) {
   504  		item := res.Entry.(*ChunkBinItem)
   505  
   506  		stop, err := cb(item)
   507  		if stop || err != nil {
   508  			return true, err
   509  		}
   510  		return false, nil
   511  	})
   512  
   513  	return err
   514  }
   515  
   516  // Reset removes all the entires in the reserve. Must be done before any calls to the reserve.
   517  func (r *Reserve) Reset(ctx context.Context) error {
   518  
   519  	size := r.Size()
   520  
   521  	err := r.st.Run(ctx, func(s transaction.Store) error { return s.IndexStore().Delete(&EpochItem{}) })
   522  	if err != nil {
   523  		return err
   524  	}
   525  
   526  	bRitems := make([]*BatchRadiusItem, 0, size)
   527  
   528  	err = r.st.IndexStore().Iterate(storage.Query{
   529  		Factory: func() storage.Item { return &BatchRadiusItem{} },
   530  	}, func(res storage.Result) (bool, error) {
   531  		bRitems = append(bRitems, res.Entry.(*BatchRadiusItem))
   532  		return false, nil
   533  	})
   534  	if err != nil {
   535  		return err
   536  	}
   537  
   538  	var eg errgroup.Group
   539  	eg.SetLimit(runtime.NumCPU())
   540  
   541  	for _, item := range bRitems {
   542  		item := item
   543  		eg.Go(func() error {
   544  			return r.st.Run(ctx, func(s transaction.Store) error {
   545  				return errors.Join(
   546  					s.ChunkStore().Delete(ctx, item.Address),
   547  					s.IndexStore().Delete(item),
   548  					s.IndexStore().Delete(&ChunkBinItem{Bin: item.Bin, BinID: item.BinID}),
   549  				)
   550  			})
   551  		})
   552  	}
   553  
   554  	err = eg.Wait()
   555  	if err != nil {
   556  		return err
   557  	}
   558  	bRitems = nil
   559  
   560  	sitems := make([]*stampindex.Item, 0, size)
   561  	err = r.st.IndexStore().Iterate(storage.Query{
   562  		Factory: func() storage.Item { return &stampindex.Item{} },
   563  	}, func(res storage.Result) (bool, error) {
   564  		sitems = append(sitems, res.Entry.(*stampindex.Item))
   565  		return false, nil
   566  	})
   567  	if err != nil {
   568  		return err
   569  	}
   570  	for _, item := range sitems {
   571  		item := item
   572  		eg.Go(func() error {
   573  			return r.st.Run(ctx, func(s transaction.Store) error {
   574  				return errors.Join(
   575  					s.IndexStore().Delete(item),
   576  					chunkstamp.DeleteWithStamp(s.IndexStore(), reserveScope, item.ChunkAddress, postage.NewStamp(item.BatchID, item.StampIndex, item.StampTimestamp, nil)),
   577  				)
   578  			})
   579  		})
   580  	}
   581  
   582  	err = eg.Wait()
   583  	if err != nil {
   584  		return err
   585  	}
   586  	sitems = nil
   587  
   588  	r.size.Store(0)
   589  
   590  	return nil
   591  }
   592  
   593  func (r *Reserve) Radius() uint8 {
   594  	return uint8(r.radius.Load())
   595  }
   596  
   597  func (r *Reserve) Size() int {
   598  	return int(r.size.Load())
   599  }
   600  
   601  func (r *Reserve) Capacity() int {
   602  	return r.capacity
   603  }
   604  
   605  func (r *Reserve) IsWithinCapacity() bool {
   606  	return int(r.size.Load()) <= r.capacity
   607  }
   608  
   609  func (r *Reserve) EvictionTarget() int {
   610  	if r.IsWithinCapacity() {
   611  		return 0
   612  	}
   613  	return int(r.size.Load()) - r.capacity
   614  }
   615  
   616  func (r *Reserve) SetRadius(rad uint8) error {
   617  	r.radius.Store(uint32(rad))
   618  	r.radiusSetter.SetStorageRadius(rad)
   619  	return r.st.Run(context.Background(), func(s transaction.Store) error {
   620  		return s.IndexStore().Put(&radiusItem{Radius: rad})
   621  	})
   622  }
   623  
   624  func (r *Reserve) LastBinIDs() ([]uint64, uint64, error) {
   625  	var epoch EpochItem
   626  	err := r.st.IndexStore().Get(&epoch)
   627  	if err != nil {
   628  		return nil, 0, err
   629  	}
   630  
   631  	ids := make([]uint64, swarm.MaxBins)
   632  
   633  	for bin := uint8(0); bin < swarm.MaxBins; bin++ {
   634  		binItem := &BinItem{Bin: bin}
   635  		err := r.st.IndexStore().Get(binItem)
   636  		if err != nil {
   637  			if errors.Is(err, storage.ErrNotFound) {
   638  				ids[bin] = 0
   639  			} else {
   640  				return nil, 0, err
   641  			}
   642  		} else {
   643  			ids[bin] = binItem.BinID
   644  		}
   645  	}
   646  
   647  	return ids, epoch.Timestamp, nil
   648  }
   649  
   650  func (r *Reserve) IncBinID(store storage.IndexStore, bin uint8) (uint64, error) {
   651  	item := &BinItem{Bin: bin}
   652  	err := store.Get(item)
   653  	if err != nil {
   654  		if errors.Is(err, storage.ErrNotFound) {
   655  			item.BinID = 1
   656  			return 1, store.Put(item)
   657  		}
   658  
   659  		return 0, err
   660  	}
   661  
   662  	item.BinID += 1
   663  
   664  	return item.BinID, store.Put(item)
   665  }