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

     1  package chunksPkg
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"time"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    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/output"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/pinning"
    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  func (opts *ChunksOptions) HandlePin(rCtx *output.RenderCtx, blockNums []base.Blknum) error {
    24  	chain := opts.Globals.Chain
    25  	if opts.Globals.TestMode {
    26  		logger.Warn("Pinning option not tested.")
    27  		return nil
    28  	}
    29  
    30  	if !opts.Globals.IsApiMode() && usage.QueryUser(pinWarning, "Check skipped") {
    31  		if err := opts.doCheck(rCtx, blockNums); err != nil {
    32  			rCtx.Cancel()
    33  			return err
    34  		}
    35  	}
    36  
    37  	firstBlock := base.MustParseBlknum(os.Getenv("TB_CHUNKS_PINFIRSTBLOCK"))
    38  	lastBlock := base.MustParseBlknum(os.Getenv("TB_CHUNKS_PINLASTBLOCK"))
    39  	if lastBlock == 0 {
    40  		lastBlock = base.NOPOSN
    41  	}
    42  
    43  	outPath := filepath.Join(config.PathToCache(chain), "tmp", "manifest.json")
    44  	if opts.Rewrite {
    45  		outPath = config.PathToManifest(chain)
    46  	}
    47  
    48  	man, err := manifest.LoadManifest(chain, opts.PublisherAddr, manifest.LocalCache)
    49  	if err != nil {
    50  		rCtx.Cancel()
    51  		return err
    52  	}
    53  
    54  	fetchData := func(modelChan chan types.Modeler, errorChan chan error) {
    55  		hash := base.BytesToHash(config.HeaderHash(config.ExpectedVersion()))
    56  		report := types.ChunkPin{
    57  			Version:  config.VersionTags[hash.Hex()],
    58  			Chain:    chain,
    59  			SpecHash: base.IpfsHash(manifest.Specification()),
    60  		}
    61  
    62  		fileList := make([]string, 0, len(man.Chunks))
    63  		listFiles := func(walker *walk.CacheWalker, path string, first bool) (bool, error) {
    64  			rng, err := base.RangeFromFilenameE(path)
    65  			if err != nil {
    66  				return false, err
    67  			}
    68  			if rng.Last < firstBlock || rng.First > lastBlock {
    69  				logger.Progress(true, "Skipping", path)
    70  				return true, nil
    71  			}
    72  			if path != index.ToBloomPath(path) {
    73  				return false, fmt.Errorf("should not happen in pinChunk")
    74  			}
    75  			if opts.Deep || len(blockNums) > 0 || man.ChunkMap[rng.String()] == nil {
    76  				fileList = append(fileList, path)
    77  			}
    78  			return true, nil
    79  		}
    80  
    81  		walker := walk.NewCacheWalker(
    82  			chain,
    83  			opts.Globals.TestMode,
    84  			100, /* maxTests */
    85  			listFiles,
    86  		)
    87  		if err := walker.WalkBloomFilters(blockNums); err != nil {
    88  			errorChan <- err
    89  			rCtx.Cancel()
    90  			return
    91  		}
    92  
    93  		sort.Slice(fileList, func(i, j int) bool {
    94  			rng1, _ := base.RangeFromFilenameE(fileList[i])
    95  			rng2, _ := base.RangeFromFilenameE(fileList[j])
    96  			return rng1.First < rng2.First
    97  		})
    98  
    99  		failCnt := 1.0
   100  		for i := 0; i < len(fileList); i++ {
   101  			sleep := opts.Sleep
   102  
   103  			path := fileList[i]
   104  			if opts.Globals.Verbose {
   105  				logger.Info("pinning path:", path)
   106  			}
   107  
   108  			local, remote, err := pinning.PinOneChunk(chain, path, opts.Remote)
   109  			if err != nil {
   110  				errorChan <- err
   111  				logger.Error("Pin failed:", path, err)
   112  				failCnt *= 2.
   113  				sleep = failCnt
   114  				i-- // try again after sleeping for a bit
   115  				logger.Info(colors.Yellow, "Sleeping for", sleep, "seconds then trying again.", colors.Off)
   116  
   117  			} else {
   118  				blMatches, idxMatches := matches(&local, &remote)
   119  				opts.matchReport(blMatches, local.BloomHash, remote.BloomHash)
   120  				opts.matchReport(idxMatches, local.IndexHash, remote.IndexHash)
   121  
   122  				if opts.Remote {
   123  					man.Chunks = append(man.Chunks, remote)
   124  				} else {
   125  					man.Chunks = append(man.Chunks, local)
   126  				}
   127  				_ = man.SaveManifest(chain, outPath)
   128  
   129  				if opts.Globals.Verbose {
   130  					if opts.Remote {
   131  						fmt.Println("result.Remote:", remote.String())
   132  					} else {
   133  						fmt.Println("result.Local:", local.String())
   134  					}
   135  				}
   136  			}
   137  
   138  			if sleep > 0 {
   139  				ms := time.Duration(sleep*1000) * time.Millisecond
   140  				if !opts.Globals.TestMode {
   141  					logger.Info("Sleeping for", sleep, "seconds")
   142  				}
   143  				time.Sleep(ms)
   144  			}
   145  		}
   146  
   147  		if len(blockNums) == 0 && firstBlock == 0 && lastBlock == base.NOPOSN {
   148  			tsPath := config.PathToTimestamps(chain)
   149  			if localHash, remoteHash, err := pinning.PinOneFile(chain, "timestamps", tsPath, opts.Remote); err != nil {
   150  				errorChan <- err
   151  				logger.Error("Pin failed:", tsPath, err)
   152  			} else {
   153  				opts.matchReport(localHash == remoteHash, localHash, remoteHash)
   154  				report.TimestampHash = localHash
   155  			}
   156  
   157  			manPath := config.PathToManifest(chain)
   158  			if opts.Deep {
   159  				manPath = outPath
   160  			}
   161  			if localHash, remoteHash, err := pinning.PinOneFile(chain, "manifest", manPath, opts.Remote); err != nil {
   162  				errorChan <- err
   163  				logger.Error("Pin failed:", manPath, err)
   164  			} else {
   165  				opts.matchReport(localHash == remoteHash, localHash, remoteHash)
   166  				report.ManifestHash = localHash
   167  			}
   168  		}
   169  
   170  		logger.Info("The new manifest was written to", colors.BrightGreen+outPath+colors.Off, len(man.Chunks), "chunks")
   171  
   172  		modelChan <- &report
   173  	}
   174  
   175  	return output.StreamMany(rCtx, fetchData, opts.Globals.OutputOpts())
   176  }
   177  
   178  // matches returns true if the Result has both local and remote hashes for both the index and the bloom and they match
   179  func matches(local, remote *types.ChunkRecord) (bool, bool) {
   180  	return local.BloomHash == remote.BloomHash, local.IndexHash == remote.IndexHash
   181  }
   182  
   183  func (opts *ChunksOptions) matchReport(matches bool, localHash, remoteHash base.IpfsHash) {
   184  	_ = remoteHash // linter
   185  	if !opts.Remote || !config.IpfsRunning() {
   186  		return // if we're not pinning in two places, don't report on matches
   187  	}
   188  
   189  	if matches {
   190  		logger.Info(colors.BrightGreen+"Matches: "+localHash.String(), " ", localHash, colors.Off)
   191  	} else {
   192  		logger.Warn("Pins mismatch:", localHash.String(), " ", localHash)
   193  	}
   194  }
   195  
   196  func (opts *ChunksOptions) doCheck(rCtx *output.RenderCtx, blockNums []base.Blknum) error {
   197  	if err, ok := opts.check(rCtx, blockNums, false /* silent */); err != nil {
   198  		return err
   199  	} else if !ok {
   200  		return fmt.Errorf("checks failed")
   201  	}
   202  	return nil
   203  }
   204  
   205  var pinWarning = `Do you want to run --check first (Yn)? `