github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/chunks/handle_diff.go (about)

     1  package chunksPkg
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    21  )
    22  
    23  var nVisited int
    24  
    25  func (opts *ChunksOptions) HandleDiff(rCtx *output.RenderCtx, blockNums []base.Blknum) error {
    26  	chain := opts.Globals.Chain
    27  	testMode := opts.Globals.TestMode
    28  
    29  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    30  		walker := walk.NewCacheWalker(
    31  			chain,
    32  			testMode,
    33  			10000, /* maxTests */
    34  			func(walker *walk.CacheWalker, path string, first bool) (bool, error) {
    35  				return opts.handleDiff(chain, path)
    36  			},
    37  		)
    38  
    39  		logger.Info("Walking the bloom files at...", config.PathToIndex(chain))
    40  		if !file.FolderExists(config.PathToIndex(chain)) {
    41  			logger.Fatal(fmt.Sprintf("The index folder does not exist: [%s]", config.PathToIndex(chain)))
    42  		}
    43  
    44  		if err := walker.WalkBloomFilters(blockNums); err != nil {
    45  			errorChan <- err
    46  			rCtx.Cancel()
    47  		}
    48  
    49  		if nVisited == 0 {
    50  			logger.Warn("No bloom filters were visited. Does the block number exist in the finalized folder?")
    51  		}
    52  	}
    53  
    54  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOpts())
    55  }
    56  
    57  func (opts *ChunksOptions) handleDiff(chain, path string) (bool, error) {
    58  	nVisited++
    59  
    60  	srcPath, diffPath, rd := opts.getParams(chain, path)
    61  
    62  	logger.Info("Comparing:")
    63  	logger.Info(fmt.Sprintf("  current (one):  %s (%d)", srcPath, file.FileSize(srcPath)))
    64  	logger.Info(fmt.Sprintf("  diffPath (two): %s (%d)", diffPath, file.FileSize(diffPath)))
    65  
    66  	if _, err := opts.exportTo("one", srcPath, rd); err != nil {
    67  		return false, err
    68  	}
    69  
    70  	if _, err := opts.exportTo("two", diffPath, rd); err != nil {
    71  		return false, err
    72  	}
    73  
    74  	return true, nil
    75  }
    76  
    77  func writeArray(disp, dest, fn string, lines []string) error {
    78  	outputFolder, _ := filepath.Abs("./" + dest)
    79  	if !file.FolderExists(outputFolder) {
    80  		if err := os.MkdirAll(outputFolder, os.ModePerm); err != nil {
    81  			return err
    82  		}
    83  	}
    84  	outputFile := filepath.Join(outputFolder, fmt.Sprintf("%s_%s.txt", fn, disp))
    85  	if err := file.LinesToAsciiFile(outputFile, lines); err != nil {
    86  		return err
    87  	}
    88  	logger.Info(colors.Colored(fmt.Sprintf("Wrote {%d} lines to {%s}", len(lines), outputFile)))
    89  	return nil
    90  }
    91  
    92  func (opts *ChunksOptions) exportTo(dest, source string, rd base.RangeDiff) (bool, error) {
    93  	indexChunk, err := index.OpenIndex(source, true /* check */)
    94  	if err != nil {
    95  		return false, err
    96  	}
    97  	defer indexChunk.Close()
    98  
    99  	_, err = indexChunk.File.Seek(int64(index.HeaderWidth), io.SeekStart)
   100  	if err != nil {
   101  		return false, err
   102  	}
   103  
   104  	apps := make([]types.Appearance, 0, 500000)
   105  	for i := 0; i < int(indexChunk.Header.AddressCount); i++ {
   106  		s := types.AppearanceTable{}
   107  		if err := binary.Read(indexChunk.File, binary.LittleEndian, &s.AddressRecord); err != nil {
   108  			return false, err
   109  		}
   110  		if s.Appearances, err = indexChunk.ReadAppearancesAndReset(&s.AddressRecord); err != nil {
   111  			return false, err
   112  		}
   113  		s.AddressRecord.Count = uint32(len(s.Appearances))
   114  		for _, app := range s.Appearances {
   115  			apps = append(apps, types.Appearance{
   116  				Address:          s.AddressRecord.Address,
   117  				BlockNumber:      app.BlockNumber,
   118  				TransactionIndex: app.TransactionIndex,
   119  			})
   120  		}
   121  	}
   122  
   123  	sort.Slice(apps, func(i, j int) bool {
   124  		if apps[i].BlockNumber == apps[j].BlockNumber {
   125  			if apps[i].TransactionIndex == apps[j].TransactionIndex {
   126  				return apps[i].Address.Hex() < apps[j].Address.Hex()
   127  			}
   128  			return apps[i].TransactionIndex < apps[j].TransactionIndex
   129  		}
   130  		return apps[i].BlockNumber < apps[j].BlockNumber
   131  	})
   132  
   133  	filtered := func(app types.Appearance) bool {
   134  		return base.Txnum(app.TransactionIndex) == types.WithdrawalAmt
   135  		// return false
   136  	}
   137  
   138  	pre := make([]string, 0, len(apps))
   139  	out := make([]string, 0, len(apps))
   140  	post := make([]string, 0, len(apps))
   141  	for _, app := range apps {
   142  		if !filtered(app) &&
   143  			(app.Address != base.SentinalAddr || base.Txnum(app.TransactionIndex) != types.MisconfigReward) {
   144  			line := fmt.Sprintf("%d\t%d\t%s", app.BlockNumber, app.TransactionIndex, app.Address)
   145  			bn := base.Blknum(app.BlockNumber)
   146  			if bn < rd.In {
   147  				pre = append(pre, line)
   148  			} else if bn > rd.Out {
   149  				post = append(post, line)
   150  			} else {
   151  				out = append(out, line)
   152  			}
   153  		}
   154  	}
   155  
   156  	outFn := os.Getenv("TB_CHUCKS_DIFFOUT")
   157  	if len(outFn) == 0 {
   158  		outFn = fmt.Sprintf("%d", rd.Mid)
   159  	}
   160  	if err = writeArray("apps", dest, outFn, out); err != nil {
   161  		return false, err
   162  	}
   163  
   164  	if len(pre) > 0 {
   165  		preFn := os.Getenv("TB_CHUNKS_PREOUT")
   166  		dets := ""
   167  		if len(preFn) == 0 {
   168  			preFn = fmt.Sprintf("%d-%d", rd.Min, rd.In-1)
   169  			dets = "pre"
   170  		}
   171  		if err = writeArray(dets, dest, preFn, pre); err != nil {
   172  			return false, err
   173  		}
   174  	}
   175  
   176  	if len(post) > 0 {
   177  		postFn := os.Getenv("TB_CHUNKS_POSTOUT")
   178  		dets := ""
   179  		if len(postFn) == 0 {
   180  			postFn = fmt.Sprintf("%d-%d", rd.Out+1, rd.Max)
   181  			dets = "post"
   182  		}
   183  		if err = writeArray(dets, dest, postFn, post); err != nil {
   184  			return false, err
   185  		}
   186  	}
   187  
   188  	return true, nil
   189  }
   190  
   191  // findFileByBlockNumber returns the path to a file whose range intersects the given block number.
   192  func findFileByBlockNumber(chain, path string, bn base.Blknum) (fileName string, err error) {
   193  	walker := walk.NewCacheWalker(
   194  		chain,
   195  		false,
   196  		10000, /* maxTests */
   197  		func(walker *walk.CacheWalker, path string, first bool) (bool, error) {
   198  			rng := base.RangeFromFilename(path)
   199  			if rng.IntersectsB(bn) {
   200  				fileName = index.ToIndexPath(path)
   201  				return false, nil // stop walking
   202  			}
   203  			return true, nil // continue walking
   204  		},
   205  	)
   206  	return fileName, walker.WalkRegularFolder(path)
   207  }
   208  
   209  func (opts *ChunksOptions) getParams(chain, path string) (string, string, base.RangeDiff) {
   210  	srcPath := index.ToIndexPath(path)
   211  	thisRange := base.RangeFromFilename(srcPath)
   212  	middleMark := thisRange.First + (thisRange.Last-thisRange.First)/2 // this mark is used to find the diffPath
   213  	diffPath := toDiffPath(chain, middleMark)
   214  	diffRange := base.RangeFromFilename(diffPath)
   215  
   216  	return srcPath, diffPath, thisRange.Overlaps(diffRange)
   217  }
   218  
   219  func toDiffPath(chain string, middleMark base.Blknum) string {
   220  	diffPath := os.Getenv("TB_CHUNKS_DIFFPATH")
   221  	if !strings.Contains(diffPath, "unchained") {
   222  		diffPath = filepath.Join(diffPath, "unchained", chain, "finalized")
   223  	}
   224  	diffPath, _ = filepath.Abs(diffPath)
   225  	diffPath, _ = findFileByBlockNumber(chain, diffPath, middleMark)
   226  	if !file.FileExists(diffPath) {
   227  		logger.Fatal(fmt.Sprintf("The diff path does not exist: [%s]", diffPath))
   228  	}
   229  	return diffPath
   230  }