mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-05-29 23:00:05 +00:00
feat: network presets (#1437)
This commit is contained in:
parent
1218a7edbe
commit
ebb1ae2599
@ -71,6 +71,13 @@ proc readValue*(r: var JsonReader, val: var Duration) =
|
||||
raise newException(SerializationError, "Cannot parse the duration: " & input)
|
||||
val = dur
|
||||
|
||||
proc readValue(r: var JsonReader, val: var NetworkPreset) =
|
||||
let name = r.readValue(string)
|
||||
let res = NetworkPresets.find(name)
|
||||
if res.isNone:
|
||||
raise newException(SerializationError, "Invalid network preset: " & name)
|
||||
val = res.get()
|
||||
|
||||
type NodeLifecycleRequest* = object
|
||||
operation: NodeLifecycleMsgType
|
||||
configJson: cstring
|
||||
|
||||
@ -42,11 +42,13 @@ import ./stores
|
||||
import ./units
|
||||
import ./utils
|
||||
import ./nat
|
||||
import ./presets
|
||||
import ./utils/natutils
|
||||
|
||||
from ./blockexchange/engine/downloadmanager import DefaultBlockRetries
|
||||
|
||||
export units, net, storagetypes, logutils, completeCmdArg, parseCmdArg, NatConfig
|
||||
export
|
||||
units, net, storagetypes, logutils, presets, completeCmdArg, parseCmdArg, NatConfig
|
||||
|
||||
export
|
||||
DefaultQuotaBytes, DefaultBlockTtl, DefaultBlockInterval, DefaultNumBlocksPerInterval,
|
||||
@ -179,11 +181,18 @@ type
|
||||
bootstrapNodes* {.
|
||||
desc:
|
||||
"Specifies one or more bootstrap nodes to use when " &
|
||||
"connecting to the network",
|
||||
"connecting to the network. When specified, overrides " &
|
||||
"the network preset option.",
|
||||
abbr: "b",
|
||||
name: "bootstrap-node"
|
||||
.}: seq[SignedPeerRecord]
|
||||
|
||||
network* {.
|
||||
desc: "The network to connect to. Options are: \n" & NetworkPresetsDescription,
|
||||
name: "network",
|
||||
defaultValue: DefaultNetworkPreset
|
||||
.}: NetworkPreset
|
||||
|
||||
maxPeers* {.
|
||||
desc: "The maximum number of peers to connect to",
|
||||
defaultValue: 160,
|
||||
@ -347,16 +356,6 @@ proc parseCmdArg*(T: type ThreadCount, input: string): T =
|
||||
quit QuitFailure
|
||||
return val.get()
|
||||
|
||||
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 parseCmdArg*(T: type SignedPeerRecord, uri: string): T =
|
||||
let res = SignedPeerRecord.parse(uri)
|
||||
if res.isErr:
|
||||
@ -417,6 +416,13 @@ proc parseCmdArg*(T: type Duration, val: string): T =
|
||||
quit QuitFailure
|
||||
dur
|
||||
|
||||
proc parseCmdArg*(T: type NetworkPreset, p: string): NetworkPreset =
|
||||
let res = NetworkPresets.find(p)
|
||||
if res.isNone:
|
||||
fatal "Invalid network preset.", input = p
|
||||
quit QuitFailure
|
||||
return res.get()
|
||||
|
||||
proc readValue*(r: var TomlReader, val: var SignedPeerRecord) =
|
||||
without uri =? r.readValue(string).catch, err:
|
||||
error "invalid SignedPeerRecord configuration value", error = err.msg
|
||||
@ -480,6 +486,17 @@ proc readValue*(
|
||||
except CatchableError as err:
|
||||
raise newException(SerializationError, err.msg)
|
||||
|
||||
proc readValue*(
|
||||
r: var TomlReader, val: var NetworkPreset
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
let
|
||||
str = r.readValue(string)
|
||||
preset = NetworkPresets.find(str)
|
||||
if preset.isNone:
|
||||
raise newException(SerializationError, "Invalid network preset: " & str)
|
||||
|
||||
val = preset.get()
|
||||
|
||||
# no idea why confutils needs this:
|
||||
proc completeCmdArg*(T: type NBytes, val: string): seq[string] =
|
||||
discard
|
||||
@ -490,6 +507,9 @@ proc completeCmdArg*(T: type Duration, val: string): seq[string] =
|
||||
proc completeCmdArg*(T: type ThreadCount, val: string): seq[string] =
|
||||
discard
|
||||
|
||||
proc completeCmdArg*(T: type NetworkPreset, val: string): seq[string] =
|
||||
NetworkPresets.findByPrefix(val)
|
||||
|
||||
# silly chronicles, colors is a compile-time property
|
||||
proc stripAnsi*(v: string): string =
|
||||
var
|
||||
|
||||
106
storage/presets.nim
Normal file
106
storage/presets.nim
Normal file
@ -0,0 +1,106 @@
|
||||
# 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 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 `$`*[N](presets: array[N, 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())
|
||||
|
||||
const NetworkPresets* = [
|
||||
NetworkPreset.init(
|
||||
"logos.dev",
|
||||
"Logos devnet",
|
||||
@[
|
||||
"spr:CiUIAhIhAwfZDeTtWNlSgRbZlZfvxLI5Bpy0lFEYN7gImS3oHNaSEgIDARpJCicAJQgCEiEDB9kN5O1Y2VKBFtmVl-" &
|
||||
"_EsjkGnLSUURg3uAiZLegc1pIQ__O20AYaCwoJBBiQTsiRAiOCGgsKCQQYkE7IkQIjgipHMEUCIQCIZx-HlVsLXJLhD6SEV" &
|
||||
"x6Zt_1aG9IqMq-Luvz8No_J0wIgc8I9PRtheG4s5tzHjkEJMLcq3Jf09IT_FGkzPcJm8h4",
|
||||
"spr:CiUIAhIhA8d4LjRirtXO1M-JEmbhVA0CQeA7hHNR9BA7DvFsPKTEEgIDARpJCicAJQgCEiEDx3guNGKu1c7Uz4kSZu" &
|
||||
"FUDQJB4DuEc1H0EDsO8Ww8pMQQhPW20AYaCwoJBCIq5juRAiOCGgsKCQQiKuY7kQIjgipGMEQCIHV_8nJ0iedWjlAxUhBm" &
|
||||
"dAbDPLu5g2RmcnmJBD8cbD98AiAp1w9nAJgLlPIr41aMcdkds_eSoh8ImOVKvq6Idx-Ugg",
|
||||
"spr:CiUIAhIhA_MocWwn1_t__FEONMqYluUjc9ZVkcvYRLo6C0GzTkbfEgIDARpJCicAJQgCEiED8yhxbCfX-3_8UQ40yp" &
|
||||
"iW5SNz1lWRy9hEujoLQbNORt8QlfO20AYaCwoJBC_u5W-RAiOCGgsKCQQv7uVvkQIjgipGMEQCIHMpQO31gg4FoKYtDyTT" &
|
||||
"QS8xFz1KEmfqH385EeMUNbhPAiBblCkmOfQBmXj6eryaSiXWsftgohE-SPbKwsASZ1Zs3Q",
|
||||
],
|
||||
),
|
||||
NetworkPreset.init(
|
||||
"codex.dev",
|
||||
"Codex legacy devnet (deprecated)",
|
||||
@[
|
||||
"spr:CiUIAhIhA-VlcoiRm02KyIzrcTP-ljFpzTljfBRRKTIvhMIwqBqWEgIDARpJCicAJQgCEiED5WVyiJGbTYrIjOtxM_6" &
|
||||
"WMWnNOWN8FFEpMi-EwjCoGpYQs8n8wQYaCwoJBHTKubmRAnU6GgsKCQR0yrm5kQJ1OipHMEUCIQDwUNsfReB4ty7JFS" &
|
||||
"5WVQ6n1fcko89qVAOfQEHixa03rgIgan2-uFNDT-r4s9TOkLe9YBkCbsRWYCHGGVJ25rLj0QE",
|
||||
"spr:CiUIAhIhApIj9p6zJDRbw2NoCo-tj98Y760YbppRiEpGIE1yGaMzEgIDARpJCicAJQgCEiECkiP2nrMkNFvDY2gKj62P" &
|
||||
"3xjvrRhumlGISkYgTXIZozMQvcz8wQYaCwoJBAWhF3WRAnVEGgsKCQQFoRd1kQJ1RCpGMEQCIFZB84O_nzPNuViqEGRL" &
|
||||
"1vJTjHBJ-i5ZDgFL5XZxm4HAAiB8rbLHkUdFfWdiOmlencYVn0noSMRHzn4lJYoShuVzlw",
|
||||
"spr:CiUIAhIhApqRgeWRPSXocTS9RFkQmwTZRG-Cdt7UR2N7POoz606ZEgIDARpJCicAJQgCEiECmpGB5ZE9JehxNL1EWRCb" &
|
||||
"BNlEb4J23tRHY3s86jPrTpkQj8_8wQYaCwoJBAXfEfiRAnVOGgsKCQQF3xH4kQJ1TipGMEQCIGWJMsF57N1iIEQgTH7I" &
|
||||
"rVOgEgv0J2P2v3jvQr5Cjy-RAiAy4aiZ8QtyDvCfl_K_w6SyZ9csFGkRNTpirq_M_QNgKw",
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
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* = $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
|
||||
@ -216,6 +216,15 @@ proc new*(
|
||||
error "Failed to initialize discovery datastore",
|
||||
path = providersPath, err = discoveryStoreRes.error.msg
|
||||
|
||||
let bootstrapNodes =
|
||||
if config.bootstrapNodes.len > 0:
|
||||
info "Overriding network preset using custom bootstrap nodes",
|
||||
nodes = config.bootstrapNodes
|
||||
config.bootstrapNodes
|
||||
else:
|
||||
info "Bootstrapping node using a predefined network", network = $config.network
|
||||
config.network.bootstrapNodes
|
||||
|
||||
let
|
||||
discoveryStore =
|
||||
Datastore(discoveryStoreRes.expect("Should create discovery datastore!"))
|
||||
@ -224,7 +233,7 @@ proc new*(
|
||||
switch.peerInfo.privateKey,
|
||||
announceAddrs = @[listenMultiAddr],
|
||||
bindPort = config.discoveryPort,
|
||||
bootstrapNodes = config.bootstrapNodes,
|
||||
bootstrapNodes = bootstrapNodes,
|
||||
store = discoveryStore,
|
||||
)
|
||||
|
||||
|
||||
76
tests/storage/testpresets.nim
Normal file
76
tests/storage/testpresets.nim
Normal file
@ -0,0 +1,76 @@
|
||||
import std/options
|
||||
import std/sequtils
|
||||
|
||||
import pkg/codexdht/discv5/protocol
|
||||
import pkg/toml_serialization
|
||||
import pkg/unittest2
|
||||
|
||||
import pkg/storage/presets
|
||||
import pkg/storage/conf
|
||||
|
||||
const SPRs = [
|
||||
"spr:CiUIAhIhA-VlcoiRm02KyIzrcTP-ljFpzTljfBRRKTIvhMIwqBqWEgIDARpJCicAJQgCEiED5WVyiJGbTYrIjOtxM_6" &
|
||||
"WMWnNOWN8FFEpMi-EwjCoGpYQs8n8wQYaCwoJBHTKubmRAnU6GgsKCQR0yrm5kQJ1OipHMEUCIQDwUNsfReB4ty7JFS" &
|
||||
"5WVQ6n1fcko89qVAOfQEHixa03rgIgan2-uFNDT-r4s9TOkLe9YBkCbsRWYCHGGVJ25rLj0QE",
|
||||
"spr:CiUIAhIhApIj9p6zJDRbw2NoCo-tj98Y760YbppRiEpGIE1yGaMzEgIDARpJCicAJQgCEiECkiP2nrMkNFvDY2gKj62P" &
|
||||
"3xjvrRhumlGISkYgTXIZozMQvcz8wQYaCwoJBAWhF3WRAnVEGgsKCQQFoRd1kQJ1RCpGMEQCIFZB84O_nzPNuViqEGRL" &
|
||||
"1vJTjHBJ-i5ZDgFL5XZxm4HAAiB8rbLHkUdFfWdiOmlencYVn0noSMRHzn4lJYoShuVzlw",
|
||||
]
|
||||
|
||||
# Construct presets as const to make sure that everything we do
|
||||
# on `init` runs properly in VM (e.g. parsing SPRs in VM is a
|
||||
# no-go because of: https://github.com/nim-lang/Nim/issues/23468)
|
||||
const Presets = [
|
||||
NetworkPreset.init("preset1", "a preset", SPRs.toSeq),
|
||||
NetworkPreset.init("preset2", "empty preset", @[]),
|
||||
]
|
||||
|
||||
type TestConfig = object
|
||||
network: NetworkPreset
|
||||
|
||||
suite "Network presets":
|
||||
test "should construct presets correctly":
|
||||
check Presets[0].name == "preset1"
|
||||
check Presets[0].description == "a preset"
|
||||
|
||||
check Presets[0].bootstrapNodes.len == 2
|
||||
check Presets[0].bootstrapNodes[0].toURI() == SPRs[0]
|
||||
check Presets[0].bootstrapNodes[1].toURI() == SPRs[1]
|
||||
|
||||
test "should find existing presets by name":
|
||||
let
|
||||
preset1 = Presets.find("preset1").get()
|
||||
preset2 = Presets.find("preset2").get()
|
||||
|
||||
check preset1.name == "preset1"
|
||||
check preset2.name == "preset2"
|
||||
|
||||
test "should return error when preset is not found":
|
||||
let result = Presets.find("nonexistent")
|
||||
check result.isNone
|
||||
|
||||
test "should return presets matching prefix":
|
||||
let result = Presets.findByPrefix("preset")
|
||||
check result.len == 2
|
||||
check result[0] == "preset1"
|
||||
check result[1] == "preset2"
|
||||
|
||||
let result2 = Presets.findByPrefix("preset1")
|
||||
check result2.len == 1
|
||||
check result2[0] == "preset1"
|
||||
|
||||
test "should deserialize valid preset from TOML":
|
||||
# Sadly, we have no option but reading from the global presets array
|
||||
# here, unless we really want to complicate things.
|
||||
let toml = """
|
||||
network = "logos.dev"
|
||||
"""
|
||||
let config = Toml.decode(toml, TestConfig)
|
||||
check config.network.name == "logos.dev"
|
||||
|
||||
test "should raise SerializationError for invalid preset":
|
||||
let toml = """
|
||||
network = "nonexistent"
|
||||
"""
|
||||
expect SerializationError:
|
||||
discard Toml.decode(toml, TestConfig)
|
||||
Loading…
x
Reference in New Issue
Block a user