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  }