github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/chunks/handle_check_deep.go (about) 1 // Copyright 2021 The TrueBlocks Authors. All rights reserved. 2 // Use of this source code is governed by a license that can 3 // be found in the LICENSE file. 4 5 package chunksPkg 6 7 import ( 8 "context" 9 "encoding/binary" 10 "fmt" 11 "io" 12 "sync" 13 14 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 15 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 17 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" 18 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index" 19 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 20 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/manifest" 21 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 22 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" 23 shell "github.com/ipfs/go-ipfs-api" 24 ) 25 26 type reporter struct { 27 chunk *types.ChunkRecord 28 report *types.ReportCheck 29 mutex *sync.Mutex 30 } 31 32 // CheckDeep digs deep into the data. In `index` mode, it opens each index and checks 33 // that all addresses in the index return true when checked against its corresponding 34 // Bloom filter. In `manifest` mode, it checks that each IPFS hash in the manifest is 35 // actually pinned. The later requires a locally running IPFS node. 36 func (opts *ChunksOptions) CheckDeep(cacheMan *manifest.Manifest, report *types.ReportCheck) error { 37 chain := opts.Globals.Chain 38 testMode := opts.Globals.TestMode 39 nErrors := 0 40 41 mutex := sync.Mutex{} 42 appMap := make(map[string]*reporter) 43 for _, chunk := range cacheMan.ChunkMap { 44 appMap[chunk.Range] = &reporter{chunk, report, &mutex} 45 } 46 47 showProgress := opts.Globals.ShowProgress() 48 bar := logger.NewBar(logger.BarOptions{ 49 Enabled: showProgress, 50 Total: int64(len(appMap)), 51 }) 52 53 addrCnt := 0 54 55 var sh *shell.Shell 56 var iterFunc func(rangeStr string, item *reporter) (err error) 57 if opts.Mode == "index" { 58 logger.Info("Checking each address in each index against its Bloom filter...") 59 iterFunc = func(rangeStr string, item *reporter) (err error) { 60 rng := base.RangeFromRangeString(item.chunk.Range) 61 path := rng.RangeToFilename(chain) 62 bl, err := index.OpenBloom(index.ToBloomPath(path), true /* check */) 63 if err != nil { 64 return 65 } 66 defer bl.Close() 67 68 misses := 0 69 path = index.ToIndexPath(path) // it may not exist if user did not do chifra init --all for example 70 if file.FileExists(path) { 71 indexChunk, err := index.OpenIndex(path, true /* check */) 72 if err != nil { 73 return err 74 } 75 defer indexChunk.Close() 76 77 _, err = indexChunk.File.Seek(int64(index.HeaderWidth), io.SeekStart) 78 if err != nil { 79 return err 80 } 81 82 for i := 0; i < int(indexChunk.Header.AddressCount); i++ { 83 obj := types.AddrRecord{} 84 if err := binary.Read(indexChunk.File, binary.LittleEndian, &obj); err != nil { 85 return err 86 } 87 if !bl.IsMember(obj.Address) { 88 fmt.Println("X", colors.Yellow, "bloom miss", obj.Address, "in", item.chunk.Range, colors.Off) 89 misses++ 90 } 91 addrCnt++ 92 if i%8000 == 0 { 93 bar.Prefix = fmt.Sprintf("Checked %d addresses against %d Blooms", addrCnt, len(appMap)) 94 bar.Tick() 95 } 96 } 97 98 item.mutex.Lock() 99 defer item.mutex.Unlock() 100 report.VisitedCnt++ 101 report.CheckedCnt++ 102 if misses == 0 { 103 report.PassedCnt++ 104 } 105 106 return nil 107 } 108 return nil 109 } 110 111 } else if opts.Mode == "manifest" { 112 sh = shell.NewShell(config.GetPinning().LocalPinUrl) 113 iterFunc = func(rangeStr string, item *reporter) (err error) { 114 bar.Tick() 115 err = checkHashes(item.chunk, "bloom", sh, item) 116 if err != nil { 117 return err 118 } 119 return checkHashes(item.chunk, "index", sh, item) 120 } 121 } else { 122 return fmt.Errorf("unknown mode: %s", opts.Mode) 123 } 124 125 iterErrorChan := make(chan error) 126 iterCtx, iterCancel := context.WithCancel(context.Background()) 127 defer iterCancel() 128 go utils.IterateOverMap(iterCtx, iterErrorChan, appMap, iterFunc) 129 for err := range iterErrorChan { 130 if !testMode || nErrors == 0 { 131 logger.Fatal(err) 132 nErrors++ 133 } 134 } 135 bar.Finish(true /* newLine */) 136 137 return nil 138 } 139 140 func checkHashes(chunk *types.ChunkRecord, which string, sh *shell.Shell, report *reporter) error { 141 h := chunk.BloomHash.String() 142 // sz := int(chunk.BloomSize) 143 if which == "index" { 144 h = chunk.IndexHash.String() 145 // sz = int(chunk.IndexSize) 146 } 147 148 hash, _, err := sh.BlockStat(h) 149 150 report.mutex.Lock() 151 defer report.mutex.Unlock() 152 153 report.report.VisitedCnt++ 154 report.report.CheckedCnt++ 155 if err != nil { 156 err = fmt.Errorf("%s %s is not pinned: %w", which, h, err) 157 } else if hash != h { 158 err = fmt.Errorf("%s hash (%s) mismatch (%s)", which, h, hash) 159 // } else if size != sz { 160 // err = fmt.Errorf("%s size (%d) mismatch (%d)", which, sz, size) 161 } else { 162 report.report.PassedCnt++ 163 } 164 165 if err != nil { 166 msg := fmt.Sprintf("%s %s: %s", which, chunk.Range, err) 167 report.report.MsgStrings = append(report.report.MsgStrings, msg) 168 } 169 170 return nil 171 }