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)? `