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 }