E M f538083894
feat(monitoring): ping bootstrap nodes daily and open issue if some are dead
Creates a check_spr binary that reads an SPR and reports if that SPR is diallable (for tcp) or pingable (for udp). The check_spr binary takes several args and options:
Usage:
  check_spr [options]
  check_spr <spr-uri> [--timeout <secs>]

Options:
  --source <file>    Config file to read SPRs from (default: network_presets.json).
  --network <name>   Only probe the preset with this network name.
  --timeout <secs>   Per-node probe timeout in seconds (default: 10).
  --format <fmt>     Output format: "text" (default) or "json". "text" is the
                     human-readable table; "json" is a pretty-printed summary.
  --out <file>       Write the output to <file> instead of stdout. The content
                     is whichever --format is selected (text or json).
  --help, -h         Show this help and exit.

Arguments:
  <spr-uri>          A single "spr:" URI to probe instead of reading the config
                     file. Prints ALIVE/DEAD and exits with the matching status;
                     this mode ignores --format and --out.
2026-06-19 20:12:44 +10:00

102 lines
3.7 KiB
Nim

# Presets are hard-coded configuration bundles that get compiled into code. They can refer
# to different things, but the canonical example are sets of bootstrap nodes that define
# logically different networks; e.g., "logos.dev" and "logos.test" refer to the Logos
# devnet and latest testnet, respectively.
import std/options
import std/strutils
import std/json
import pkg/chronicles
import pkg/codexdht/discv5/protocol
import pkg/libp2p/[errors, routing_record]
import pkg/results
import pkg/stew/base64
# A NetworkPreset is a set of bootstrap nodes (represented
# by their signed peer records) along with some description metadata.
type NetworkPreset* = object
name*: string
description*: string
unparsedRecords: seq[string]
proc init*(
T: type NetworkPreset, name: string, description: string, records: seq[string]
): T =
result.name = name
result.description = description
# We have to delay parsing of records to runtime because
# of https://github.com/nim-lang/Nim/issues/23468
result.unparsedRecords = records
func `$`*(preset: NetworkPreset): string =
"[" & preset.name & "]: " & preset.description
func describePresets(presets: openArray[NetworkPreset]): string =
result = ""
for preset in presets:
result &= $preset & "; "
proc parse*(T: type SignedPeerRecord, p: string): Result[SignedPeerRecord, string] =
var res: SignedPeerRecord
try:
if not res.fromURI(p):
return err("The uri is not a valid SignedPeerRecord: " & p)
return ok(res)
except LPError, Base64Error:
let e = getCurrentException()
return err(e.msg)
proc `bootstrapNodes`*(self: NetworkPreset): seq[SignedPeerRecord] =
for record in self.unparsedRecords:
# Having an invalid SPR in a hardcoded config is a bug, a+
# it should crash the node.
result.add(parse(SignedPeerRecord, record).tryGet())
# Bootstrap node SPRs live in a single source-of-truth config file at the repo
# root. staticRead embeds it into the binary at build time, so the node remains
# self-contained (the file is not needed alongside the binary at runtime). The
# same file is read at runtime by the bootstrap liveness checker (tools/check_spr).
const networkPresetsJson = staticRead("../network_presets.json")
proc parsePresetsJson(jsonStr: string): seq[NetworkPreset] =
let root = parseJson(jsonStr)
for p in root["presets"].items:
var records: seq[string]
for r in p["records"].items:
records.add(r.getStr)
result.add(NetworkPreset.init(p["name"].getStr, p["description"].getStr, records))
const NetworkPresets* = parsePresetsJson(networkPresetsJson)
proc loadNetworkPresets*(path: string): seq[NetworkPreset] =
## Runtime loader for the same bootstrap-node config file embedded at compile
## time into `NetworkPresets`. Used by the bootstrap liveness checker so the
## node and the checker share one source of truth.
parsePresetsJson(readFile(path))
proc rawRecords*(self: NetworkPreset): seq[string] =
## The unparsed `spr:` strings for this preset (for tooling that wants to
## handle parse failures per-record instead of crashing).
self.unparsedRecords
proc `default`*(presets: openArray[NetworkPreset]): NetworkPreset =
presets[0]
# Precomputes those as as consts so we can use them in nim-confutils CLI
# help strings.
const
NetworkPresetsDescription* = describePresets(NetworkPresets)
DefaultNetworkPreset* = NetworkPresets.default
proc find*(presets: openArray[NetworkPreset], p: string): Option[NetworkPreset] =
for preset in presets:
if preset.name == p:
return some(preset)
return none(NetworkPreset)
proc findByPrefix*(presets: openArray[NetworkPreset], val: string): seq[string] =
for p in presets:
if p.name.startsWith(val):
result.add p.name