github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/mock/mockreserve.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 mockstorer 6 7 import ( 8 "context" 9 "math/big" 10 "sync" 11 12 "github.com/ethersphere/bee/v2/pkg/storage" 13 "github.com/ethersphere/bee/v2/pkg/storer" 14 "github.com/ethersphere/bee/v2/pkg/swarm" 15 ) 16 17 type chunksResponse struct { 18 chunks []*storer.BinC 19 err error 20 } 21 22 // WithSubscribeResp mocks a desired response when calling IntervalChunks method. 23 // Different possible responses for subsequent responses in multi-call scenarios 24 // are possible (i.e. first call yields a,b,c, second call yields d,e,f). 25 // Mock maintains state of current call using chunksCalls counter. 26 func WithSubscribeResp(chunks []*storer.BinC, err error) Option { 27 return optionFunc(func(p *ReserveStore) { 28 p.subResponses = append(p.subResponses, chunksResponse{chunks: chunks, err: err}) 29 }) 30 } 31 32 // WithChunks mocks the set of chunks that the store is aware of (used in Get and Has calls). 33 func WithChunks(chs ...swarm.Chunk) Option { 34 return optionFunc(func(p *ReserveStore) { 35 for _, c := range chs { 36 c := c 37 if c.Stamp() != nil { 38 stampHash, _ := c.Stamp().Hash() 39 p.chunks[c.Address().String()+string(c.Stamp().BatchID())+string(stampHash)] = c 40 } else { 41 p.chunks[c.Address().String()] = c 42 } 43 } 44 }) 45 } 46 47 // WithEvilChunk allows to inject a malicious chunk (request a certain address 48 // of a chunk, but get another), in order to mock unsolicited chunk delivery. 49 func WithEvilChunk(addr swarm.Address, ch swarm.Chunk) Option { 50 return optionFunc(func(p *ReserveStore) { 51 p.evilAddr = addr 52 p.evilChunk = ch 53 }) 54 } 55 56 func WithCursors(c []uint64, e uint64) Option { 57 return optionFunc(func(p *ReserveStore) { 58 p.cursors = c 59 p.epoch = e 60 }) 61 } 62 63 func WithCursorsErr(e error) Option { 64 return optionFunc(func(p *ReserveStore) { 65 p.cursorsErr = e 66 }) 67 } 68 69 func WithRadius(r uint8) Option { 70 return optionFunc(func(p *ReserveStore) { 71 p.radius = r 72 }) 73 } 74 75 func WithReserveSize(s int) Option { 76 return optionFunc(func(p *ReserveStore) { 77 p.reservesize = s 78 }) 79 } 80 81 func WithPutHook(f func(swarm.Chunk) error) Option { 82 return optionFunc(func(p *ReserveStore) { 83 p.putHook = f 84 }) 85 } 86 87 func WithSample(s storer.Sample) Option { 88 return optionFunc(func(p *ReserveStore) { 89 p.sample = s 90 }) 91 } 92 93 var _ storer.ReserveStore = (*ReserveStore)(nil) 94 95 type ReserveStore struct { 96 mtx sync.Mutex 97 chunksCalls int 98 putCalls int 99 setCalls int 100 101 chunks map[string]swarm.Chunk 102 evilAddr swarm.Address 103 evilChunk swarm.Chunk 104 105 cursors []uint64 106 cursorsErr error 107 epoch uint64 108 109 radius uint8 110 reservesize int 111 112 subResponses []chunksResponse 113 putHook func(swarm.Chunk) error 114 115 sample storer.Sample 116 } 117 118 // NewReserve returns a new Reserve mock. 119 func NewReserve(opts ...Option) *ReserveStore { 120 s := &ReserveStore{ 121 chunks: make(map[string]swarm.Chunk), 122 } 123 for _, v := range opts { 124 v.apply(s) 125 } 126 return s 127 } 128 129 func (s *ReserveStore) EvictBatch(ctx context.Context, batchID []byte) error { return nil } 130 func (s *ReserveStore) IsWithinStorageRadius(addr swarm.Address) bool { return true } 131 func (s *ReserveStore) IsFullySynced() bool { return true } 132 func (s *ReserveStore) StorageRadius() uint8 { 133 s.mtx.Lock() 134 defer s.mtx.Unlock() 135 return s.radius 136 } 137 func (s *ReserveStore) SetStorageRadius(r uint8) { 138 s.mtx.Lock() 139 s.radius = r 140 s.mtx.Unlock() 141 } 142 143 // IntervalChunks returns a set of chunk in a requested interval. 144 func (s *ReserveStore) SubscribeBin(ctx context.Context, bin uint8, start uint64) (<-chan *storer.BinC, func(), <-chan error) { 145 s.mtx.Lock() 146 defer s.mtx.Unlock() 147 148 r := s.subResponses[s.chunksCalls] 149 s.chunksCalls++ 150 151 out := make(chan *storer.BinC) 152 errC := make(chan error, 1) 153 154 go func() { 155 for _, c := range r.chunks { 156 select { 157 case out <- &storer.BinC{Address: c.Address, BatchID: c.BatchID, BinID: c.BinID, StampHash: c.StampHash}: 158 case <-ctx.Done(): 159 select { 160 case errC <- ctx.Err(): 161 default: 162 } 163 } 164 } 165 }() 166 167 return out, func() {}, errC 168 } 169 170 func (s *ReserveStore) ReserveSize() int { 171 return s.reservesize 172 } 173 174 func (s *ReserveStore) ReserveLastBinIDs() (curs []uint64, epoch uint64, err error) { 175 return s.cursors, s.epoch, s.cursorsErr 176 } 177 178 // PutCalls returns the amount of times Put was called. 179 func (s *ReserveStore) PutCalls() int { 180 s.mtx.Lock() 181 defer s.mtx.Unlock() 182 return s.putCalls 183 } 184 185 // SetCalls returns the amount of times Set was called. 186 func (s *ReserveStore) SetCalls() int { 187 s.mtx.Lock() 188 defer s.mtx.Unlock() 189 return s.setCalls 190 } 191 192 // Get chunks. 193 func (s *ReserveStore) ReserveGet(ctx context.Context, addr swarm.Address, batchID []byte, stampHash []byte) (swarm.Chunk, error) { 194 if s.evilAddr.Equal(addr) { 195 //inject the malicious chunk instead 196 return s.evilChunk, nil 197 } 198 199 if v, ok := s.chunks[addr.String()+string(batchID)+string(stampHash)]; ok { 200 return v, nil 201 } 202 203 return nil, storage.ErrNotFound 204 } 205 206 // Put chunks. 207 func (s *ReserveStore) ReservePutter() storage.Putter { 208 return storage.PutterFunc( 209 func(ctx context.Context, c swarm.Chunk) error { 210 s.mtx.Lock() 211 s.putCalls++ 212 s.mtx.Unlock() 213 return s.put(ctx, c) 214 }, 215 ) 216 } 217 218 // Put chunks. 219 func (s *ReserveStore) put(_ context.Context, chs ...swarm.Chunk) error { 220 s.mtx.Lock() 221 defer s.mtx.Unlock() 222 for _, c := range chs { 223 c := c 224 if s.putHook != nil { 225 if err := s.putHook(c); err != nil { 226 return err 227 } 228 } 229 stampHash, err := c.Stamp().Hash() 230 if err != nil { 231 return err 232 } 233 s.chunks[c.Address().String()+string(c.Stamp().BatchID())+string(stampHash)] = c 234 } 235 return nil 236 } 237 238 // Has chunks. 239 func (s *ReserveStore) ReserveHas(addr swarm.Address, batchID []byte, stampHash []byte) (bool, error) { 240 if _, ok := s.chunks[addr.String()+string(batchID)+string(stampHash)]; !ok { 241 return false, nil 242 } 243 return true, nil 244 } 245 246 func (s *ReserveStore) ReserveSample(context.Context, []byte, uint8, uint64, *big.Int) (storer.Sample, error) { 247 return s.sample, nil 248 } 249 250 type Option interface { 251 apply(*ReserveStore) 252 } 253 type optionFunc func(*ReserveStore) 254 255 func (f optionFunc) apply(r *ReserveStore) { f(r) }