diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ea027aaaf..f8555fe02b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ on: - '**/*.yml' - 'hive_integration/**' - 'portal/**' + - 'nimbus/**' - '.github/workflows/portal*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' diff --git a/.github/workflows/kurtosis.yml b/.github/workflows/kurtosis.yml index 6098d46d4f..443121b24a 100644 --- a/.github/workflows/kurtosis.yml +++ b/.github/workflows/kurtosis.yml @@ -20,6 +20,7 @@ on: - '**/*.md' - 'hive_integration/**' - 'portal/**' + - 'nimbus/**' - '.github/workflows/portal*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' @@ -32,6 +33,7 @@ on: - '**/*.md' - 'hive_integration/**' - 'portal/**' + - 'nimbus/**' - '.github/workflows/portal*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' diff --git a/.gitmodules b/.gitmodules index 5478a8f64a..1ea525e0e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -161,7 +161,7 @@ [submodule "vendor/nimbus-eth2"] path = vendor/nimbus-eth2 url = https://github.com/status-im/nimbus-eth2.git - branch = unstable + branch = dev/pedro/pubBN [submodule "vendor/nim-taskpools"] path = vendor/nim-taskpools url = https://github.com/status-im/nim-taskpools.git diff --git a/Makefile b/Makefile index bb7d5efaef..67a1b98e42 100644 --- a/Makefile +++ b/Makefile @@ -215,9 +215,6 @@ nimbus_execution_client: | build deps rocksdb echo -e $(BUILD_MSG) "build/nimbus_execution_client" && \ $(ENV_SCRIPT) nim c $(NIM_PARAMS) -d:chronicles_log_level=TRACE -o:build/nimbus_execution_client "execution_chain/nimbus_execution_client.nim" -nimbus: nimbus_execution_client - echo "The nimbus target is deprecated and will soon change meaning, use 'nimbus_execution_client' instead" - # symlink nimbus.nims: ln -s nimbus.nimble $@ @@ -378,13 +375,26 @@ txparse: | build deps # usual cleaning clean: | clean-common - rm -rf build/{nimbus,nimbus_execution_client,nimbus_portal_client,fluffy,portal_bridge,libverifproxy,nimbus_verified_proxy,$(TOOLS_CSV),$(PORTAL_TOOLS_CSV),all_tests,test_kvstore_rocksdb,test_rpc,all_portal_tests,all_history_network_custom_chain_tests,test_portal_testnet,utp_test_app,utp_test,*.dSYM} + rm -rf build/{nimbus_client,nimbus_execution_client,nimbus_portal_client,fluffy,portal_bridge,libverifproxy,nimbus_verified_proxy} + rm -rf build/{$(TOOLS_CSV),$(PORTAL_TOOLS_CSV)} + rm -rf build/{all_tests_nimbus,all_tests,test_kvstore_rocksdb,test_rpc,all_portal_tests,all_history_network_custom_chain_tests,test_portal_testnet,utp_test_app,utp_test} + rm -rf build/*.dSYM rm -rf tools/t8n/{t8n,t8n_test} rm -rf tools/evmstate/{evmstate,evmstate_test} ifneq ($(USE_LIBBACKTRACE), 0) + $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT) endif +# Nimbus +NIM_PARAMS := -d:release --parallelBuild:1 -d:libp2p_agents_metrics -d:KnownLibP2PAgents=nimbus,lighthouse,lodestar,prysm,teku,grandine $(NIM_PARAMS) +nimbus: | build deps rocksdb + echo -e $(BUILD_MSG) "build/nimbus_client" && \ + $(ENV_SCRIPT) nim c $(NIM_PARAMS) --threads:on -d:disable_libbacktrace -d:libp2p_pki_schemes=secp256k1 -o:build//nimbus_client "nimbus/nimbus.nim" + +all_tests_nimbus: | build deps + echo -e $(BUILD_MSG) "build/$@" && \ + $(ENV_SCRIPT) nim c -r $(NIM_PARAMS) -d:testing --threads:on -d:chronicles_log_level=ERROR -o:build/$@ "nimbus/tests/$@.nim" + # Note about building Nimbus as a library: # # There were `wrappers`, `wrappers-static`, `libnimbus.so` and `libnimbus.a` diff --git a/config.nims b/config.nims index 7a67d1f935..d298393fa8 100644 --- a/config.nims +++ b/config.nims @@ -132,6 +132,7 @@ if not defined(windows): --mm:refc switch("define", "withoutPCRE") +switch("import", "testutils/moduletests") when not defined(disable_libbacktrace): --define:nimStackTraceOverride diff --git a/execution_chain/nimbus_execution_client.nim b/execution_chain/nimbus_execution_client.nim index 0d9acf2c59..81890fa30b 100644 --- a/execution_chain/nimbus_execution_client.nim +++ b/execution_chain/nimbus_execution_client.nim @@ -192,7 +192,7 @@ proc preventLoadingDataDirForTheWrongNetwork(db: CoreDbRef; conf: NimbusConf) = expected=calculatedId quit(QuitFailure) -proc run(nimbus: NimbusNode, conf: NimbusConf) = +proc run*(nimbus: NimbusNode, conf: NimbusConf) = info "Launching execution client", version = FullVersionStr, conf diff --git a/nimbus.nimble b/nimbus.nimble index 8306149a9b..3d90229211 100644 --- a/nimbus.nimble +++ b/nimbus.nimble @@ -40,6 +40,7 @@ when declared(namedBin): "execution_chain/nimbus_execution_client": "nimbus_execution_client", "portal/client/nimbus_portal_client": "nimbus_portal_client", "nimbus_verified_proxy/nimbus_verified_proxy": "nimbus_verified_proxy", + "nimbus/nimbus_client": "nimbus_client", }.toTable() import std/[os, strutils] @@ -135,3 +136,11 @@ task build_fuzzers, "Build fuzzer test cases": for file in walkDirRec("tests/networking/fuzzing/"): if file.endsWith("nim"): exec "nim c -c -d:release " & file + +## Nimbus tasks + +task nimbus, "Build Nimbus": + buildBinary "nimbus", "nimbus/", "-d:chronicles_log_level=TRACE" + +task nimbus_test, "Run Nimbus tests": + test "nimbus/tests/", "all_tests_nimbus", "-d:chronicles_log_level=ERROR -d:testing" diff --git a/nimbus/README.md b/nimbus/README.md new file mode 100644 index 0000000000..7dc638ffc1 --- /dev/null +++ b/nimbus/README.md @@ -0,0 +1,43 @@ +# Nimbus + + +[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +[![Discord: Nimbus](https://img.shields.io/badge/discord-nimbus-orange.svg)](https://discord.gg/XRxWahP) +[![Status: #nimbus-general](https://img.shields.io/badge/status-nimbus--general-orange.svg)](https://join.status.im/nimbus-general) + + +# description +tbd +For in-depth configuration and functionality of Nimbus execution and consensus layer refer to: +- [Nimbus-eth1 - Execution layer client](https://github.com/status-im/nimbus-eth1) Documentation +- [Nimbus-eth2 - Consensus layer client](https://github.com/status-im/nimbus-eth2) Documentation + +tbc +# dependencies +tbd +# how to +## configuration + todo +## commands + todo +## compile +tbd + - mac os, windows, and linux + + ]$ make nimbus +## collaborate +- Use [Status Nim style guide](https://status-im.github.io/nim-style-guide/) to maintain code consistency. +- Format your code using the [Nim Pretty Printer (nph)](https://github.com/arnetheduck/nph) to ensure consistency across the codebase. Run it as part of your pull request process. +## License + +Licensed and distributed under either of + +* MIT license: [LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT + +or + +* Apache License, Version 2.0: [LICENSE-APACHEv2](LICENSE-APACHEv2) or https://www.apache.org/licenses/LICENSE-2.0 + +at your option. These files may not be copied, modified, or distributed except according to those terms. diff --git a/nimbus/common/utils.nim b/nimbus/common/utils.nim new file mode 100644 index 0000000000..46ed636fa7 --- /dev/null +++ b/nimbus/common/utils.nim @@ -0,0 +1,83 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import results, chronicles, stew/shims/macros, confutils + +logScope: + topics = "utils" + +## Macro that collects names and abbreviations per layer from configuration +macro extractFieldNames*(configType: typed): untyped = + var names: seq[string] = newSeq[string]() + let recDef = configType.getImpl() + + for field in recordFields(recDef): + let + name = field.readPragma("name") + abbr = field.readPragma("abbr") + + if name.kind == nnkNilLit: + continue + + names.add($name) + + if abbr.kind != nnkNilLit: + names.add($abbr) + + result = quote: + `names . mapIt ( newLit ( it ))` + +## Write a string into a raw memory buffer (prefixed with length) +proc writeConfigString*(offset: var uint, elem: string) = + if offset <= 0: + fatal "memory offset can't be zero" + quit(QuitFailure) + + let optLen = uint(elem.len) + copyMem(cast[pointer](offset), addr optLen, sizeof(uint)) + offset += uint(sizeof(uint)) + + if optLen > 0: + copyMem(cast[pointer](offset), unsafeAddr elem[0], elem.len) + offset += uint(elem.len) + +## Read a string from a raw memory buffer (expects length prefix) +proc readConfigString*(offset: var uint): string = + var strLen: uint + copyMem(addr strLen, cast[pointer](offset), sizeof(uint)) + offset += uint(sizeof(uint)) + + var strData = "" + if strLen > 0: + strData = newString(strLen) + copyMem(addr strData[0], cast[pointer](offset), uint(strLen)) + offset += uint(strLen) + + strData + +## Parse configuration options from a memory block. +## Format: (table size:uint) | [ (key size:uint)(key:string) (val size:uint)(val:string) ]* +proc deserializeConfigArgs*(p: pointer): Result[seq[string], string] = + var + readOffset = cast[uint](p) + optionsList = newSeq[string]() + totalSize: uint = 0 + + copyMem(addr totalSize, cast[pointer](readOffset), sizeof(uint)) + readOffset += uint(sizeof(uint)) + + while readOffset < cast[uint](p) + totalSize: + let + optName = readConfigString(readOffset) + arg = readConfigString(readOffset) + option = optName & arg + + optionsList.add(option) + + ok optionsList diff --git a/nimbus/conf.nim b/nimbus/conf.nim new file mode 100644 index 0000000000..1a950b72f9 --- /dev/null +++ b/nimbus/conf.nim @@ -0,0 +1,53 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import + std/[atomics, tables], + chronicles + + +## log +logScope: + topics = "Service manager" + +## Exceptions +type NimbusServiceError* = object of CatchableError + +## Constants +const + cNimbusServiceTimeoutMs* = 3000 + cThreadTimeAck* = 10 + +# configuration read by threads +var isConfigRead*: Atomic[bool] +isConfigRead.store(false) + +## Nimbus service arguments +type + NimbusConfigTable* = Table[string, string] + + ConfigKind* = enum + Execution + Consensus + + LayerConfig* = object + case kind*: ConfigKind + of Consensus: + consensusOptions*: NimbusConfigTable + of Execution: + executionOptions*: NimbusConfigTable + + NimbusService* = ref object + name*: string + layerConfig*: LayerConfig + serviceHandler*: Thread[ptr Channel[pointer]] + serviceFunc*: proc(ch: ptr Channel[pointer]) {.thread.} + + Nimbus* = ref object + serviceList*: seq[NimbusService] diff --git a/nimbus/consensus/consensus_layer.nim b/nimbus/consensus/consensus_layer.nim new file mode 100644 index 0000000000..9c1e217586 --- /dev/null +++ b/nimbus/consensus/consensus_layer.nim @@ -0,0 +1,100 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms . + +{.push raises: [].} + +import + std/[atomics, os], + chronicles, + results, + confutils, + ../conf, + ../common/utils, + beacon_chain/validators/keystore_management, + beacon_chain/[beacon_node_status, nimbus_binary_common] + +from beacon_chain/nimbus_beacon_node import handleStartUpCmd +from beacon_chain/conf import + BeaconNodeConf, SlashingDbKind, BNStartUpCmd, defaultDataDir + +logScope: + topics = "Consensus layer" + +## Request to shutdown Consensus layer +proc shutdownConsensus*() = + bnStatus = BeaconNodeStatus.Stopping + +## Creates required beacon node configuration and possibility of additional sources +proc makeConfig(cmdCommandList: seq[string], ConfType: type): Result[ConfType, string] = + {.push warning[ProveInit]: off.} + let config = + try: + ConfType.load( + cmdLine = cmdCommandList, + secondarySources = proc( + config: ConfType, sources: auto + ) {.raises: [ConfigurationError], gcsafe.} = + if config.configFile.isSome: + sources.addConfigFile(Toml, config.configFile.get) + , + ) + except CatchableError as exc: + # We need to log to stderr here, because logging hasn't been configured yet + var msg = "Failure while loading the configuration:\p" & exc.msg & "\p" + if (exc[] of ConfigurationError) and not (isNil(exc.parent)) and + (exc.parent[] of TomlFieldReadingError): + let fieldName = ((ref TomlFieldReadingError)(exc.parent)).field + if fieldName in + [ + "el", "web3-url", "bootstrap-node", "direct-peer", + "validator-monitor-pubkey", + ]: + msg &= + "Since the '" & fieldName & "' option is allowed to " & + "have more than one value, please make sure to supply " & + "a properly formatted TOML array\p" + return err(msg) + {.pop.} + ok(config) + +## starts beacon node +proc startBeaconNode(paramsList: seq[string]) {.raises: [CatchableError].} = + var config = makeConfig(paramsList, BeaconNodeConf).valueOr: + error "Error starting consensus", err = error + quit QuitFailure + + # required for db + if not (checkAndCreateDataDir(string(config.dataDir))): + quit QuitFailure + + setupLogging(config.logLevel, config.logStdout, config.logFile) + + handleStartUpCmd(config) + +## Consensus Layer handler +proc consensusLayerHandler*(channel: ptr Channel[pointer]) = + var p: pointer + try: + p = channel[].recv() + except Exception as e: + fatal " service unable to receive configuration", err = e.msg + quit(QuitFailure) + + let configList = deserializeConfigArgs(p).valueOr: + fatal "unable to parse service data", message = error + quit(QuitFailure) + + #signal main thread that data is read + isConfigRead.store(true) + + try: + {.gcsafe.}: + startBeaconNode(configList) + except CatchableError as e: + fatal "error", message = e.msg + + warn "\tExiting consensus layer" diff --git a/nimbus/execution/execution_layer.nim b/nimbus/execution/execution_layer.nim new file mode 100644 index 0000000000..fae7ccb496 --- /dev/null +++ b/nimbus/execution/execution_layer.nim @@ -0,0 +1,54 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import std/[atomics], chronos, chronicles, results, ../conf, ../common/utils + +from metrics/chronos_httpserver import MetricsError +from ../../execution_chain/nimbus_execution_client import run +from ../../execution_chain/nimbus_desc import NimbusNode, NimbusState +from ../../execution_chain/config import makeConfig +from ../../execution_chain/common import newEthContext + +# Workaround for https://github.com/nim-lang/Nim/issues/24844 +from web3 import Quantity +discard newFuture[Quantity]() + +logScope: + topics = "Execution layer" + +## Request to shutdown execution layer +var nimbusHandler: NimbusNode +proc shutdownExecution*() = + nimbusHandler.state = NimbusState.Stopping + +## Execution Layer handler +proc executionLayerHandler*(channel: ptr Channel[pointer]) = + var p: pointer + try: + p = channel[].recv() + except Exception as e: + fatal "service unable to receive configuration", err = e.msg + quit(QuitFailure) + + let parametersList = deserializeConfigArgs(p).valueOr: + fatal "unable to parse service data", message = error + quit(QuitFailure) + + #signal main thread that data is read + isConfigRead.store(true) + + try: + {.gcsafe.}: + nimbusHandler = NimbusNode(state: NimbusState.Starting, ctx: newEthContext()) + let conf = makeConfig(parametersList) + nimbusHandler.run(conf) + except [CatchableError, OSError, IOError, CancelledError, MetricsError]: + fatal "error", message = getCurrentExceptionMsg() + + warn "\tExiting execution layer" diff --git a/nimbus/nimbus.cfg b/nimbus/nimbus.cfg new file mode 100644 index 0000000000..ab67ecad13 --- /dev/null +++ b/nimbus/nimbus.cfg @@ -0,0 +1,16 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +-d:"libp2p_pki_schemes=secp256k1" + +-d:"chronicles_sinks=textlines[dynamic],json[dynamic]" +-d:"chronicles_runtime_filtering=on" +-d:"chronicles_disable_thread_id" + +@if release: + -d:"chronicles_line_numbers:0" +@end diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim new file mode 100644 index 0000000000..cf020c6fdf --- /dev/null +++ b/nimbus/nimbus.nim @@ -0,0 +1,216 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import + std/[concurrency/atomics, os, exitprocs], + chronicles, + execution/execution_layer, + consensus/consensus_layer, + common/utils, + conf, + confutils/cli_parser, + stew/io2 + +from beacon_chain/conf import BeaconNodeConf +from ../execution_chain/config import NimbusConf +from beacon_chain/nimbus_binary_common import setupFileLimits, shouldCreatePidFile + +# ------------------------------------------------------------------------------ +# Private +# ------------------------------------------------------------------------------ + +#beacon node db lock +var beaconNodeLock {.global.}: string + +proc createBeaconNodeFileLock(filename: string) {.raises: [IOError].} = + shouldCreatePidFile.store(false) + + writeFile filename, $os.getCurrentProcessId() + beaconNodeLock = filename + + addExitProc proc() {.noconv.} = + if beaconNodeLock.len > 0: + discard io2.removeFile(beaconNodeLock) + +## create and configure service +proc startService(nimbus: var Nimbus, service: var NimbusService) = + var serviceChannel = + cast[ptr Channel[pointer]](allocShared0(sizeof(Channel[pointer]))) + + serviceChannel[].open() + + isConfigRead.store(false) + + try: + createThread(service.serviceHandler, service.serviceFunc, serviceChannel) + except Exception as e: + fatal "error creating thread", err = e.msg + + let optionsTable = block: + case service.layerConfig.kind + of Consensus: service.layerConfig.consensusOptions + of Execution: service.layerConfig.executionOptions + + #configs table total size + var totalSize: uint = 0 + totalSize += uint(sizeof(uint)) + for opt, arg in optionsTable: + totalSize += uint(sizeof(uint)) + uint(opt.len) # option + totalSize += uint(sizeof(uint)) + uint(arg.len) # arg + + # Allocate shared memory + # schema: (table size:Uint) | [ (option size:Uint) (option data:byte) (arg size: Uint) (arg data:byte)] + var byteArray = cast[ptr byte](allocShared(totalSize)) + if byteArray.isNil: + fatal "Memory allocation failed" + quit QuitFailure + + var writeOffset = cast[uint](byteArray) + copyMem(cast[pointer](writeOffset), addr totalSize, sizeof(uint)) + writeOffset += uint(sizeof(uint)) + + for opt, arg in optionsTable: + writeConfigString(writeOffset, opt) + writeConfigString(writeOffset, arg) + + try: + serviceChannel[].send(byteArray) + except Exception as e: + fatal "channel error: ", err = e.msg + + #wait for service read ack + while not isConfigRead.load(): + sleep(cThreadTimeAck) + isConfigRead.store(true) + + serviceChannel[].close() + #dealloc shared data + deallocShared(byteArray) + deallocShared(serviceChannel) + +## Gracefully exits all services +proc monitorServices(nimbus: Nimbus) = + for service in nimbus.serviceList: + joinThread(service.serviceHandler) + info "Exited service ", service = service.name + + notice "Exited all services" + +## Auxiliary function to prepare arguments and options for eth1 and eth2 +func addArg( + paramTable: var NimbusConfigTable, cmdKind: CmdLineKind, key: string, arg: string +) = + var + newKey = "" + newArg = "" + + if cmdKind == cmdLongOption: + newKey = "--" & key + + if cmdKind == cmdShortOption: + newKey = "-" & key + + if arg != "": + newArg = "=" & arg + + paramTable[newKey] = newArg + +proc controlCHandler() {.noconv.} = + when defined(windows): + # workaround for https://github.com/nim-lang/Nim/issues/4057 + try: + setupForeignThreadGc() + except NimbusServiceError as exc: + raiseAssert exc.msg # shouldn't happen + + notice "\tCtrl+C pressed. Shutting down services ..." + + shutdownExecution() + shutdownConsensus() + +# ------------------------------------------------------------------------------ +# Public +# ------------------------------------------------------------------------------ + +# Setup nimbus and services +proc setup(nimbus: var Nimbus) {.raises: [CatchableError].} = + let + executionConfigNames = extractFieldNames(NimbusConf) + consensusConfigNames = extractFieldNames(BeaconNodeConf) + + var consensusParams, executionParams = NimbusConfigTable() + + for cmdKind, cmdKey, cmdArg in getopt(commandLineParams()): + var found = false + if cmdKey in consensusConfigNames: + consensusParams.addArg(cmdKind, cmdKey, cmdArg) + found = true + + if cmdKey in executionConfigNames: + executionParams.addArg(cmdKind, cmdKey, cmdArg) + found = true + + if not found: + error "Unrecognized option ", option = cmdKey + #TODO: invoke configurations helpers + quit 0 + + let + consensusService = NimbusService( + name: "Consensus Layer", + serviceFunc: consensusLayerHandler, + layerConfig: LayerConfig(kind: Consensus, consensusOptions: consensusParams), + ) + executionService = NimbusService( + name: "Execution Layer", + serviceFunc: executionLayerHandler, + layerConfig: LayerConfig(kind: Execution, executionOptions: executionParams), + ) + + nimbus.serviceList.add(executionService) + nimbus.serviceList.add(consensusService) + + # todo: replace path with config,datadir when creating Nimbus config + createBeaconNodeFileLock(".beacon_node.pid") + +## start nimbus client +proc run*(nimbus: var Nimbus) = + try: + for service in nimbus.serviceList.mitems(): + info "Starting service ", service = service.name + nimbus.startService(service) + except Exception as e: + fatal "error starting service:", msg = e.msg + quit QuitFailure + + # handling Ctrl+C signal + # note: do not move. Both execution and consensus clients create these handlers. + setControlCHook(controlCHandler) + + # wait for shutdown + nimbus.monitorServices() + +{.pop.} +# ----- + +when isMainModule: + notice "Starting Nimbus" + + setupFileLimits() + + # todo: replace path with config after creating Nimbus config + # setupLogging(config.logLevel, config.logStdout, config.logFile) + + var nimbus = Nimbus() + nimbus.setup() + nimbus.run() + +# ----- +when defined(testing): + export monitorServices, startService diff --git a/nimbus/tests/all_tests_nimbus.nim b/nimbus/tests/all_tests_nimbus.nim new file mode 100644 index 0000000000..42aba2e018 --- /dev/null +++ b/nimbus/tests/all_tests_nimbus.nim @@ -0,0 +1,10 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.warning[UnusedImport]: off.} + +import ./test_nimbus, ./consensus/test_consensus_layer, ./execution/test_execution_layer diff --git a/nimbus/tests/consensus/test_consensus_layer.nim b/nimbus/tests/consensus/test_consensus_layer.nim new file mode 100644 index 0000000000..601088203b --- /dev/null +++ b/nimbus/tests/consensus/test_consensus_layer.nim @@ -0,0 +1,15 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import unittest2 + +suite "Nimbus consensus layer": + #tbd, given that layer is in development + test "tbd": + check true diff --git a/nimbus/tests/execution/test_execution_layer.nim b/nimbus/tests/execution/test_execution_layer.nim new file mode 100644 index 0000000000..2008a838e4 --- /dev/null +++ b/nimbus/tests/execution/test_execution_layer.nim @@ -0,0 +1,16 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [].} + +import + unittest2 + +suite "Nimbus execution layer": + #tbd, given that layer is in development + test "tbd": + check true diff --git a/nimbus/tests/nim.cfg b/nimbus/tests/nim.cfg new file mode 100644 index 0000000000..81cc747b32 --- /dev/null +++ b/nimbus/tests/nim.cfg @@ -0,0 +1,14 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +# Use only `secp256k1` public key cryptography as an identity in LibP2P. +-d:"libp2p_pki_schemes=secp256k1" +-d:"chronicles_runtime_filtering=on" + +--styleCheck:usages +--styleCheck:hint +--hint[Processing]:off diff --git a/nimbus/tests/test_nimbus.nim b/nimbus/tests/test_nimbus.nim new file mode 100644 index 0000000000..b69315e234 --- /dev/null +++ b/nimbus/tests/test_nimbus.nim @@ -0,0 +1,113 @@ +# Nimbus +# Copyright (c) 2025 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import unittest2, std/atomics, ../[nimbus, conf], ../common/utils, tables, results + +# # ---------------------------------------------------------------------------- +# # Helpers +# # ---------------------------------------------------------------------------- + +# checks result computed in thread procedures +var checkResult: ptr bool = createShared(bool) + +# simple mock +proc handlerMock(channel: ptr Channel[pointer]) = + return + +#handles data for a given service +proc handlerService_1(channel: ptr Channel[pointer]) = + const expectedConfigList = + @["-config=a", "--singleconfig", "-abbrev", "-abbrevArg=arg"] + + let p = channel[].recv() + + let configs = deserializeConfigArgs(p).valueOr: + quit(QuitFailure) + + isConfigRead.store(true) + + checkResult[] = configs == expectedConfigList + +#handles data for a given service +proc handlerService_2(channel: ptr Channel[pointer]) = + const expectedConfigList = + @["--singleconfig2", "-config2=a2", "-abbrev2", "-abbrevArg2=arg2"] + + let p = channel[].recv() + + let configs = deserializeConfigArgs(p).valueOr: + quit(QuitFailure) + + isConfigRead.store(true) + + checkResult[] = configs == expectedConfigList + +# ---------------------------------------------------------------------------- +# # Unit Tests +# ---------------------------------------------------------------------------- + +suite "Nimbus Service Management": + var nimbus: Nimbus + setup: + nimbus = Nimbus.new + + const configTable_1 = + {"-config": "=a", "--singleconfig": "", "-abbrev": "", "-abbrevArg": "=arg"}.toTable + const configTable_2 = { + "-config2": "=a2", "--singleconfig2": "", "-abbrev2": "", "-abbrevArg2": "=arg2" + }.toTable + + # Test: Creating a new service successfully + test "startService successfully adds a service": + var someService: NimbusService = NimbusService( + name: "FooBar service", + serviceFunc: handlerMock, + layerConfig: LayerConfig(kind: Consensus, consensusOptions: configTable_1), + ) + nimbus.serviceList.add(someService) + + check nimbus.serviceList.len == 1 + check nimbus.serviceList[0].name == "FooBar service" + + test "nimbus sends correct data for a service": + var someService: NimbusService = NimbusService( + name: "FooBar service", + serviceFunc: handlerService_1, + layerConfig: LayerConfig(kind: Consensus, consensusOptions: configTable_1), + ) + + nimbus.serviceList.add(someService) + + nimbus.startService(someService) + + check nimbus.serviceList.len == 1 + check nimbus.serviceList[0].name == "FooBar service" + check checkResult[] == true + + test "nimbus sends correct data for multiple services": + var someService: NimbusService = NimbusService( + name: "FooBar service", + serviceFunc: handlerService_1, + layerConfig: LayerConfig(kind: Consensus, consensusOptions: configTable_1), + ) + var anotherService: NimbusService = NimbusService( + name: "Xpto service", + serviceFunc: handlerService_2, + layerConfig: LayerConfig(kind: Execution, executionOptions: configTable_2), + ) + nimbus.serviceList.add(someService) + nimbus.serviceList.add(anotherService) + + nimbus.startService(someService) + check checkResult[] == true + + nimbus.startService(anotherService) + check checkResult[] == true + + check nimbus.serviceList.len == 2 + check nimbus.serviceList[0].name == "FooBar service" + check nimbus.serviceList[1].name == "Xpto service" diff --git a/vendor/nimbus-eth2 b/vendor/nimbus-eth2 index 8eb4f78569..24ec4576d3 160000 --- a/vendor/nimbus-eth2 +++ b/vendor/nimbus-eth2 @@ -1 +1 @@ -Subproject commit 8eb4f785690b4a7b7e203a158632e68d048f4ee8 +Subproject commit 24ec4576d3be2847dfca0fab5f0ef2e574659ea3