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

     1  package chunksPkg
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/manifest"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/monitor"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/usage"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    21  )
    22  
    23  // TODO: We need to make sure, when we truncate, that we truncate the corresponding maps files as well.
    24  
    25  func (opts *ChunksOptions) HandleTruncate(rCtx *output.RenderCtx, blockNums []base.Blknum) error {
    26  	chain := opts.Globals.Chain
    27  	if opts.Globals.TestMode {
    28  		logger.Warn("Truncate option not tested.")
    29  		return nil
    30  	}
    31  
    32  	if !opts.Globals.IsApiMode() &&
    33  		!usage.QueryUser(strings.Replace(truncateWarning, "{0}", fmt.Sprintf("%d", opts.Truncate), -1), "Not truncated") {
    34  		return nil
    35  	}
    36  
    37  	_ = file.CleanFolder(chain, config.PathToIndex(chain), []string{"ripe", "unripe", "maps", "staging"})
    38  
    39  	showProgress := opts.Globals.ShowProgressNotTesting()
    40  	bar := logger.NewBar(logger.BarOptions{
    41  		Enabled: showProgress,
    42  		Total:   128,
    43  		Type:    logger.Expanding,
    44  	})
    45  
    46  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    47  
    48  		// First, we will remove the chunks and update the manifest. We do this separately for
    49  		// each chunk, so that if we get interrupted, we have a relatively sane state (although,
    50  		// we will have to manually repair the index with chifra init --all if this fails. Keep track
    51  		// of the last chunks remaining.
    52  		latestChunk := base.Blknum(0)
    53  		nChunksRemoved := 0
    54  		truncateIndex := func(walker *walk.CacheWalker, path string, first bool) (bool, error) {
    55  			if path != index.ToBloomPath(path) {
    56  				logger.Fatal("should not happen ==> we're spinning through the bloom filters")
    57  			}
    58  
    59  			if strings.HasSuffix(path, ".gz") {
    60  				os.Remove(path)
    61  				return true, nil
    62  			}
    63  
    64  			rng, err := base.RangeFromFilenameE(path)
    65  			if err != nil {
    66  				return false, err
    67  			}
    68  
    69  			testRange := base.FileRange{First: opts.Truncate, Last: base.NOPOSN}
    70  			if rng.Intersects(testRange) {
    71  				if err = manifest.RemoveChunk(chain, opts.PublisherAddr, index.ToBloomPath(path), index.ToIndexPath(path)); err != nil {
    72  					return false, err
    73  				}
    74  				bar.Prefix = fmt.Sprintf("Removing %s     ", rng)
    75  				nChunksRemoved++
    76  			} else {
    77  				// We did not remove the chunk, so we need to keep track of where the truncated index ends
    78  				latestChunk = base.Max(latestChunk, rng.Last)
    79  				bar.Prefix = fmt.Sprintf("Not removing %s", rng)
    80  			}
    81  			bar.Tick()
    82  			return true, nil
    83  		}
    84  
    85  		walker := walk.NewCacheWalker(
    86  			chain,
    87  			opts.Globals.TestMode,
    88  			100, /* maxTests */
    89  			truncateIndex,
    90  		)
    91  		if err := walker.WalkBloomFilters(blockNums); err != nil {
    92  			errorChan <- err
    93  			rCtx.Cancel()
    94  
    95  		} else {
    96  			bar.Prefix = fmt.Sprintf("Truncated to %d                    ", opts.Truncate)
    97  			bar.Finish(true /* newLine */)
    98  			bar = logger.NewBar(logger.BarOptions{
    99  				Enabled: showProgress,
   100  				Total:   20,
   101  				Type:    logger.Expanding,
   102  			})
   103  
   104  			// We've made it this far (removed chunks and updated manifest) now we need to remove appearances
   105  			// from any monitors that may exist which happen after the truncated block. Also, update the monitors'
   106  			// header to reflect this new lastScanned block.
   107  			nMonitorsTruncated := 0
   108  			truncateMonitor := func(path string, info fs.FileInfo, err error) error {
   109  				if err != nil {
   110  					return err
   111  				}
   112  				if !info.IsDir() && strings.HasSuffix(path, ".mon.bin") {
   113  					addr, _ := base.AddressFromPath(path, ".mon.bin")
   114  					bar.Prefix = fmt.Sprintf("Truncating monitor for %s", addr.Hex())
   115  					if !addr.IsZero() {
   116  						mon, _ := monitor.NewMonitor(chain, addr, false /* create */)
   117  						var removed bool
   118  						if removed, err = mon.TruncateTo(chain, uint32(latestChunk)); err != nil {
   119  							return err
   120  						}
   121  						if removed {
   122  							nMonitorsTruncated++
   123  						}
   124  					}
   125  					bar.Tick()
   126  				}
   127  				return nil
   128  			}
   129  			_ = filepath.Walk(filepath.Join(config.PathToCache(chain), "monitors"), truncateMonitor)
   130  			bar.Prefix = fmt.Sprintf("Truncated monitors to %d                                        ", opts.Truncate)
   131  			bar.Finish(true /* newLine */)
   132  
   133  			// All that's left to do is report on what happened.
   134  			fin := "."
   135  			if nChunksRemoved > 0 {
   136  				fin = ", the manifest was updated."
   137  			}
   138  			msg1 := fmt.Sprintf("Truncated index to block %d (the latest full chunk).", latestChunk)
   139  			msg2 := fmt.Sprintf("%d chunks removed, %d monitors truncated%s", nChunksRemoved, nMonitorsTruncated, fin)
   140  			if opts.Globals.Format == "json" {
   141  				s := types.Message{
   142  					Msg: msg1 + " " + msg2,
   143  				}
   144  				modelChan <- &s
   145  			} else {
   146  				logger.Info(msg1)
   147  				logger.Info(msg2)
   148  			}
   149  		}
   150  	}
   151  
   152  	opts.Globals.NoHeader = true
   153  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOpts())
   154  }
   155  
   156  var truncateWarning = `Are sure you want to remove index chunks after and including block {0} (Yn)? `