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 }