github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/contrib/scripts/check-and-generate-port-registry.go (about) 1 // usr/bin/go run $0 $@ ; exit 2 package main 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "math" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 ) 16 17 const ( 18 inputDir = "configs/coins" 19 outputFile = "docs/ports.md" 20 ) 21 22 // PortInfo contains backend and blockbook ports 23 type PortInfo struct { 24 CoinName string 25 BlockbookInternalPort uint16 26 BlockbookPublicPort uint16 27 BackendRPCPort uint16 28 BackendServicePorts map[string]uint16 29 } 30 31 // PortInfoSlice is self describing 32 type PortInfoSlice []*PortInfo 33 34 // Config contains coin configuration 35 type Config struct { 36 Coin struct { 37 Name string `json:"name"` 38 Label string `json:"label"` 39 } 40 Ports map[string]uint16 `json:"ports"` 41 } 42 43 func checkPorts() int { 44 ports := make(map[uint16][]string) 45 status := 0 46 47 files, err := ioutil.ReadDir(inputDir) 48 if err != nil { 49 panic(err) 50 } 51 52 for _, fi := range files { 53 if fi.IsDir() || fi.Name()[0] == '.' { 54 continue 55 } 56 57 path := filepath.Join(inputDir, fi.Name()) 58 f, err := os.Open(path) 59 if err != nil { 60 panic(fmt.Errorf("%s: %s", path, err)) 61 } 62 defer f.Close() 63 64 v := Config{} 65 d := json.NewDecoder(f) 66 err = d.Decode(&v) 67 if err != nil { 68 panic(fmt.Errorf("%s: json: %s", path, err)) 69 } 70 71 if _, ok := v.Ports["blockbook_internal"]; !ok { 72 fmt.Printf("%s: missing blockbook_internal port\n", v.Coin.Name) 73 status = 1 74 } 75 if _, ok := v.Ports["blockbook_public"]; !ok { 76 fmt.Printf("%s: missing blockbook_public port\n", v.Coin.Name) 77 status = 1 78 } 79 if _, ok := v.Ports["backend_rpc"]; !ok { 80 fmt.Printf("%s: missing backend_rpc port\n", v.Coin.Name) 81 status = 1 82 } 83 84 for _, port := range v.Ports { 85 if port > 0 { 86 ports[port] = append(ports[port], v.Coin.Name) 87 } 88 } 89 } 90 91 for port, coins := range ports { 92 if len(coins) > 1 { 93 fmt.Printf("port %d: registered by %q\n", port, coins) 94 status = 1 95 } 96 } 97 98 if status != 0 { 99 fmt.Println("Got some errors") 100 } 101 return status 102 } 103 104 func main() { 105 output := "stdout" 106 if len(os.Args) > 1 { 107 if len(os.Args) == 2 && os.Args[1] == "-w" { 108 output = outputFile 109 } else { 110 fmt.Fprintf(os.Stderr, "Usage: %s [-w]\n", filepath.Base(os.Args[0])) 111 fmt.Fprintf(os.Stderr, " -w write output to %s instead of stdout\n", outputFile) 112 os.Exit(1) 113 } 114 } 115 116 status := checkPorts() 117 if status != 0 { 118 os.Exit(status) 119 } 120 121 slice, err := loadPortInfo(inputDir) 122 if err != nil { 123 panic(err) 124 } 125 126 sortPortInfo(slice) 127 128 err = writeMarkdown(output, slice) 129 if err != nil { 130 panic(err) 131 } 132 } 133 134 func loadPortInfo(dir string) (PortInfoSlice, error) { 135 files, err := ioutil.ReadDir(dir) 136 if err != nil { 137 return nil, err 138 } 139 140 items := make(PortInfoSlice, 0, len(files)) 141 142 for _, fi := range files { 143 if fi.IsDir() || fi.Name()[0] == '.' { 144 continue 145 } 146 147 path := filepath.Join(dir, fi.Name()) 148 f, err := os.Open(path) 149 if err != nil { 150 return nil, fmt.Errorf("%s: %s", path, err) 151 } 152 defer f.Close() 153 154 v := Config{} 155 d := json.NewDecoder(f) 156 err = d.Decode(&v) 157 if err != nil { 158 return nil, fmt.Errorf("%s: json: %s", path, err) 159 } 160 161 name := v.Coin.Label 162 if len(name) == 0 { 163 name = v.Coin.Name 164 } 165 item := &PortInfo{CoinName: name, BackendServicePorts: map[string]uint16{}} 166 for k, v := range v.Ports { 167 if v == 0 { 168 continue 169 } 170 171 switch k { 172 case "blockbook_internal": 173 item.BlockbookInternalPort = v 174 case "blockbook_public": 175 item.BlockbookPublicPort = v 176 case "backend_rpc": 177 item.BackendRPCPort = v 178 default: 179 if len(k) > 8 && k[:8] == "backend_" { 180 item.BackendServicePorts[k[8:]] = v 181 } 182 } 183 } 184 185 items = append(items, item) 186 } 187 188 return items, nil 189 } 190 191 func sortPortInfo(slice PortInfoSlice) { 192 // normalizes values in order to sort zero values at the bottom of the slice 193 normalize := func(a, b uint16) (uint16, uint16) { 194 if a == 0 { 195 a = math.MaxUint16 196 } 197 if b == 0 { 198 b = math.MaxUint16 199 } 200 return a, b 201 } 202 203 // sort values by BlockbookPublicPort, then by BackendRPCPort and finally by 204 // CoinName; zero values are sorted at the bottom of the slice 205 sort.Slice(slice, func(i, j int) bool { 206 a, b := normalize(slice[i].BlockbookPublicPort, slice[j].BlockbookPublicPort) 207 208 if a < b { 209 return true 210 } 211 if a > b { 212 return false 213 } 214 215 a, b = normalize(slice[i].BackendRPCPort, slice[j].BackendRPCPort) 216 217 if a < b { 218 return true 219 } 220 if a > b { 221 return false 222 } 223 224 return strings.Compare(slice[i].CoinName, slice[j].CoinName) == -1 225 }) 226 } 227 228 func writeMarkdown(output string, slice PortInfoSlice) error { 229 var ( 230 buf bytes.Buffer 231 err error 232 ) 233 234 fmt.Fprintf(&buf, "# Registry of ports\n\n") 235 236 header := []string{"coin", "blockbook internal port", "blockbook public port", "backend rpc port", "backend service ports (zmq)"} 237 writeTable(&buf, header, slice) 238 239 fmt.Fprintf(&buf, "\n> NOTE: This document is generated from coin definitions in `configs/coins`.\n") 240 241 out := os.Stdout 242 if output != "stdout" { 243 out, err = os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 244 if err != nil { 245 return err 246 } 247 defer out.Close() 248 } 249 250 n, err := out.Write(buf.Bytes()) 251 if err != nil { 252 return err 253 } 254 if n < len(buf.Bytes()) { 255 return io.ErrShortWrite 256 } 257 258 return nil 259 } 260 261 func writeTable(w io.Writer, header []string, slice PortInfoSlice) { 262 rows := make([][]string, len(slice)) 263 for i, item := range slice { 264 row := make([]string, len(header)) 265 row[0] = item.CoinName 266 if item.BlockbookInternalPort > 0 { 267 row[1] = fmt.Sprintf("%d", item.BlockbookInternalPort) 268 } 269 if item.BlockbookPublicPort > 0 { 270 row[2] = fmt.Sprintf("%d", item.BlockbookPublicPort) 271 } 272 if item.BackendRPCPort > 0 { 273 row[3] = fmt.Sprintf("%d", item.BackendRPCPort) 274 } 275 276 svcPorts := make([]string, 0, len(item.BackendServicePorts)) 277 for k, v := range item.BackendServicePorts { 278 var s string 279 if k == "message_queue" { 280 s = fmt.Sprintf("%d", v) 281 } else { 282 s = fmt.Sprintf("%d %s", v, k) 283 } 284 svcPorts = append(svcPorts, s) 285 } 286 287 row[4] = strings.Join(svcPorts, ", ") 288 289 rows[i] = row 290 } 291 292 padding := make([]int, len(header)) 293 for column := range header { 294 padding[column] = len(header[column]) 295 296 for _, row := range rows { 297 padding[column] = maxInt(padding[column], len(row[column])) 298 } 299 } 300 301 content := make([][]string, 0, len(rows)+2) 302 303 content = append(content, paddedRow(header, padding)) 304 content = append(content, delim("-", padding)) 305 306 for _, row := range rows { 307 content = append(content, paddedRow(row, padding)) 308 } 309 310 for _, row := range content { 311 fmt.Fprintf(w, "|%s|\n", strings.Join(row, "|")) 312 } 313 } 314 315 func maxInt(a, b int) int { 316 if a > b { 317 return a 318 } 319 return b 320 } 321 322 func paddedRow(row []string, padding []int) []string { 323 out := make([]string, len(row)) 324 for i := 0; i < len(row); i++ { 325 format := fmt.Sprintf(" %%-%ds ", padding[i]) 326 out[i] = fmt.Sprintf(format, row[i]) 327 } 328 return out 329 } 330 331 func delim(str string, padding []int) []string { 332 out := make([]string, len(padding)) 333 for i := 0; i < len(padding); i++ { 334 out[i] = strings.Repeat(str, padding[i]+2) 335 } 336 return out 337 }