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 }