github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/validate.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 storer
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"path"
    12  	"sync"
    13  	"time"
    14  
    15  	"sync/atomic"
    16  
    17  	"github.com/ethersphere/bee/v2/pkg/cac"
    18  	"github.com/ethersphere/bee/v2/pkg/log"
    19  	"github.com/ethersphere/bee/v2/pkg/sharky"
    20  	"github.com/ethersphere/bee/v2/pkg/soc"
    21  	"github.com/ethersphere/bee/v2/pkg/storage"
    22  	"github.com/ethersphere/bee/v2/pkg/storer/internal/chunkstore"
    23  	pinstore "github.com/ethersphere/bee/v2/pkg/storer/internal/pinning"
    24  	"github.com/ethersphere/bee/v2/pkg/swarm"
    25  )
    26  
    27  // Validate ensures that all retrievalIndex chunks are correctly stored in sharky.
    28  func ValidateReserve(ctx context.Context, basePath string, opts *Options) error {
    29  
    30  	logger := opts.Logger
    31  
    32  	store, err := initStore(basePath, opts)
    33  	if err != nil {
    34  		return fmt.Errorf("failed creating levelDB index store: %w", err)
    35  	}
    36  	defer func() {
    37  		if err := store.Close(); err != nil {
    38  			logger.Error(err, "failed closing store")
    39  		}
    40  	}()
    41  
    42  	sharky, err := sharky.New(&dirFS{basedir: path.Join(basePath, sharkyPath)},
    43  		sharkyNoOfShards, swarm.SocMaxChunkSize)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	defer func() {
    48  		if err := sharky.Close(); err != nil {
    49  			logger.Error(err, "failed closing sharky")
    50  		}
    51  	}()
    52  
    53  	logger.Info("performing chunk validation")
    54  
    55  	validateWork(logger, store, sharky.Read)
    56  
    57  	return nil
    58  }
    59  
    60  // ValidateRetrievalIndex ensures that all retrievalIndex chunks are correctly stored in sharky.
    61  func ValidateRetrievalIndex(ctx context.Context, basePath string, opts *Options) error {
    62  
    63  	logger := opts.Logger
    64  
    65  	store, err := initStore(basePath, opts)
    66  	if err != nil {
    67  		return fmt.Errorf("failed creating levelDB index store: %w", err)
    68  	}
    69  	defer func() {
    70  		if err := store.Close(); err != nil {
    71  			logger.Error(err, "failed closing store")
    72  		}
    73  	}()
    74  
    75  	sharky, err := sharky.New(&dirFS{basedir: path.Join(basePath, sharkyPath)},
    76  		sharkyNoOfShards, swarm.SocMaxChunkSize)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	defer func() {
    81  		if err := sharky.Close(); err != nil {
    82  			logger.Error(err, "failed closing sharky")
    83  		}
    84  	}()
    85  
    86  	logger.Info("performing chunk validation")
    87  	validateWork(logger, store, sharky.Read)
    88  
    89  	return nil
    90  }
    91  
    92  func validateWork(logger log.Logger, store storage.Store, readFn func(context.Context, sharky.Location, []byte) error) {
    93  
    94  	total := 0
    95  	socCount := 0
    96  	invalidCount := 0
    97  
    98  	n := time.Now()
    99  	defer func() {
   100  		logger.Info("validation finished", "duration", time.Since(n), "invalid", invalidCount, "soc", socCount, "total", total)
   101  	}()
   102  
   103  	iteratateItemsC := make(chan *chunkstore.RetrievalIndexItem)
   104  
   105  	validChunk := func(item *chunkstore.RetrievalIndexItem, buf []byte) {
   106  		err := readFn(context.Background(), item.Location, buf)
   107  		if err != nil {
   108  			logger.Warning("invalid chunk", "address", item.Address, "timestamp", time.Unix(int64(item.Timestamp), 0), "location", item.Location, "error", err)
   109  			return
   110  		}
   111  
   112  		ch := swarm.NewChunk(item.Address, buf)
   113  		if !cac.Valid(ch) {
   114  			if soc.Valid(ch) {
   115  				socCount++
   116  				logger.Debug("found soc chunk", "address", item.Address, "timestamp", time.Unix(int64(item.Timestamp), 0))
   117  			} else {
   118  				invalidCount++
   119  				logger.Warning("invalid cac/soc chunk", "address", item.Address, "timestamp", time.Unix(int64(item.Timestamp), 0))
   120  
   121  				h, err := cac.DoHash(buf[swarm.SpanSize:], buf[:swarm.SpanSize])
   122  				if err != nil {
   123  					logger.Error(err, "cac hash")
   124  					return
   125  				}
   126  
   127  				computedAddr := swarm.NewAddress(h)
   128  
   129  				if !cac.Valid(swarm.NewChunk(computedAddr, buf)) {
   130  					logger.Warning("computed chunk is also an invalid cac", "err", err)
   131  					return
   132  				}
   133  
   134  				sharedEntry := chunkstore.RetrievalIndexItem{Address: computedAddr}
   135  				err = store.Get(&sharedEntry)
   136  				if err != nil {
   137  					logger.Warning("no shared entry found")
   138  					return
   139  				}
   140  
   141  				logger.Warning("retrieved chunk with shared slot", "shared_address", sharedEntry.Address, "shared_timestamp", time.Unix(int64(sharedEntry.Timestamp), 0))
   142  			}
   143  		}
   144  	}
   145  
   146  	s := time.Now()
   147  
   148  	_ = chunkstore.IterateItems(store, func(item *chunkstore.RetrievalIndexItem) error {
   149  		total++
   150  		return nil
   151  	})
   152  	logger.Info("validation count finished", "duration", time.Since(s), "total", total)
   153  
   154  	var wg sync.WaitGroup
   155  
   156  	for i := 0; i < 8; i++ {
   157  		wg.Add(1)
   158  		go func() {
   159  			defer wg.Done()
   160  			buf := make([]byte, swarm.SocMaxChunkSize)
   161  			for item := range iteratateItemsC {
   162  				validChunk(item, buf[:item.Location.Length])
   163  			}
   164  		}()
   165  	}
   166  
   167  	count := 0
   168  	_ = chunkstore.IterateItems(store, func(item *chunkstore.RetrievalIndexItem) error {
   169  		iteratateItemsC <- item
   170  		count++
   171  		if count%100_000 == 0 {
   172  			logger.Info("..still validating chunks", "count", count, "invalid", invalidCount, "soc", socCount, "total", total, "percent", fmt.Sprintf("%.2f", (float64(count)*100.0)/float64(total)))
   173  		}
   174  		return nil
   175  	})
   176  
   177  	close(iteratateItemsC)
   178  
   179  	wg.Wait()
   180  }
   181  
   182  // ValidatePinCollectionChunks collects all chunk addresses that are present in a pin collection but
   183  // are either invalid or missing altogether.
   184  func ValidatePinCollectionChunks(ctx context.Context, basePath, pin, location string, opts *Options) error {
   185  	logger := opts.Logger
   186  
   187  	store, err := initStore(basePath, opts)
   188  	if err != nil {
   189  		return fmt.Errorf("failed creating levelDB index store: %w", err)
   190  	}
   191  	defer func() {
   192  		if err := store.Close(); err != nil {
   193  			logger.Error(err, "failed closing store")
   194  		}
   195  	}()
   196  
   197  	fs := &dirFS{basedir: path.Join(basePath, sharkyPath)}
   198  	sharky, err := sharky.New(fs, sharkyNoOfShards, swarm.SocMaxChunkSize)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	defer func() {
   203  		if err := sharky.Close(); err != nil {
   204  			logger.Error(err, "failed closing sharky")
   205  		}
   206  	}()
   207  
   208  	logger.Info("performing chunk validation")
   209  
   210  	pv := PinIntegrity{
   211  		Store:  store,
   212  		Sharky: sharky,
   213  	}
   214  
   215  	var (
   216  		fileName = "address.csv"
   217  		fileLoc  = "."
   218  	)
   219  
   220  	if location != "" {
   221  		if path.Ext(location) != "" {
   222  			fileName = path.Base(location)
   223  		}
   224  		fileLoc = path.Dir(location)
   225  	}
   226  
   227  	logger.Info("saving stats", "location", fileLoc, "name", fileName)
   228  
   229  	location = path.Join(fileLoc, fileName)
   230  
   231  	f, err := os.OpenFile(location, os.O_CREATE|os.O_WRONLY, 0644)
   232  	if err != nil {
   233  		return fmt.Errorf("open output file for writing: %w", err)
   234  	}
   235  
   236  	if _, err := f.WriteString("invalid\tmissing\ttotal\taddress\n"); err != nil {
   237  		return fmt.Errorf("write title: %w", err)
   238  	}
   239  
   240  	defer f.Close()
   241  
   242  	var ch = make(chan PinStat)
   243  	go pv.Check(ctx, logger, pin, ch)
   244  
   245  	for st := range ch {
   246  		report := fmt.Sprintf("%d\t%d\t%d\t%s\n", st.Invalid, st.Missing, st.Total, st.Ref)
   247  
   248  		if _, err := f.WriteString(report); err != nil {
   249  			logger.Error(err, "write report line")
   250  			break
   251  		}
   252  	}
   253  
   254  	return nil
   255  }
   256  
   257  type PinIntegrity struct {
   258  	Store  storage.Store
   259  	Sharky *sharky.Store
   260  }
   261  
   262  type PinStat struct {
   263  	Ref                     swarm.Address
   264  	Total, Missing, Invalid int
   265  }
   266  
   267  func (p *PinIntegrity) Check(ctx context.Context, logger log.Logger, pin string, out chan PinStat) {
   268  	var stats struct {
   269  		total, read, invalid atomic.Int32
   270  	}
   271  
   272  	n := time.Now()
   273  	defer func() {
   274  		close(out)
   275  		logger.Info("done", "duration", time.Since(n), "read", stats.read.Load(), "invalid", stats.invalid.Load(), "total", stats.total.Load())
   276  	}()
   277  
   278  	validChunk := func(item *chunkstore.RetrievalIndexItem, buf []byte) bool {
   279  		stats.total.Add(1)
   280  
   281  		if err := p.Sharky.Read(ctx, item.Location, buf); err != nil {
   282  			stats.read.Add(1)
   283  			return false
   284  		}
   285  
   286  		ch := swarm.NewChunk(item.Address, buf)
   287  
   288  		if cac.Valid(ch) {
   289  			return true
   290  		}
   291  
   292  		if soc.Valid(ch) {
   293  			return true
   294  		}
   295  
   296  		stats.invalid.Add(1)
   297  
   298  		return false
   299  	}
   300  
   301  	var pins []swarm.Address
   302  
   303  	if pin != "" {
   304  		addr, err := swarm.ParseHexAddress(pin)
   305  		if err != nil {
   306  			panic(fmt.Sprintf("parse provided pin: %s", err))
   307  		}
   308  		pins = append(pins, addr)
   309  	} else {
   310  		var err error
   311  		pins, err = pinstore.Pins(p.Store)
   312  		if err != nil {
   313  			logger.Error(err, "get pins")
   314  			return
   315  		}
   316  	}
   317  
   318  	logger.Info("got a total number of pins", "size", len(pins))
   319  
   320  	var tcount, tmicrs int64
   321  	defer func() {
   322  		dur := float64(tmicrs) / float64(tcount)
   323  		logger.Info("done iterating pins", "duration", dur)
   324  	}()
   325  
   326  	for _, pin := range pins {
   327  		var wg sync.WaitGroup
   328  		var (
   329  			total, missing, invalid atomic.Int32
   330  		)
   331  
   332  		iteratateItemsC := make(chan *chunkstore.RetrievalIndexItem)
   333  
   334  		for i := 0; i < 8; i++ {
   335  			wg.Add(1)
   336  			go func() {
   337  				defer wg.Done()
   338  				buf := make([]byte, swarm.SocMaxChunkSize)
   339  				for item := range iteratateItemsC {
   340  					if ctx.Err() != nil {
   341  						break
   342  					}
   343  					if !validChunk(item, buf[:item.Location.Length]) {
   344  						invalid.Add(1)
   345  					}
   346  				}
   347  			}()
   348  		}
   349  
   350  		var count, micrs int64
   351  
   352  		err := pinstore.IterateCollection(p.Store, pin, func(addr swarm.Address) (bool, error) {
   353  			n := time.Now()
   354  
   355  			defer func() {
   356  				count++
   357  				micrs += time.Since(n).Microseconds()
   358  			}()
   359  
   360  			total.Add(1)
   361  
   362  			rIdx := &chunkstore.RetrievalIndexItem{Address: addr}
   363  			if err := p.Store.Get(rIdx); err != nil {
   364  				missing.Add(1)
   365  			} else {
   366  				select {
   367  				case <-ctx.Done():
   368  					return true, nil
   369  				case iteratateItemsC <- rIdx:
   370  				}
   371  			}
   372  
   373  			return false, nil
   374  		})
   375  
   376  		dur := float64(micrs) / float64(count)
   377  
   378  		if err != nil {
   379  			logger.Error(err, "new iteration", "pin", pin, "duration", dur)
   380  		} else {
   381  			logger.Info("new iteration", "pin", pin, "duration", dur)
   382  		}
   383  
   384  		tcount++
   385  		tmicrs += int64(dur)
   386  
   387  		close(iteratateItemsC)
   388  
   389  		wg.Wait()
   390  
   391  		select {
   392  		case <-ctx.Done():
   393  			logger.Info("context done")
   394  			return
   395  		case out <- PinStat{
   396  			Ref:     pin,
   397  			Total:   int(total.Load()),
   398  			Missing: int(missing.Load()),
   399  			Invalid: int(invalid.Load())}:
   400  		}
   401  	}
   402  }