github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/tslib/list.go (about) 1 // Copyright 2021 The TrueBlocks Authors. All rights reserved. 2 // Use of this source code is governed by a license that can 3 // be found in the LICENSE file. 4 5 package tslib 6 7 import ( 8 "encoding/csv" 9 "errors" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 "strings" 15 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 17 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 18 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 19 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 20 ) 21 22 // GetSpecials returns a chain-specific list of special block names and numbers 23 func GetSpecials(chain string) (specials []types.NamedBlock, err error) { 24 path := filepath.Join(config.MustGetPathToChainConfig(chain), "specials.csv") 25 _, err = os.Stat(path) 26 if err != nil { 27 // It's okay if there are no specials for a certain chain 28 if chain == "mainnet" { 29 // should not happen, but we want to know if it does 30 logger.Info("No special block file found for chain", chain) 31 } 32 return specials, nil 33 } 34 35 if specials, err = readSpecials(path, 6); err != nil { 36 if errors.Is(err, csv.ErrFieldCount) { 37 if specials, err = readSpecials(path, 4); err != nil { 38 return specials, err 39 } 40 } 41 } 42 43 if len(specials) == 0 { 44 err = fmt.Errorf("found no special blocks in file %s", path) 45 } 46 47 return 48 } 49 50 // IsSpecialBlock returns true if the given chain-specific name is a special block 51 func IsSpecialBlock(chain, needle string) bool { 52 _, err := FromNameToBn(chain, needle) 53 return err == nil 54 } 55 56 func readSpecials(path string, nFields int) (specials []types.NamedBlock, err error) { 57 file, err := os.OpenFile(path, os.O_RDONLY, 0) 58 if err != nil { 59 return 60 } 61 62 reader := csv.NewReader(file) 63 reader.FieldsPerRecord = nFields 64 for { 65 if record, err := reader.Read(); err == io.EOF { 66 break 67 68 } else if err != nil { 69 return specials, err 70 71 } else { 72 if nFields == len(record) { 73 locs := map[string]int{ 74 "bn": 1, 75 "name": 2, 76 "ts": 3, 77 "component": 0, 78 // "date": 4, // skipped since we have timestamp 79 "description": 5, 80 } 81 if nFields == 4 { 82 locs = map[string]int{ 83 "bn": 0, 84 "name": 1, 85 "ts": 2, 86 // "date": 3, // skipped since we have timestamp 87 } 88 } 89 if strings.Contains(record[locs["bn"]], "-") { 90 // before block zero 91 continue 92 } 93 s := types.NamedBlock{ 94 BlockNumber: base.MustParseBlknum(record[locs["bn"]]), 95 Timestamp: base.MustParseTimestamp(record[locs["ts"]]), 96 Name: record[locs["name"]], 97 } 98 // is this the header? 99 if s.BlockNumber == 0 && s.Name == "name" { 100 continue 101 } 102 103 if nFields == 6 { 104 s.Component = record[locs["component"]] 105 s.Description = record[locs["description"]] 106 } else { 107 s.Component = "execution" 108 } 109 110 specials = append(specials, s) 111 } 112 } 113 } 114 return 115 }