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 }