2025-12-18 18:23:09 +01:00
|
|
|
## Logos Storage
|
2021-08-30 13:25:20 -06:00
|
|
|
## Copyright (c) 2021 Status Research & Development GmbH
|
|
|
|
|
## Licensed under either of
|
|
|
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
|
## at your option.
|
|
|
|
|
## This file may not be copied, modified, or distributed except according to
|
|
|
|
|
## those terms.
|
|
|
|
|
|
2022-01-10 09:32:56 -06:00
|
|
|
import std/os
|
2022-12-02 18:00:55 -06:00
|
|
|
import std/tables
|
2025-02-12 23:26:26 +05:30
|
|
|
import std/cpuinfo
|
2025-12-11 22:03:36 +01:00
|
|
|
import std/net
|
2026-04-21 15:36:02 +04:00
|
|
|
import std/sequtils
|
2022-01-10 09:32:56 -06:00
|
|
|
|
|
|
|
|
import pkg/chronos
|
2025-02-12 23:26:26 +05:30
|
|
|
import pkg/taskpools
|
2022-01-10 09:32:56 -06:00
|
|
|
import pkg/presto
|
|
|
|
|
import pkg/libp2p
|
2026-05-25 12:07:30 +04:00
|
|
|
import pkg/libp2p/connmanager
|
2026-06-04 13:22:23 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/autonatv2/[service, client]
|
2026-04-22 12:21:43 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/relay/client as relayClientModule
|
2026-05-12 10:05:58 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/relay/relay as relayModule
|
2026-04-22 12:21:43 +04:00
|
|
|
import pkg/libp2p/services/autorelayservice
|
2022-01-10 09:32:56 -06:00
|
|
|
import pkg/confutils
|
|
|
|
|
import pkg/confutils/defs
|
|
|
|
|
import pkg/stew/io2
|
2022-12-02 18:00:55 -06:00
|
|
|
import pkg/datastore
|
2024-03-12 10:57:13 +01:00
|
|
|
import pkg/stew/io2
|
2022-01-10 09:32:56 -06:00
|
|
|
|
|
|
|
|
import ./node
|
2026-04-25 03:37:42 +03:00
|
|
|
import ./manifest/protocol
|
2022-01-10 09:32:56 -06:00
|
|
|
import ./conf
|
2025-05-29 20:37:38 -03:00
|
|
|
import ./rng as random
|
2022-01-10 09:32:56 -06:00
|
|
|
import ./rest/api
|
2022-03-03 03:30:42 +11:00
|
|
|
import ./stores
|
2022-01-10 09:32:56 -06:00
|
|
|
import ./blockexchange
|
|
|
|
|
import ./utils/fileutils
|
2022-04-13 18:32:35 +02:00
|
|
|
import ./discovery
|
2022-11-01 18:58:41 -06:00
|
|
|
import ./utils/addrutils
|
2022-12-02 18:00:55 -06:00
|
|
|
import ./namespaces
|
2026-02-19 15:59:15 +11:00
|
|
|
import ./storagetypes
|
feat: create logging proxy (#663)
* implement a logging proxy
The logging proxy:
- prevents the need to import chronicles (as well as export except toJson),
- prevents the need to override `writeValue` or use or import nim-json-seralization elsewhere in the codebase, allowing for sole use of utils/json for de/serialization,
- and handles json formatting correctly in chronicles json sinks
* Rename logging -> logutils to avoid ambiguity with common names
* clean up
* add setProperty for JsonRecord, remove nim-json-serialization conflict
* Allow specifying textlines and json format separately
Not specifying a LogFormat will apply the formatting to both textlines and json sinks.
Specifying a LogFormat will apply the formatting to only that sink.
* remove unneeded usages of std/json
We only need to import utils/json instead of std/json
* move serialization from rest/json to utils/json so it can be shared
* fix NoColors ambiguity
Was causing unit tests to fail on Windows.
* Remove nre usage to fix Windows error
Windows was erroring with `could not load: pcre64.dll`. Instead of fixing that error, remove the pcre usage :)
* Add logutils module doc
* Shorten logutils.formatIt for `NBytes`
Both json and textlines formatIt were not needed, and could be combined into one formatIt
* remove debug integration test config
debug output and logformat of json for integration test logs
* Use ## module doc to support docgen
* bump nim-poseidon2 to export fromBytes
Before the changes in this branch, fromBytes was likely being resolved by nim-stew, or other dependency. With the changes in this branch, that dependency was removed and fromBytes could no longer be resolved. By exporting fromBytes from nim-poseidon, the correct resolution is now happening.
* fixes to get compiling after rebasing master
* Add support for Result types being logged using formatIt
2024-01-23 18:35:03 +11:00
|
|
|
import ./logutils
|
2025-01-09 23:41:22 +05:30
|
|
|
import ./nat
|
2026-05-28 09:39:54 +04:00
|
|
|
import ./utils/natutils
|
2026-05-12 10:05:58 +04:00
|
|
|
import ./utils/natsimulation
|
2022-11-01 18:58:41 -06:00
|
|
|
|
|
|
|
|
logScope:
|
2026-02-19 15:59:15 +11:00
|
|
|
topics = "storage node"
|
2022-01-10 09:32:56 -06:00
|
|
|
|
|
|
|
|
type
|
2026-02-19 15:59:15 +11:00
|
|
|
StorageServer* = ref object
|
|
|
|
|
config: StorageConf
|
2026-04-13 12:40:14 +04:00
|
|
|
logFile*: Option[IoHandle]
|
2022-01-10 09:32:56 -06:00
|
|
|
restServer: RestServerRef
|
2026-02-19 15:59:15 +11:00
|
|
|
storageNode: StorageNodeRef
|
2022-12-02 18:00:55 -06:00
|
|
|
repoStore: RepoStore
|
2023-03-08 16:04:54 +01:00
|
|
|
maintenance: BlockMaintainer
|
2025-06-13 01:19:42 +03:00
|
|
|
taskpool: Taskpool
|
2026-05-12 10:05:58 +04:00
|
|
|
# Expose to make reachability accessible from rest api
|
2026-06-04 14:35:46 +04:00
|
|
|
autonatService*: Option[AutonatV2Service]
|
2026-05-12 15:14:01 +04:00
|
|
|
autoRelayService*: Option[AutoRelayService]
|
2026-06-04 14:37:47 +04:00
|
|
|
natMapper*: Option[NatPortMapper]
|
2026-05-25 12:07:30 +04:00
|
|
|
holePunchHandler: Option[connmanager.PeerEventHandler]
|
2026-06-05 16:31:01 +04:00
|
|
|
peerInfoObserver: Option[PeerInfoObserver]
|
2026-06-05 18:09:54 +04:00
|
|
|
bootstrapNodes: seq[SignedPeerRecord]
|
2025-11-13 11:34:09 +04:00
|
|
|
isStarted: bool
|
2022-01-10 09:32:56 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
StoragePrivateKey* = libp2p.PrivateKey # alias
|
2022-11-01 18:58:41 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
func config*(self: StorageServer): StorageConf =
|
2025-11-13 11:34:09 +04:00
|
|
|
return self.config
|
|
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
func node*(self: StorageServer): StorageNodeRef =
|
|
|
|
|
return self.storageNode
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
func repoStore*(self: StorageServer): RepoStore =
|
2025-11-13 11:34:09 +04:00
|
|
|
return self.repoStore
|
|
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
proc start*(s: StorageServer) {.async.} =
|
2025-11-13 11:34:09 +04:00
|
|
|
if s.isStarted:
|
2025-12-18 18:23:09 +01:00
|
|
|
warn "Storage server already started, skipping"
|
2025-11-13 11:34:09 +04:00
|
|
|
return
|
2022-12-02 18:00:55 -06:00
|
|
|
|
2025-12-18 18:23:09 +01:00
|
|
|
trace "Starting Storage node", config = $s.config
|
2022-12-02 18:00:55 -06:00
|
|
|
await s.repoStore.start()
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2023-03-08 16:04:54 +01:00
|
|
|
s.maintenance.start()
|
2022-01-10 09:32:56 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
await s.storageNode.switch.start()
|
2023-09-04 11:12:20 +02:00
|
|
|
|
2026-06-01 18:09:29 +04:00
|
|
|
# When listenPort is 0 the OS assigns a random port. For UDP, the port
|
|
|
|
|
# doesn't change so there is no need to update it.
|
2026-06-04 14:37:47 +04:00
|
|
|
if s.natMapper.isSome and s.config.listenPort == Port(0):
|
|
|
|
|
for listenAddr in s.storageNode.switch.peerInfo.listenAddrs:
|
|
|
|
|
let maybePort = getTcpPort(listenAddr)
|
|
|
|
|
if maybePort.isSome:
|
|
|
|
|
s.natMapper.get.tcpPort = maybePort.get
|
|
|
|
|
break
|
|
|
|
|
|
2026-05-25 17:00:34 +04:00
|
|
|
# The addresses are announced during the start process
|
|
|
|
|
# only with extIp because they should be Reachable.
|
|
|
|
|
# For other nodes, wait for AutoNat to announce addresses and update SPR.
|
|
|
|
|
if s.config.nat.hasExtIp:
|
|
|
|
|
if s.storageNode.switch.peerInfo.addrs.len == 0:
|
|
|
|
|
raise
|
|
|
|
|
newException(StorageError, "extip is set but switch has no listen addresses")
|
|
|
|
|
|
2026-05-28 09:39:54 +04:00
|
|
|
# extip means that we assume the IP is reachable.
|
|
|
|
|
# So we just take the first peer addr and remap it with extip to keep the port only.
|
2026-05-25 17:00:34 +04:00
|
|
|
let announceAddresses = @[
|
|
|
|
|
s.storageNode.switch.peerInfo.addrs[0].remapAddr(
|
|
|
|
|
ip = some(s.config.nat.extIp), port = none(Port)
|
|
|
|
|
)
|
|
|
|
|
]
|
2026-06-05 17:57:01 +04:00
|
|
|
s.storageNode.discovery.announceDirectAddrs(
|
2026-05-25 17:00:34 +04:00
|
|
|
announceAddresses, udpPort = s.config.discoveryPort
|
|
|
|
|
)
|
|
|
|
|
else:
|
2026-05-28 09:39:54 +04:00
|
|
|
# Other nodes wait for AutoNAT to announce addresses and update SPR.
|
|
|
|
|
# They start in client mode to avoid polluting DHT with NotReachable records;
|
|
|
|
|
# it will be flipped off once AutoNAT confirms reachability.
|
2026-05-14 10:56:56 +04:00
|
|
|
s.storageNode.discovery.protocol.clientMode = true
|
|
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
await s.storageNode.start()
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2026-05-25 17:00:34 +04:00
|
|
|
# Connect to the bootstrap nodes in order to have connected peers
|
2026-06-05 18:17:25 +04:00
|
|
|
# for Autonat. The dials are run concurrently in case of
|
|
|
|
|
# a dead bootstrap node that could timeout.
|
|
|
|
|
proc connectBootstrapNode(
|
|
|
|
|
spr: SignedPeerRecord
|
|
|
|
|
) {.async: (raises: [CancelledError]).} =
|
2026-04-15 09:17:45 +04:00
|
|
|
try:
|
|
|
|
|
let addrs = spr.data.addresses.mapIt(it.address)
|
|
|
|
|
await s.storageNode.switch.connect(spr.data.peerId, addrs)
|
2026-06-05 18:13:29 +04:00
|
|
|
except CancelledError as exc:
|
|
|
|
|
raise exc
|
2026-04-15 09:17:45 +04:00
|
|
|
except CatchableError as e:
|
|
|
|
|
warn "Cannot connect to bootstrap node", error = e.msg
|
|
|
|
|
|
2026-06-05 18:17:25 +04:00
|
|
|
await allFutures(findReachableNodes(s.bootstrapNodes).mapIt(connectBootstrapNode(it)))
|
|
|
|
|
|
2026-06-05 18:18:42 +04:00
|
|
|
# AutoNAT is not in switch.services: start it after the bootstrap dials
|
|
|
|
|
# so its first probe has peers to ask.
|
2026-05-28 09:39:54 +04:00
|
|
|
if s.autonatService.isSome:
|
2026-06-04 14:39:56 +04:00
|
|
|
await s.autonatService.get.start(s.storageNode.switch)
|
2026-05-28 09:39:54 +04:00
|
|
|
|
2025-11-13 11:34:09 +04:00
|
|
|
if s.restServer != nil:
|
|
|
|
|
s.restServer.start()
|
|
|
|
|
|
|
|
|
|
s.isStarted = true
|
2023-09-04 11:12:20 +02:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
proc stop*(s: StorageServer) {.async.} =
|
2025-11-13 11:34:09 +04:00
|
|
|
if not s.isStarted:
|
2025-12-18 18:23:09 +01:00
|
|
|
warn "Storage is not started"
|
2025-11-13 11:34:09 +04:00
|
|
|
return
|
|
|
|
|
|
2025-12-18 18:23:09 +01:00
|
|
|
notice "Stopping Storage node"
|
2022-12-02 18:00:55 -06:00
|
|
|
|
2026-05-12 10:05:58 +04:00
|
|
|
if s.natMapper.isSome:
|
2026-06-05 18:06:10 +04:00
|
|
|
s.natMapper.get.stop()
|
2026-06-04 14:35:46 +04:00
|
|
|
|
2026-05-25 12:07:30 +04:00
|
|
|
if s.holePunchHandler.isSome:
|
|
|
|
|
s.storageNode.switch.removePeerEventHandler(
|
|
|
|
|
s.holePunchHandler.get, PeerEventKind.Joined
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-05 16:31:01 +04:00
|
|
|
if s.peerInfoObserver.isSome:
|
|
|
|
|
s.storageNode.switch.peerInfo.removeObserver(s.peerInfoObserver.get)
|
2026-06-01 17:51:03 +04:00
|
|
|
|
2026-02-19 13:12:45 +11:00
|
|
|
var futures = @[
|
2026-02-19 15:59:15 +11:00
|
|
|
s.storageNode.switch.stop(),
|
|
|
|
|
s.storageNode.stop(),
|
2026-02-19 13:12:45 +11:00
|
|
|
s.repoStore.stop(),
|
|
|
|
|
s.maintenance.stop(),
|
|
|
|
|
]
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2026-05-12 10:05:58 +04:00
|
|
|
if s.autoRelayService.isSome and s.autoRelayService.get.isRunning:
|
|
|
|
|
proc stopAutoRelay(): Future[void] {.async: (raises: []).} =
|
2026-06-04 18:51:00 +04:00
|
|
|
await noCancel s.autoRelayService.get.stop(s.storageNode.switch)
|
2026-05-12 10:05:58 +04:00
|
|
|
|
|
|
|
|
futures.add(stopAutoRelay())
|
|
|
|
|
|
2026-06-04 14:39:56 +04:00
|
|
|
if s.autonatService.isSome:
|
|
|
|
|
futures.add(s.autonatService.get.stop(s.storageNode.switch))
|
|
|
|
|
|
2025-11-13 11:34:09 +04:00
|
|
|
if s.restServer != nil:
|
|
|
|
|
futures.add(s.restServer.stop())
|
|
|
|
|
|
|
|
|
|
let res = await noCancel allFinishedFailed[void](futures)
|
2022-01-10 09:32:56 -06:00
|
|
|
|
2026-01-16 17:10:30 +04:00
|
|
|
s.isStarted = false
|
|
|
|
|
|
2025-03-13 08:33:15 -06:00
|
|
|
if res.failure.len > 0:
|
2025-12-18 18:23:09 +01:00
|
|
|
error "Failed to stop Storage node", failures = res.failure.len
|
2026-04-21 15:36:02 +04:00
|
|
|
raise newException(
|
|
|
|
|
StorageError,
|
|
|
|
|
"Failed to stop Storage node: " & res.failure.mapIt(it.error.msg).join(", "),
|
|
|
|
|
)
|
2025-03-13 08:33:15 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
proc close*(s: StorageServer) {.async.} =
|
2026-01-16 17:10:30 +04:00
|
|
|
var futures =
|
2026-02-19 15:59:15 +11:00
|
|
|
@[s.storageNode.close(), s.repoStore.close(), s.storageNode.discovery.close()]
|
2025-11-13 11:34:09 +04:00
|
|
|
|
|
|
|
|
let res = await noCancel allFinishedFailed[void](futures)
|
|
|
|
|
|
2025-06-13 01:19:42 +03:00
|
|
|
if not s.taskpool.isNil:
|
2025-11-13 11:34:09 +04:00
|
|
|
try:
|
|
|
|
|
s.taskpool.shutdown()
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
error "Failed to stop the taskpool", failures = res.failure.len
|
2026-04-21 15:36:02 +04:00
|
|
|
raise newException(StorageError, "Failure in taskpool shutdown: " & exc.msg)
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2026-04-13 12:40:14 +04:00
|
|
|
when defaultChroniclesStream.outputs.type.arity >= 3:
|
|
|
|
|
proc noOutput(logLevel: LogLevel, msg: LogOutputStr) =
|
|
|
|
|
discard
|
|
|
|
|
|
|
|
|
|
defaultChroniclesStream.outputs[2].writer = noOutput
|
|
|
|
|
|
|
|
|
|
if s.logFile.isSome:
|
|
|
|
|
if error =? closeFile(s.logFile.get()).errorOption:
|
|
|
|
|
error "Failed to close log file", errorCode = $error
|
|
|
|
|
|
2025-11-13 11:34:09 +04:00
|
|
|
if res.failure.len > 0:
|
2025-12-18 18:23:09 +01:00
|
|
|
error "Failed to close Storage node", failures = res.failure.len
|
2026-04-21 15:36:02 +04:00
|
|
|
raise newException(
|
|
|
|
|
StorageError,
|
|
|
|
|
"Failed to close Storage node: " & res.failure.mapIt(it.error.msg).join(", "),
|
|
|
|
|
)
|
2025-11-13 11:34:09 +04:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
proc shutdown*(server: StorageServer) {.async.} =
|
2025-11-13 11:34:09 +04:00
|
|
|
await server.stop()
|
|
|
|
|
await server.close()
|
2025-06-13 01:19:42 +03:00
|
|
|
|
2023-06-21 15:02:05 -07:00
|
|
|
proc new*(
|
2026-04-13 12:40:14 +04:00
|
|
|
T: type StorageServer,
|
|
|
|
|
config: StorageConf,
|
|
|
|
|
privateKey: StoragePrivateKey,
|
|
|
|
|
logFile: Option[IoHandle] = IoHandle.none,
|
2026-02-19 15:59:15 +11:00
|
|
|
): StorageServer =
|
|
|
|
|
## create StorageServer including setting up datastore, repostore, etc
|
2026-02-24 21:03:48 +11:00
|
|
|
|
2026-06-05 18:25:19 +04:00
|
|
|
if err =? config.validateAutonatConfig().errorOption:
|
|
|
|
|
raise newException(StorageError, err.msg)
|
2026-06-04 14:37:47 +04:00
|
|
|
|
|
|
|
|
# Switch
|
|
|
|
|
let listenMultiAddr = getMultiAddrWithIpAndTcpPort(config.listenIp, config.listenPort)
|
2026-04-10 18:28:21 +04:00
|
|
|
|
2026-05-12 10:05:58 +04:00
|
|
|
let relayClient = RelayClient.new()
|
|
|
|
|
let relay: Relay =
|
2026-06-04 14:37:47 +04:00
|
|
|
if config.isRelayServer:
|
2026-05-12 10:05:58 +04:00
|
|
|
Relay.new()
|
|
|
|
|
else:
|
|
|
|
|
relayClient
|
|
|
|
|
|
2026-06-04 14:37:47 +04:00
|
|
|
var switchBuilder = SwitchBuilder
|
2022-01-10 09:32:56 -06:00
|
|
|
.new()
|
2022-04-13 18:32:35 +02:00
|
|
|
.withPrivateKey(privateKey)
|
2026-06-12 08:32:33 -04:00
|
|
|
.withAddresses(@[listenMultiAddr], enableWildcardResolver = true)
|
|
|
|
|
.withIdentifyPusher(false)
|
|
|
|
|
.withRng(random.Rng.instance().libp2pRng)
|
2022-01-10 09:32:56 -06:00
|
|
|
.withNoise()
|
2026-04-25 03:37:42 +03:00
|
|
|
.withYamux()
|
2022-01-10 09:32:56 -06:00
|
|
|
.withMaxConnections(config.maxPeers)
|
|
|
|
|
.withAgentVersion(config.agentString)
|
2022-04-13 18:32:35 +02:00
|
|
|
.withSignedPeerRecord(true)
|
2026-05-12 10:05:58 +04:00
|
|
|
.withCircuitRelay(relay)
|
2026-06-04 14:37:47 +04:00
|
|
|
|
2026-06-04 18:51:00 +04:00
|
|
|
let bootstrapNodes =
|
|
|
|
|
if config.noBootstrapNode:
|
|
|
|
|
# Sanity checks that the user isn't doing anything funny.
|
|
|
|
|
if config.bootstrapNodes.len > 0:
|
|
|
|
|
error "Cannot specify bootstrap nodes when using no-bootstrap flag"
|
|
|
|
|
raise newException(
|
|
|
|
|
ValueError, "Cannot specify bootstrap nodes when using no-bootstrap flag"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
warn "Node has been marked with --no-bootstrap-node and will NOT be bootstrapped"
|
|
|
|
|
seq[SignedPeerRecord](@[])
|
|
|
|
|
elif config.bootstrapNodes.len > 0:
|
|
|
|
|
warn "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
|
|
|
|
|
|
2026-06-04 14:39:56 +04:00
|
|
|
var autonatConfig = none(AutonatV2ServiceConfig)
|
2026-06-04 14:37:47 +04:00
|
|
|
if config.autonatServer:
|
|
|
|
|
info "AutoNAT server enabled"
|
|
|
|
|
switchBuilder = switchBuilder.withAutonatV2Server()
|
|
|
|
|
elif not config.nat.hasExtIp:
|
|
|
|
|
info "AutoNAT client enabled",
|
|
|
|
|
scheduleInterval = config.natScheduleInterval,
|
|
|
|
|
numPeersToAsk = config.natNumPeersToAsk,
|
|
|
|
|
maxQueueSize = config.natMaxQueueSize,
|
|
|
|
|
minConfidence = config.natMinConfidence
|
2026-06-04 14:39:56 +04:00
|
|
|
autonatConfig = some(
|
|
|
|
|
AutonatV2ServiceConfig.new(
|
|
|
|
|
scheduleInterval = Opt.some(config.natScheduleInterval),
|
|
|
|
|
askNewConnectedPeers = false,
|
|
|
|
|
numPeersToAsk = config.natNumPeersToAsk,
|
|
|
|
|
maxQueueSize = config.natMaxQueueSize,
|
|
|
|
|
minConfidence = config.natMinConfidence,
|
2026-06-04 14:37:47 +04:00
|
|
|
)
|
2026-06-04 14:39:56 +04:00
|
|
|
)
|
2026-06-04 18:51:00 +04:00
|
|
|
# At the first AutoNAT probe, the only identify observations available come
|
|
|
|
|
# from the bootstrap nodes, so requiring more observations than there are
|
|
|
|
|
# bootstrap nodes would make the threshold unreachable. The floor of 1
|
|
|
|
|
# covers the case where the bootstrap list is empty.
|
|
|
|
|
let observedAddrMinCount =
|
|
|
|
|
max(1, min(config.natObservedAddrMinCount, bootstrapNodes.len))
|
2026-06-04 14:39:56 +04:00
|
|
|
switchBuilder = switchBuilder.withObservedAddrManager(
|
2026-06-04 18:51:00 +04:00
|
|
|
ObservedAddrManager.new(minCount = observedAddrMinCount)
|
2026-06-04 14:39:56 +04:00
|
|
|
)
|
2026-06-10 10:54:02 +04:00
|
|
|
# libp2p keeps the private address in peerInfo.addrs.
|
|
|
|
|
# Since Autonat V2 uses the observed public address,
|
|
|
|
|
# we can filter the private addresses to keep only the dialable
|
|
|
|
|
# addresses.
|
|
|
|
|
switchBuilder = switchBuilder.withAddressPolicy(dialableAddressPolicy)
|
2026-05-12 10:05:58 +04:00
|
|
|
|
|
|
|
|
var natRouter: Option[NatRouter]
|
|
|
|
|
let switch =
|
2026-06-05 18:53:37 +04:00
|
|
|
when storage_enable_nat_simulation:
|
|
|
|
|
if config.natSimulation.isSome:
|
|
|
|
|
# Provide a NAT simulation useful for testing NAT Traversal
|
|
|
|
|
let filtering = FilteringBehavior.fromString(config.natSimulation.get).valueOr(
|
|
|
|
|
AddressAndPortDependent
|
|
|
|
|
)
|
|
|
|
|
let router = NatRouter.new(filtering)
|
|
|
|
|
natRouter = some(router)
|
|
|
|
|
switchBuilder
|
|
|
|
|
.withNatTransport(router, {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay})
|
|
|
|
|
.build()
|
|
|
|
|
else:
|
|
|
|
|
switchBuilder
|
|
|
|
|
.withTcpTransport({ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay})
|
|
|
|
|
.build()
|
2026-05-12 10:05:58 +04:00
|
|
|
else:
|
2026-06-05 18:53:37 +04:00
|
|
|
if config.natSimulation.isSome:
|
|
|
|
|
raise newException(
|
|
|
|
|
StorageError,
|
|
|
|
|
"--nat-simulation requires a build with -d:storage_enable_nat_simulation=true",
|
|
|
|
|
)
|
2026-05-12 10:05:58 +04:00
|
|
|
switchBuilder
|
|
|
|
|
.withTcpTransport({ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay})
|
|
|
|
|
.build()
|
2022-01-10 09:32:56 -06:00
|
|
|
|
2026-06-03 10:39:07 +04:00
|
|
|
var taskPool: Taskpool
|
2025-02-12 23:26:26 +05:30
|
|
|
|
2026-06-04 14:39:56 +04:00
|
|
|
# AutoNAT's first reachability probe fires immediately on start.
|
|
|
|
|
# Wired via withAutonatV2 it lands in switch.services and runs at switch.start,
|
|
|
|
|
# before bootstrap, on an empty peer set.
|
|
|
|
|
# We build and own it here so we can start it ourselves after bootstrap,
|
|
|
|
|
# with the bootstrap peers connected.
|
2026-06-04 14:37:47 +04:00
|
|
|
let autonatService: Option[AutonatV2Service] =
|
2026-06-04 14:39:56 +04:00
|
|
|
if autonatConfig.isSome:
|
|
|
|
|
let client = AutonatV2Client.new(switch.rng)
|
|
|
|
|
client.setup(switch)
|
|
|
|
|
switch.mount(client)
|
|
|
|
|
let service = AutonatV2Service.new(switch.rng, client, autonatConfig.get)
|
|
|
|
|
service.setup(switch)
|
|
|
|
|
some(service)
|
2026-06-04 14:37:47 +04:00
|
|
|
else:
|
|
|
|
|
none(AutonatV2Service)
|
|
|
|
|
|
|
|
|
|
# Storage infrastructure
|
|
|
|
|
|
2025-02-12 23:26:26 +05:30
|
|
|
try:
|
|
|
|
|
if config.numThreads == ThreadCount(0):
|
2026-01-15 19:13:14 +01:00
|
|
|
taskPool = Taskpool.new(numThreads = min(countProcessors(), 16))
|
2025-02-12 23:26:26 +05:30
|
|
|
else:
|
2026-01-15 19:13:14 +01:00
|
|
|
taskPool = Taskpool.new(numThreads = int(config.numThreads))
|
|
|
|
|
info "Threadpool started", numThreads = taskPool.numThreads
|
2025-02-12 23:26:26 +05:30
|
|
|
except CatchableError as exc:
|
2026-01-15 19:13:14 +01:00
|
|
|
raiseAssert("Failure in taskPool initialization:" & exc.msg)
|
2022-08-04 18:51:05 -05:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
let discoveryDir = config.dataDir / StorageDhtNamespace
|
2022-12-02 18:00:55 -06:00
|
|
|
|
|
|
|
|
if io2.createPath(discoveryDir).isErr:
|
|
|
|
|
trace "Unable to create discovery directory for block store",
|
|
|
|
|
discoveryDir = discoveryDir
|
|
|
|
|
raise (ref Defect)(
|
|
|
|
|
msg: "Unable to create discovery directory for block store: " & discoveryDir
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
let providersPath = config.dataDir / StorageDhtProvidersNamespace
|
2026-01-16 17:10:30 +04:00
|
|
|
let discoveryStoreRes = LevelDbDatastore.new(providersPath)
|
|
|
|
|
if discoveryStoreRes.isErr:
|
|
|
|
|
error "Failed to initialize discovery datastore",
|
|
|
|
|
path = providersPath, err = discoveryStoreRes.error.msg
|
|
|
|
|
|
2022-12-02 18:00:55 -06:00
|
|
|
let
|
2026-01-16 17:10:30 +04:00
|
|
|
discoveryStore =
|
|
|
|
|
Datastore(discoveryStoreRes.expect("Should create discovery datastore!"))
|
2022-10-27 07:44:56 -06:00
|
|
|
|
2022-11-01 18:58:41 -06:00
|
|
|
discovery = Discovery.new(
|
2022-10-27 07:44:56 -06:00
|
|
|
switch.peerInfo.privateKey,
|
2026-06-04 14:36:12 +04:00
|
|
|
announceAddrs = @[],
|
2022-11-01 18:58:41 -06:00
|
|
|
bindPort = config.discoveryPort,
|
2026-05-22 19:20:53 -03:00
|
|
|
bootstrapNodes = bootstrapNodes,
|
2026-06-04 14:36:12 +04:00
|
|
|
discoveryPort = config.discoveryPort,
|
2022-10-27 07:44:56 -06:00
|
|
|
store = discoveryStore,
|
|
|
|
|
)
|
2022-04-13 18:32:35 +02:00
|
|
|
|
2022-01-10 09:32:56 -06:00
|
|
|
network = BlockExcNetwork.new(switch)
|
2022-08-08 16:42:05 -05:00
|
|
|
|
2023-03-14 18:32:15 -04:00
|
|
|
repoData =
|
|
|
|
|
case config.repoKind
|
|
|
|
|
of repoFS:
|
|
|
|
|
Datastore(
|
|
|
|
|
FSDatastore.new($config.dataDir, depth = 5).expect(
|
|
|
|
|
"Should create repo file data store!"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
of repoSQLite:
|
|
|
|
|
Datastore(
|
|
|
|
|
SQLiteDatastore.new($config.dataDir).expect(
|
|
|
|
|
"Should create repo SQLite data store!"
|
|
|
|
|
)
|
2024-05-30 08:57:10 +02:00
|
|
|
)
|
|
|
|
|
of repoLevelDb:
|
|
|
|
|
Datastore(
|
|
|
|
|
LevelDbDatastore.new($config.dataDir).expect(
|
|
|
|
|
"Should create repo LevelDB data store!"
|
|
|
|
|
)
|
2025-01-21 21:54:46 +01:00
|
|
|
)
|
2023-03-14 18:32:15 -04:00
|
|
|
|
2022-12-02 18:00:55 -06:00
|
|
|
repoStore = RepoStore.new(
|
2023-03-14 18:32:15 -04:00
|
|
|
repoDs = repoData,
|
2026-02-19 15:59:15 +11:00
|
|
|
metaDs = LevelDbDatastore.new(config.dataDir / StorageMetaNamespace).expect(
|
2024-05-30 08:57:10 +02:00
|
|
|
"Should create metadata store!"
|
|
|
|
|
),
|
2024-06-21 00:46:06 +02:00
|
|
|
quotaMaxBytes = config.storageQuota,
|
2023-07-06 16:23:27 -07:00
|
|
|
blockTtl = config.blockTtl,
|
|
|
|
|
)
|
2023-03-08 16:04:54 +01:00
|
|
|
|
|
|
|
|
maintenance = BlockMaintainer.new(
|
|
|
|
|
repoStore,
|
2023-07-06 16:23:27 -07:00
|
|
|
interval = config.blockMaintenanceInterval,
|
2023-03-08 16:04:54 +01:00
|
|
|
numberOfBlocksPerInterval = config.blockMaintenanceNumberOfBlocks,
|
|
|
|
|
)
|
2022-08-08 16:42:05 -05:00
|
|
|
|
2026-04-25 03:37:42 +03:00
|
|
|
peerStore = PeerContextStore.new()
|
|
|
|
|
downloadManager = DownloadManager.new(retries = config.blockRetries)
|
2024-08-26 15:18:59 +02:00
|
|
|
advertiser = Advertiser.new(repoStore, discovery)
|
2026-04-25 03:37:42 +03:00
|
|
|
blockDiscovery = DiscoveryEngine.new(repoStore, peerStore, network, discovery)
|
2024-08-26 15:18:59 +02:00
|
|
|
engine = BlockExcEngine.new(
|
2026-04-25 03:37:42 +03:00
|
|
|
repoStore, network, blockDiscovery, advertiser, peerStore, downloadManager
|
2024-08-26 15:18:59 +02:00
|
|
|
)
|
2022-12-02 18:00:55 -06:00
|
|
|
store = NetworkStore.new(engine, repoStore)
|
2026-04-25 03:37:42 +03:00
|
|
|
manifestProto = ManifestProtocol.new(switch, repoStore, discovery)
|
2024-02-19 12:12:10 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
storageNode = StorageNodeRef.new(
|
2024-02-19 12:12:10 -06:00
|
|
|
switch = switch,
|
|
|
|
|
networkStore = store,
|
|
|
|
|
engine = engine,
|
2024-03-23 10:56:35 +01:00
|
|
|
discovery = discovery,
|
2026-04-25 03:37:42 +03:00
|
|
|
manifestProto = manifestProto,
|
2026-01-15 19:13:14 +01:00
|
|
|
taskPool = taskPool,
|
2025-01-10 15:12:37 +01:00
|
|
|
)
|
2025-01-21 21:54:46 +01:00
|
|
|
|
2026-06-04 14:37:47 +04:00
|
|
|
switch.mount(network)
|
|
|
|
|
switch.mount(manifestProto)
|
|
|
|
|
|
|
|
|
|
# NAT services
|
|
|
|
|
var natMapper: Option[NatPortMapper]
|
2026-05-12 10:05:58 +04:00
|
|
|
var autoRelayService: Option[AutoRelayService]
|
2026-05-25 12:07:30 +04:00
|
|
|
var holePunchHandler: Option[connmanager.PeerEventHandler]
|
2026-06-05 16:31:01 +04:00
|
|
|
var peerInfoObserver: Option[PeerInfoObserver]
|
2026-05-12 10:05:58 +04:00
|
|
|
|
|
|
|
|
if autonatService.isSome:
|
|
|
|
|
let relayService = AutoRelayService.new(
|
2026-04-22 12:21:43 +04:00
|
|
|
maxNumRelays = config.natMaxRelays,
|
|
|
|
|
client = relayClient,
|
|
|
|
|
onReservation = proc(addresses: seq[MultiAddress]) {.gcsafe, raises: [].} =
|
2026-06-10 10:54:02 +04:00
|
|
|
# A relay server is required to have a public extip, so its
|
|
|
|
|
# circuit addresses always include a public one. The relay's reservation
|
|
|
|
|
# response can also carry loopback/private addresses:
|
|
|
|
|
# they are never dialable by a remote peer, so drop them.
|
|
|
|
|
let publicAddrs = addresses.filterIt(it.hasPublicRelayTransport())
|
|
|
|
|
info "Relay reservation updated", addresses = publicAddrs
|
2026-05-06 10:12:39 +04:00
|
|
|
# relay addresses are for download traffic only, not DHT routing
|
2026-06-10 10:54:02 +04:00
|
|
|
discovery.announceRelayAddrs(publicAddrs),
|
2026-04-22 12:21:43 +04:00
|
|
|
rng = random.Rng.instance(),
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-04 18:51:00 +04:00
|
|
|
relayService.setup(switch)
|
2026-05-12 10:05:58 +04:00
|
|
|
autoRelayService = some(relayService)
|
|
|
|
|
|
2026-06-04 14:37:47 +04:00
|
|
|
natMapper = some(
|
|
|
|
|
NatPortMapper(
|
|
|
|
|
natConfig: config.nat,
|
|
|
|
|
tcpPort: config.listenPort,
|
|
|
|
|
discoveryPort: config.discoveryPort,
|
|
|
|
|
discoverTimeout: config.natPortMappingDiscoverTimeout,
|
|
|
|
|
mappingTimeout: config.natPortMappingTimeout,
|
|
|
|
|
recheckPeriod: config.natPortMappingRecheckPeriod,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-25 17:00:34 +04:00
|
|
|
# natRouter is some only when using nat simulation
|
2026-06-04 14:37:47 +04:00
|
|
|
if natRouter.isSome:
|
|
|
|
|
natRouter.get.natMapper = natMapper
|
|
|
|
|
|
2026-06-05 17:41:24 +04:00
|
|
|
peerInfoObserver =
|
|
|
|
|
some(setupPeerInfoObserver(switch, autonatService.get, discovery, natMapper.get))
|
2026-06-05 16:31:01 +04:00
|
|
|
|
2026-05-12 10:05:58 +04:00
|
|
|
autonatService.get.setStatusAndConfidenceHandler(
|
|
|
|
|
proc(
|
|
|
|
|
networkReachability: NetworkReachability,
|
|
|
|
|
confidence: Opt[float],
|
|
|
|
|
addrs: Opt[MultiAddress],
|
|
|
|
|
) {.async: (raises: [CancelledError]).} =
|
|
|
|
|
debug "AutoNAT status", reachability = networkReachability, confidence
|
|
|
|
|
await natMapper.get.handleNatStatus(
|
|
|
|
|
networkReachability, addrs, config.discoveryPort, discovery, switch,
|
|
|
|
|
relayService,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-25 12:07:30 +04:00
|
|
|
holePunchHandler = some(setupHolePunching(switch))
|
2026-05-22 22:54:07 +04:00
|
|
|
|
2026-06-04 14:37:47 +04:00
|
|
|
# REST server
|
2025-11-13 11:34:09 +04:00
|
|
|
var restServer: RestServerRef = nil
|
|
|
|
|
|
|
|
|
|
if config.apiBindAddress.isSome:
|
2022-01-10 09:32:56 -06:00
|
|
|
restServer = RestServerRef
|
|
|
|
|
.new(
|
2026-04-10 18:28:21 +04:00
|
|
|
storageNode.initRestApi(
|
2026-05-13 18:04:07 +04:00
|
|
|
config, repoStore, autonatService, autoRelayService, natMapper, natRouter,
|
2026-05-12 10:05:58 +04:00
|
|
|
config.apiCorsAllowedOrigin,
|
2026-04-10 18:28:21 +04:00
|
|
|
),
|
2025-11-13 11:34:09 +04:00
|
|
|
initTAddress(config.apiBindAddress.get(), config.apiPort),
|
2022-01-10 09:32:56 -06:00
|
|
|
bufferSize = (1024 * 64),
|
|
|
|
|
maxRequestBodySize = int.high,
|
|
|
|
|
)
|
2025-02-24 15:01:23 -06:00
|
|
|
.expect("Should create rest server!")
|
2022-01-10 09:32:56 -06:00
|
|
|
|
2026-02-19 15:59:15 +11:00
|
|
|
StorageServer(
|
2022-01-10 09:32:56 -06:00
|
|
|
config: config,
|
2026-02-19 15:59:15 +11:00
|
|
|
storageNode: storageNode,
|
2022-12-02 18:00:55 -06:00
|
|
|
restServer: restServer,
|
2023-03-08 16:04:54 +01:00
|
|
|
repoStore: repoStore,
|
2025-01-10 15:12:37 +01:00
|
|
|
maintenance: maintenance,
|
2026-01-15 19:13:14 +01:00
|
|
|
taskPool: taskPool,
|
2026-04-13 12:40:14 +04:00
|
|
|
logFile: logFile,
|
2026-04-10 18:28:21 +04:00
|
|
|
autonatService: autonatService,
|
2026-04-22 12:21:43 +04:00
|
|
|
autoRelayService: autoRelayService,
|
2026-06-04 14:35:46 +04:00
|
|
|
natMapper: natMapper,
|
2026-05-25 12:07:30 +04:00
|
|
|
holePunchHandler: holePunchHandler,
|
2026-06-05 16:31:01 +04:00
|
|
|
peerInfoObserver: peerInfoObserver,
|
2026-06-05 18:09:54 +04:00
|
|
|
bootstrapNodes: bootstrapNodes,
|
2025-01-10 15:12:37 +01:00
|
|
|
)
|