diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49841abb19..c721d2c212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ on: - '**/*.md' - 'hive_integration/**' - 'fluffy/**' + - 'nimbus/**' - '.github/workflows/fluffy*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' diff --git a/.github/workflows/kurtosis.yml b/.github/workflows/kurtosis.yml index 8f7974d4c5..4062a23a8f 100644 --- a/.github/workflows/kurtosis.yml +++ b/.github/workflows/kurtosis.yml @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2025 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -20,6 +20,7 @@ on: - '**/*.md' - 'hive_integration/**' - 'fluffy/**' + - 'nimbus/**' - '.github/workflows/fluffy*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' @@ -32,6 +33,7 @@ on: - '**/*.md' - 'hive_integration/**' - 'fluffy/**' + - 'nimbus/**' - '.github/workflows/fluffy*.yml' - 'nimbus_verified_proxy/**' - '.github/workflows/nimbus_verified_proxy.yml' diff --git a/Dockerfile b/Dockerfile index eb7bbf8cbd..fe182bddac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2024 Status Research & Development GmbH +# Copyright (c) 2025 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) @@ -21,7 +21,7 @@ ADD . /root/nimbus-eth1 RUN cd /root/nimbus-eth1 \ && make -j$(nproc) update-from-ci \ - && make -j$(nproc) V=1 LOG_LEVEL=TRACE nimbus + && make -j$(nproc) V=1 LOG_LEVEL=TRACE nimbus_execution_client # --------------------------------- # # Starting new image to reduce size # diff --git a/Makefile b/Makefile index e2208c19ab..c3cfb2d130 100644 --- a/Makefile +++ b/Makefile @@ -213,9 +213,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 $@ @@ -365,13 +362,25 @@ libnimbusevm: | build deps # usual cleaning clean: | clean-common - rm -rf build/{nimbus,nimbus_execution_client,fluffy,libverifproxy,nimbus_verified_proxy,$(TOOLS_CSV),$(FLUFFY_TOOLS_CSV),all_tests,test_kvstore_rocksdb,test_rpc,all_fluffy_tests,all_history_network_custom_chain_tests,test_portal_testnet,utp_test_app,utp_test,*.dSYM} + rm -rf build/{nimbus_client,nimbus_execution_client,fluffy,libverifproxy,nimbus_verified_proxy} + rm -rf build/{$(TOOLS_CSV),$(FLUFFY_TOOLS_CSV)} + rm -rf build/{all_tests_nimbus,all_tests,test_kvstore_rocksdb,test_rpc,all_fluffy_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 +nimbus: | build deps + echo -e $(BUILD_MSG) "build/$@" && \ + $(ENV_SCRIPT) nim c $(NIM_PARAMS) --threads:on -d:chronicles_log_level=TRACE -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/nimbus.nimble b/nimbus.nimble index 3b6a5f0f10..c04a55067b 100644 --- a/nimbus.nimble +++ b/nimbus.nimble @@ -40,6 +40,7 @@ when declared(namedBin): "execution_chain/nimbus_execution_client": "nimbus_execution_client", "fluffy/fluffy": "fluffy", "nimbus_verified_proxy/nimbus_verified_proxy": "nimbus_verified_proxy", + "nimbus/nimbus_client": "nimbus_client", }.toTable() import std/[os, strutils] @@ -137,3 +138,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..8168a917d4 --- /dev/null +++ b/nimbus/common/utils.nim @@ -0,0 +1,57 @@ +# 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, ../conf, chronicles + +## Serialize table string elements +proc serializeTableElement*(offset: var uint, elem: string) = + if offset <= 0: + fatal "memory offset can't be zero" + quit(QuitFailure) + + let optLen = uint(elem.len) #element size + + copyMem(cast[pointer](offset), addr optLen, sizeof(uint)) + offset += uint(sizeof(uint)) + + if optLen > 0: + copyMem(cast[pointer](offset), unsafeAddr elem[0], elem.len) #element data + offset += uint(elem.len) + +## Deserialize table string elements +proc deserializeTableElement*(offset: var uint): string = + var strLen: uint #element size + copyMem(addr strLen, cast[pointer](offset), sizeof(uint)) + offset += uint(sizeof(uint)) + + var strData = "" + if strLen > 0: + strData = newString(strLen) #element + copyMem(addr strData[0], cast[pointer](offset), uint(strLen)) + offset += uint(strLen) + + strData + +## Parse data from a given channel. +## schema: (table size:Uint) | [ (option size:Uint) (option data:byte) (arg size: Uint) (arg data:byte)] +proc parseChannelData*(p: pointer): Result[NimbusConfigTable, string] = + var + readOffset = cast[uint](p) + confTable = NimbusConfigTable() + totalSize: uint = 0 + + copyMem(addr totalSize, cast[pointer](readOffset), sizeof(uint)) + readOffset += uint(sizeof(uint)) + + while readOffset < cast[uint](p) + totalSize: + let opt = deserializeTableElement(readOffset) + let arg = deserializeTableElement(readOffset) + confTable[opt] = arg + + ok confTable diff --git a/nimbus/conf.nim b/nimbus/conf.nim new file mode 100644 index 0000000000..808e02bd04 --- /dev/null +++ b/nimbus/conf.nim @@ -0,0 +1,56 @@ +# 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, + #eth2 + beacon_chain/nimbus_binary_common + +export setupFileLimits + +## 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..a6848ae8c0 --- /dev/null +++ b/nimbus/consensus/consensus_layer.nim @@ -0,0 +1,38 @@ +# 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], chronos, chronicles, ../conf, ../common/utils, results + +logScope: + topics = "Consensus layer" + +## 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 configs = parseChannelData(p).valueOr: + fatal "unable to parse service data", message = error + quit(QuitFailure) + + #signal main thread that data is read + isConfigRead.store(true) + + try: + while true: + info "consensus ..." + sleep(cNimbusServiceTimeoutMs) + 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..22988599be --- /dev/null +++ b/nimbus/execution/execution_layer.nim @@ -0,0 +1,38 @@ +# 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, ../conf, ../common/utils, results + +logScope: + topics = "Execution layer" + +## 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 configs = parseChannelData(p).valueOr: + fatal "unable to parse service data", message = error + quit(QuitFailure) + + #signal main thread that data is read + isConfigRead.store(true) + + try: + while true: + info "execution ..." + sleep(cNimbusServiceTimeoutMs) + except CatchableError as e: + fatal "error", message = e.msg + + 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..53db75f830 --- /dev/null +++ b/nimbus/nimbus.nim @@ -0,0 +1,148 @@ +# 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 + std/[concurrency/atomics, os], + chronicles, + consensus/consensus_layer, + execution/execution_layer, + common/utils, + conf + +# ------------------------------------------------------------------------------ +# Private +# ------------------------------------------------------------------------------ + +## create and configure service +proc startService(nimbus: var Nimbus, service: var NimbusService) = + #channel creation (shared memory) + var serviceChannel = + cast[ptr Channel[pointer]](allocShared0(sizeof(Channel[pointer]))) + + serviceChannel[].open() + + #thread read ack + isConfigRead.store(false) + + #start thread + createThread(service.serviceHandler, service.serviceFunc, serviceChannel) + + 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 + + # Writing to shared memory + var writeOffset = cast[uint](byteArray) + + #write total size of array + copyMem(cast[pointer](writeOffset), addr totalSize, sizeof(uint)) + writeOffset += uint(sizeof(uint)) + + for opt, arg in optionsTable: + serializeTableElement(writeOffset, opt) + serializeTableElement(writeOffset, arg) + + serviceChannel[].send(byteArray) + + #wait for service read ack + while not isConfigRead.load(): + sleep(cThreadTimeAck) + isConfigRead.store(true) + + #close channel + serviceChannel[].close() + + #dealloc shared data + deallocShared(byteArray) + deallocShared(serviceChannel) + +## Gracefully exits all services +proc monitorServices(nimbus: Nimbus) = + for service in nimbus.serviceList: + if service.serviceHandler.running(): + joinThread(service.serviceHandler) + info "Exited service ", service = service.name + + notice "Exited all services" + +# ------------------------------------------------------------------------------ +# Public +# ------------------------------------------------------------------------------ + +## start nimbus client +proc run*(nimbus: var Nimbus) = + # to be filled with command line options after parsed according to service + var + consOpt, execOpt = NimbusConfigTable() + + executionService: NimbusService = NimbusService( + name: "Execution Layer", + serviceFunc: executionLayerHandler, + layerConfig: LayerConfig(kind: Execution, executionOptions: execOpt), + ) + + consensusService: NimbusService = NimbusService( + name: "Consensus Layer", + serviceFunc: consensusLayerHandler, + layerConfig: LayerConfig(kind: Consensus, consensusOptions: consOpt), + ) + + nimbus.serviceList.add(executionService) + nimbus.serviceList.add(consensusService) + + try: + for service in nimbus.serviceList.mitems(): + info "Starting service ", service = service.name + nimbus.startService(service) + except Exception as e: + fatal "error", msg = e.msg + quit QuitFailure + + ## wait for shutdown + nimbus.monitorServices() + +# ------ +when isMainModule: + notice "Starting Nimbus" + + setupFileLimits() + + var nimbus: Nimbus = Nimbus.new + + ## Graceful shutdown by handling of Ctrl+C signal + 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 ..." + quit 0 + + setControlCHook(controlCHandler) + 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..2add1b6648 --- /dev/null +++ b/nimbus/tests/test_nimbus.nim @@ -0,0 +1,105 @@ +# 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 expectedConfigTable = {"0": "zero", "1": "one", "2": "two"}.toTable + + let p = channel[].recv() + + let configs = parseChannelData(p).valueOr: + quit(QuitFailure) + + isConfigRead.store(true) + checkResult[] = configs == expectedConfigTable + +#handles data for a given service +proc handlerService_2(channel: ptr Channel[pointer]) = + const expectedConfigTable = {"4": "four", "5": "", "6": "six"}.toTable + let p = channel[].recv() + + let configs = parseChannelData(p).valueOr: + quit(QuitFailure) + + isConfigRead.store(true) + checkResult[] = configs == expectedConfigTable + +# ---------------------------------------------------------------------------- +# # Unit Tests +# ---------------------------------------------------------------------------- + +suite "Nimbus Service Management": + var nimbus: Nimbus + setup: + nimbus = Nimbus.new + + const configTable_1 = {"0": "zero", "1": "one", "2": "two"}.toTable + const configTable_2 = {"4": "four", "5": "", "6": "six"}.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"