github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/chunkstamp/chunkstamp.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 chunkstamp
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/ethersphere/bee/v2/pkg/postage"
    14  	"github.com/ethersphere/bee/v2/pkg/storage"
    15  	"github.com/ethersphere/bee/v2/pkg/storage/storageutil"
    16  	"github.com/ethersphere/bee/v2/pkg/swarm"
    17  )
    18  
    19  var (
    20  	// errMarshalInvalidChunkStampItemScope is returned during marshaling if the scope is not set.
    21  	errMarshalInvalidChunkStampItemScope = errors.New("marshal chunkstamp.Item: invalid scope")
    22  	// errMarshalInvalidChunkStampAddress is returned during marshaling if the address is zero.
    23  	errMarshalInvalidChunkStampItemAddress = errors.New("marshal chunkstamp.item: invalid address")
    24  	// errUnmarshalInvalidChunkStampAddress is returned during unmarshaling if the address is not set.
    25  	errUnmarshalInvalidChunkStampItemAddress = errors.New("unmarshal chunkstamp.item: invalid address")
    26  	// errMarshalInvalidChunkStamp is returned if the stamp is invalid during marshaling.
    27  	errMarshalInvalidChunkStampItemStamp = errors.New("marshal chunkstamp.item: invalid stamp")
    28  	// errUnmarshalInvalidChunkStampSize is returned during unmarshaling if the passed buffer is not the expected size.
    29  	errUnmarshalInvalidChunkStampItemSize = errors.New("unmarshal chunkstamp.item: invalid size")
    30  )
    31  
    32  var _ storage.Item = (*Item)(nil)
    33  
    34  // Item is the index used to represent a stamp for a chunk.
    35  //
    36  // Going ahead we will support multiple stamps on chunks. This Item will allow
    37  // mapping multiple stamps to a single address. For this reason, the address is
    38  // part of the Namespace and can be used to iterate on all the stamps for this
    39  // address.
    40  type Item struct {
    41  	scope   []byte // The scope of other related item.
    42  	address swarm.Address
    43  	stamp   swarm.Stamp
    44  }
    45  
    46  // ID implements the storage.Item interface.
    47  func (i *Item) ID() string {
    48  	return storageutil.JoinFields(string(i.stamp.BatchID()), string(i.stamp.Index()))
    49  }
    50  
    51  // Namespace implements the storage.Item interface.
    52  func (i *Item) Namespace() string {
    53  	return storageutil.JoinFields("chunkStamp", string(i.scope), i.address.ByteString())
    54  }
    55  
    56  func (i *Item) SetScope(ns []byte) {
    57  	i.scope = ns
    58  }
    59  
    60  // Marshal implements the storage.Item interface.
    61  // address is not part of the payload which is stored, as address is part of the
    62  // prefix, hence already known before querying this object. This will be reused
    63  // during unmarshaling.
    64  func (i *Item) Marshal() ([]byte, error) {
    65  	// The address is not part of the payload, but it is used to create the
    66  	// scope so it is better if we check that the address is correctly
    67  	// set here before it is stored in the underlying storage.
    68  
    69  	switch {
    70  	case len(i.scope) == 0:
    71  		return nil, errMarshalInvalidChunkStampItemScope
    72  	case i.address.IsZero():
    73  		return nil, errMarshalInvalidChunkStampItemAddress
    74  	case i.stamp == nil:
    75  		return nil, errMarshalInvalidChunkStampItemStamp
    76  	}
    77  
    78  	buf := make([]byte, 8+len(i.scope)+postage.StampSize)
    79  
    80  	l := 0
    81  	binary.LittleEndian.PutUint64(buf[l:l+8], uint64(len(i.scope)))
    82  	l += 8
    83  	copy(buf[l:l+len(i.scope)], i.scope)
    84  	l += len(i.scope)
    85  	data, err := i.stamp.MarshalBinary()
    86  	if err != nil {
    87  		return nil, fmt.Errorf("unable to marshal chunkstamp.item: %w", err)
    88  	}
    89  	copy(buf[l:l+postage.StampSize], data)
    90  
    91  	return buf, nil
    92  }
    93  
    94  // Unmarshal implements the storage.Item interface.
    95  func (i *Item) Unmarshal(bytes []byte) error {
    96  	if len(bytes) < 8 {
    97  		return errUnmarshalInvalidChunkStampItemSize
    98  	}
    99  	nsLen := int(binary.LittleEndian.Uint64(bytes))
   100  	if len(bytes) != 8+nsLen+postage.StampSize {
   101  		return errUnmarshalInvalidChunkStampItemSize
   102  	}
   103  
   104  	// Ensure that the address is set already in the item.
   105  	if i.address.IsZero() {
   106  		return errUnmarshalInvalidChunkStampItemAddress
   107  	}
   108  
   109  	ni := &Item{address: i.address.Clone()}
   110  	l := 8
   111  	ni.scope = append(make([]byte, 0, nsLen), bytes[l:l+nsLen]...)
   112  	l += nsLen
   113  	stamp := new(postage.Stamp)
   114  	if err := stamp.UnmarshalBinary(bytes[l : l+postage.StampSize]); err != nil {
   115  		if errors.Is(err, postage.ErrStampInvalid) {
   116  			return errUnmarshalInvalidChunkStampItemSize
   117  		}
   118  		return fmt.Errorf("unable to unmarshal chunkstamp.item: %w", err)
   119  	}
   120  	ni.stamp = stamp
   121  	*i = *ni
   122  	return nil
   123  }
   124  
   125  // Clone implements the storage.Item interface.
   126  func (i *Item) Clone() storage.Item {
   127  	if i == nil {
   128  		return nil
   129  	}
   130  	clone := &Item{
   131  		scope:   append([]byte(nil), i.scope...),
   132  		address: i.address.Clone(),
   133  	}
   134  	if i.stamp != nil {
   135  		clone.stamp = i.stamp.Clone()
   136  	}
   137  	return clone
   138  }
   139  
   140  // String implements the storage.Item interface.
   141  func (i Item) String() string {
   142  	return storageutil.JoinFields(i.Namespace(), i.ID())
   143  }
   144  
   145  // Load returns first found swarm.Stamp related to the given address.
   146  func Load(s storage.Reader, scope string, addr swarm.Address) (swarm.Stamp, error) {
   147  	return LoadWithBatchID(s, scope, addr, nil)
   148  }
   149  
   150  // LoadWithBatchID returns swarm.Stamp related to the given address and batchID.
   151  func LoadWithBatchID(s storage.Reader, scope string, addr swarm.Address, batchID []byte) (swarm.Stamp, error) {
   152  	var stamp swarm.Stamp
   153  
   154  	found := false
   155  	err := s.Iterate(
   156  		storage.Query{
   157  			Factory: func() storage.Item {
   158  				return &Item{
   159  					scope:   []byte(scope),
   160  					address: addr,
   161  				}
   162  			},
   163  		},
   164  		func(res storage.Result) (bool, error) {
   165  			item := res.Entry.(*Item)
   166  			if batchID == nil || bytes.Equal(batchID, item.stamp.BatchID()) {
   167  				stamp = item.stamp
   168  				found = true
   169  				return true, nil
   170  			}
   171  			return false, nil
   172  		},
   173  	)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	if !found {
   178  		return nil, fmt.Errorf("stamp not found for batchID %x: %w", batchID, storage.ErrNotFound)
   179  	}
   180  
   181  	return stamp, nil
   182  }
   183  
   184  // Store creates new or updated an existing stamp index
   185  // record related to the given scope and chunk.
   186  func Store(s storage.IndexStore, scope string, chunk swarm.Chunk) error {
   187  	item := &Item{
   188  		scope:   []byte(scope),
   189  		address: chunk.Address(),
   190  		stamp:   chunk.Stamp(),
   191  	}
   192  	if err := s.Put(item); err != nil {
   193  		return fmt.Errorf("unable to put chunkstamp.item %s: %w", item, err)
   194  	}
   195  	return nil
   196  }
   197  
   198  // DeleteAll removes all swarm.Stamp related to the given address.
   199  func DeleteAll(s storage.IndexStore, scope string, addr swarm.Address) error {
   200  	var stamps []swarm.Stamp
   201  	err := s.Iterate(
   202  		storage.Query{
   203  			Factory: func() storage.Item {
   204  				return &Item{
   205  					scope:   []byte(scope),
   206  					address: addr,
   207  				}
   208  			},
   209  		},
   210  		func(res storage.Result) (bool, error) {
   211  			stamps = append(stamps, res.Entry.(*Item).stamp)
   212  			return false, nil
   213  		},
   214  	)
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	var errs error
   220  	for _, stamp := range stamps {
   221  		errs = errors.Join(
   222  			errs,
   223  			s.Delete(&Item{
   224  				scope:   []byte(scope),
   225  				address: addr,
   226  				stamp:   stamp,
   227  			}),
   228  		)
   229  	}
   230  	return errs
   231  }
   232  
   233  // Delete removes a stamp associated with an chunk and batchID.
   234  func Delete(s storage.IndexStore, scope string, addr swarm.Address, batchId []byte) error {
   235  	stamp, err := LoadWithBatchID(s, scope, addr, batchId)
   236  	if err != nil {
   237  		if errors.Is(err, storage.ErrNotFound) {
   238  			return nil
   239  		}
   240  		return err
   241  	}
   242  	return s.Delete(&Item{
   243  		scope:   []byte(scope),
   244  		address: addr,
   245  		stamp:   stamp,
   246  	})
   247  }
   248  
   249  func DeleteWithStamp(
   250  	writer storage.Writer,
   251  	scope string,
   252  	addr swarm.Address,
   253  	stamp swarm.Stamp,
   254  ) error {
   255  	return writer.Delete(&Item{
   256  		scope:   []byte(scope),
   257  		address: addr,
   258  		stamp:   stamp,
   259  	})
   260  }