diff --git a/.ci/.env b/.ci/.env new file mode 100644 index 0000000..d17091b --- /dev/null +++ b/.ci/.env @@ -0,0 +1,34 @@ +LINUX_PACKAGES="make \ + autoconf \ + automake \ + build-essential \ + cargo \ + curl \ + git \ + libtool \ + ninja-build \ + pkg-config \ + python3.12 \ + python3.12-dev \ + python3.12-venv \ + python3-pip \ + unzip \ + zip \ + nano" + +MACOS_PACKAGES="make \ + autoconf \ + automake \ + rust \ + curl \ + git \ + libtool \ + ninja \ + pkg-config \ + python@3.12 \ + unzip \ + zip" + +CMAKE_VERSION=3.31.1 +DEBIAN_FRONTEND=noninteractive +#VCPKG_FORCE_SYSTEM_BINARIES=1 diff --git a/.ci/scripts/detect_os.sh b/.ci/scripts/detect_os.sh new file mode 100755 index 0000000..ac6e1dd --- /dev/null +++ b/.ci/scripts/detect_os.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x + +detect_os() { + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + if command -v apt >/dev/null 2>&1; then + echo "linux_deb" + else + echo "linux_other" + fi + elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "macos" + else + echo "unknown" + fi +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "This script is intended to be sourced or used as a library." + echo "Exported functions: detect_os" + exit 1 +fi diff --git a/.ci/scripts/init.sh b/.ci/scripts/init.sh new file mode 100755 index 0000000..58d7b3f --- /dev/null +++ b/.ci/scripts/init.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x +trap 'echo "=== Error on line $LINENO"; exit 1' ERR + +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +OS_SELECT=$(source ${SCRIPT_DIR}/detect_os.sh && detect_os) + +set -o allexport && . ${SCRIPT_DIR}/../.env && set +o allexport + +main() { + case "$OS_SELECT" in + linux_deb) + echo "=== Detected Linux system with apt" + apt update && apt install -y $LINUX_PACKAGES + if [ -f "$HOME/.cargo/env" ]; then + source "$HOME/.cargo/env" + fi + export PATH="$HOME/.cargo/bin:$PATH" + ;; + linux_other) + echo "=== Detected Linux system without apt" + echo "=== Support for other package managers is not added" + ;; + macos) + echo "=== Detected macOS system" + if command -v brew >/dev/null 2>&1; then + echo "=== Homebrew found. Installing packages..." + brew update && brew install $MACOS_PACKAGES + else + echo "=== Homebrew is not installed. Install it before proceeding: https://brew.sh" + fi + ;; + *) + echo "=== Unknown system" + ;; + esac + + if command -v cargo >/dev/null 2>&1; then + echo "=== Cargo is available: $(cargo --version)" + else + echo "=== Warning: Cargo is not available in PATH" + fi +} + +main + +exit 0 diff --git a/.ci/scripts/init_py.sh b/.ci/scripts/init_py.sh new file mode 100755 index 0000000..7894fbc --- /dev/null +++ b/.ci/scripts/init_py.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x + +init_py() { + echo "$VENV" + python3 -m venv "$VENV" + source $VENV/bin/activate + pip3 install cmake==${CMAKE_VERSION} + pip3 install --no-cache-dir asn1tools +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "This script is intended to be sourced or used as a library." + echo "Exported functions: init_py" + exit 1 +fi diff --git a/.ci/scripts/init_vcpkg.sh b/.ci/scripts/init_vcpkg.sh new file mode 100755 index 0000000..6cbafbe --- /dev/null +++ b/.ci/scripts/init_vcpkg.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x + +init_vcpkg() { + if [[ ! -d $VCPKG || -z "$(ls -A $VCPKG 2>/dev/null)" ]]; then + echo "Directory $VCPKG does not exist or is empty. Cloning vcpkg..." + git clone https://github.com/microsoft/vcpkg.git $VCPKG + fi + + if [[ ! -e $VCPKG/vcpkg ]]; then + echo "vcpkg executable not found. Bootstrapping vcpkg..." + $VCPKG/bootstrap-vcpkg.sh -disableMetrics + fi + + echo "vcpkg is initialized at $VCPKG." +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "This script is intended to be sourced or used as a library." + echo "Exported functions: init_vcpkg" + exit 1 +fi diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7d0b343 --- /dev/null +++ b/.clang-format @@ -0,0 +1,91 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowShortFunctionsOnASingleLine: Empty +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakConstructorInitializers: BeforeColon +BreakConstructorInitializersBeforeComma: false +BreakStringLiterals: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ContinuationIndentWidth: 4 +DerivePointerAlignment: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^["<]MAIN_INCLUDE_FILE[">]$' + Priority: 1 + - Regex: '^(()|())$' + Priority: 2 + - Regex: '^<[^/]*>$' + Priority: 3 + - Regex: '^<.*>$' + Priority: 4 + - Regex: '^".*"$' + Priority: 5 + - Regex: '.*' + Priority: 6 +IndentWidth: 2 +InsertBraces: true +InsertNewlineAtEOF: true +InsertTrailingCommas: Wrapped +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +PointerAlignment: Right +QualifierAlignment: Custom +QualifierOrder: + - inline + - static + - constexpr + - const + - volatile + - type + - restrict +ReferenceAlignment: Right +ReflowComments: true +ShortNamespaceLines: 2 +SortIncludes: CaseSensitive +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +TabWidth: 2 +... diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100755 index 0000000..fdee8ef --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,27 @@ +[//]: # ( +Copyright Quadrivium LLC +All Rights Reserved +SPDX-License-Identifier: Apache-2.0 +) + +# Git Hooks + +This folder _might_ contain some git-hooks to execute various checkup in Kagome. + +To activate presented (_and all future_) hooks just execute **once** shell-script [activate_hooks.sh](./activate_hooks.sh) from Kagome project root path. + +## pre-commit + +This hook check existing `clang-format` and `cmake-format` and their versions. +If they have recommended version, specific checkup is enabled. + +Each changed C++ file (`.hpp` and `.cpp` extensions) gets processed by `clang-format`. + +Each changed CMake file (`CMakeLists.txt` and `.cmake` extension) gets processed by `cmake-format` + +Commit will be blocked while there are any differences between original files and `clang-format/cmake-format` output files. + +## etc. + +_Other hooks might be provided by maintainers in the future._ + diff --git a/.githooks/activate_hooks.sh b/.githooks/activate_hooks.sh new file mode 100755 index 0000000..8a815e6 --- /dev/null +++ b/.githooks/activate_hooks.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +GIT=$(which git) + +if [ -z "${GIT}" ]; then + echo "Command ``git'' command not found" + exit 1 +fi + +cd "$(dirname "$0")/.." || (echo "Run script from file" | exit 1) + +chmod +x .githooks/* + +${GIT} config --local core.hooksPath .githooks || (echo "Hooks activation has failed" | exit 1) + +echo "Hooks activated successfully" +exit 0 diff --git a/.githooks/pre-commit_ b/.githooks/pre-commit_ new file mode 100755 index 0000000..5e70227 --- /dev/null +++ b/.githooks/pre-commit_ @@ -0,0 +1,90 @@ +#!/bin/sh +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +if git rev-parse --verify HEAD >/dev/null 2>&1; then + BASE=HEAD +else + # Initial commit: diff BASE an empty tree object + BASE=$(git hash-object -t tree /dev/null) +fi + +# check clang-format binary +CLANG_FORMAT_ENABLED=1 +CLANG_FORMAT=$(which clang-format-16 2>/dev/null) +if [ -z "${CLANG_FORMAT}" ]; then + CLANG_FORMAT=$(which clang-format) + if [ -z "${CLANG_FORMAT}" ]; then + echo "Command clang-format is not found" >&2 + echo "Please, install clang-format version 16 to enable checkup C++-files formatting over git pre-commit hook" >&2 + CLANG_FORMAT_ENABLED=0 + fi +fi + +# check clang-format version +if [ $CLANG_FORMAT_ENABLED ]; then + CLANG_FORMAT_VERSION=$($CLANG_FORMAT --version | sed -r "s/.*version ([[:digit:]]+).*/\1/") + + if [ "$CLANG_FORMAT_VERSION" != "16" ]; then + echo "clang-format version 16 is recommended" >&2 + fi +fi + +FILES=$(git diff --staged --diff-filter=ACMR --name-only) + +# check c++ files' format with clang-format +CXX_RES=0 +if [ $CLANG_FORMAT_ENABLED ]; then +# for FILE in $(git diff-index --name-only "${BASE}" --diff-filter=ACMR | grep -e "\\.[ch]pp$"); do + for FILE in $(echo "$FILES" | grep -e "\\.[ch]pp$"); do + O_HASH=$(shasum <"${FILE}") + F_HASH=$(${CLANG_FORMAT} --style=file "$FILE" | shasum) + if [ "${O_HASH}" != "${F_HASH}" ]; then + echo "File looks nonformatted: $FILE" + CXX_RES=1 + fi + done + + if [ $CXX_RES = 1 ]; then + CLANG_FORMAT_VERSION_FULL=$($CLANG_FORMAT --version | sed -r "s/.*version ([[:digit:]\.]+).*/\1/") + echo "Used clang-format version $CLANG_FORMAT_VERSION_FULL" >&2 + fi +fi + +## check cmake-format binary +#CMAKE_FORMAT_ENABLED=1 +#CMAKE_FORMAT=$(which cmake-format) +#if [ -z "${CMAKE_FORMAT}" ]; then +# echo "Command cmake-format is not found" >&2 +# echo "Please, install cmake-format version 15 to enable checkup cmake-files formatting over git pre-commit hook" >&2 +# CMAKE_FORMAT_ENABLED=0 +#fi +# +## check cmake-files' format with cmake-format +#CMAKE_RES=0 +#if [ $CMAKE_FORMAT_ENABLED ]; then +# for FILE in $(echo "$FILES" | grep -e "\(\(CMakeLists\\.txt\)\|\(\\.cmake\)\)$"); do +# O_HASH=$(shasum <"${FILE}") +# F_HASH=$(${CMAKE_FORMAT} "$FILE" | shasum) +# if [ "${O_HASH}" != "${F_HASH}" ]; then +# echo "File looks nonformatted: $FILE" +# CMAKE_RES=1 +# fi +# done +# +# if [ $CMAKE_RES = 1 ]; then +# CMAKE_FORMAT_VERSION_FULL=$($CMAKE_FORMAT --version) +# echo "Used cmake-format version $CMAKE_FORMAT_VERSION_FULL" >&2 +# fi +#fi + +# result of checks +if [ "$CXX_RES" = "1" ] || [ "$CMAKE_RES" = "1" ]; then + echo "Formatter required" >&2 + exit 1 +fi + +exit 0 diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 0000000..beac45b --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,110 @@ +name: Run Tests + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + inputs: + use_cache: + description: 'Use cache for build' + required: true + default: 'true' + type: 'choice' + options: + - 'true' + - 'false' + +env: + USE_CACHE: ${{ github.event.inputs.use_cache || 'true' }} + CACHE_VERSION: v02 + CARGO_HOME: ~/.cargo + RUSTUP_HOME: ~/.rustup + CACHE_PATH: | + ~/.cargo + ~/.hunter + ~/.cache/pip + ~/.cache/vcpkg + .vcpkg + .venv + .build + + +jobs: + build-and-test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-24.04, macos-15] + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - name: "Set up Rust" + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt, clippy + + - name: "Restore cache dependencies" + id: cache-restore + if: ${{ env.USE_CACHE == 'true' }} + uses: actions/cache/restore@v4 + with: + path: ${{ env.CACHE_PATH }} + key: jam-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }}-${{ github.run_id }} + restore-keys: | + jam-${{ runner.os }}-${{ github.job }}-${{ env.CACHE_VERSION }} + jam-${{ runner.os }}-${{ github.job }} + jam-${{ runner.os }} + + - name: "Basic init" + run: | + if [[ $RUNNER_OS == "Linux" ]]; then + sudo ./.ci/scripts/init.sh + else + ./.ci/scripts/init.sh + fi + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + # Ensure Rust is available + source "$HOME/.cargo/env" || true + echo "CARGO_HOME=$HOME/.cargo" >> $GITHUB_ENV + echo "RUSTUP_HOME=$HOME/.rustup" >> $GITHUB_ENV + + - name: "Init all dependencies" + run: | + make init_py + make init_vcpkg + + - name: "Check Rust toolchain" + run: | + echo "=== Checking Rust toolchain ===" + which rustc || echo "rustc not found" + which cargo || echo "cargo not found" + rustc --version || echo "rustc version check failed" + cargo --version || echo "cargo version check failed" + echo "PATH: $PATH" + echo "CARGO_HOME: $CARGO_HOME" + echo "RUSTUP_HOME: $RUSTUP_HOME" + + - name: "Configure" + run: make configure + + - name: "Build" + run: make build + + - name: "Test" + run: make test + + - name: "Always Save Cache" + id: cache-save + if: always() && ( steps.cache-restore.outputs.cache-hit != 'true' ) + uses: actions/cache/save@v4 + with: + path: ${{ env.CACHE_PATH }} + key: ${{ steps.cache-restore.outputs.cache-primary-key }} diff --git a/.gitignore b/.gitignore index d4fb281..9b5ac0d 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,16 @@ # debug information files *.dwo + +# IDE auxiliaries +.idea + +/build +/build-ex +/cmake-build-* + +/.build +/.venv +/.vcpkg +/.build* +/vcpkg_installed diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..04bf1e6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test-vectors/jamtestvectors"] + path = test-vectors/jamtestvectors + url = https://github.com/davxy/jam-test-vectors diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e9035fd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,75 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.25) + +option(TESTING "Build and run test suite" ON) +if (TESTING) + list(APPEND VCPKG_MANIFEST_FEATURES test) +endif () + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API ON) + +project(qlean-mini + VERSION 0.0.1 + LANGUAGES CXX C +) + +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "Boost_DIR: ${Boost_DIR}") + +find_package(Python3 REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(libb2 REQUIRED IMPORTED_TARGET GLOBAL libb2) + +find_package(Boost CONFIG REQUIRED COMPONENTS algorithm outcome program_options property_tree) +find_package(fmt CONFIG REQUIRED) +find_package(yaml-cpp CONFIG REQUIRED) +find_package(sszpp CONFIG REQUIRED) +find_package(soralog CONFIG REQUIRED) +find_package(Boost.DI CONFIG REQUIRED) +find_package(qtils CONFIG REQUIRED) +find_package(prometheus-cpp CONFIG REQUIRED) +find_package(OpenSSL CONFIG REQUIRED) +find_package(RocksDB CONFIG REQUIRED) +find_package(hashtree CONFIG REQUIRED) + +# TODO Temporarily commented out until gcc is updated (gcc-13 crashes because of this). +# if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +# add_compile_options(-fmodules-ts) +# elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") +# add_compile_options(-fmodules) +# endif() + +add_library(headers INTERFACE) +target_include_directories(headers INTERFACE + $ + $ + $ + $ +) + +include_directories(${CMAKE_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src) +include_directories(${CMAKE_SOURCE_DIR}/src/third_party) +include_directories(${CMAKE_SOURCE_DIR}/src/_TODO) +include_directories(${CMAKE_BINARY_DIR}/generated) + +add_subdirectory(src) + +if (TESTING) + enable_testing() + + find_package(GTest CONFIG REQUIRED) + set(GTEST_DEPS GTest::gtest_main) + + add_subdirectory(tests) +endif () diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..2f03c14 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,15 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "CMAKE_BUILD_TYPE": "Debug", + "VCPKG_OVERLAY_PORTS": "${sourceDir}/vcpkg-overlay" + } + } + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d795d33 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +SHELL := /bin/bash +PROJECT := $(shell pwd) +CI_DIR := $(PROJECT)/.ci + +VENV ?= $(PROJECT)/.venv +BUILD ?= $(PROJECT)/.build +VCPKG ?= $(PROJECT)/.vcpkg +PATH = $(VENV)/bin:$(shell echo $$PATH) + +ifneq (,$(wildcard $(CI_DIR)/.env)) + include $(CI_DIR)/.env + export +endif + +OS_TYPE := $(shell bash -c 'source $(CI_DIR)/scripts/detect_os.sh && detect_os') + + +all: init_all configure build test + +init_all: init init_py init_vcpkg + +os: + @echo "=== Detected OS: $(OS_TYPE)" + +init: + @echo "=== Initializing..." + $(CI_DIR)/scripts/init.sh + +init_py: + @echo "=== Initializing Python..." + source $(CI_DIR)/scripts/init_py.sh && init_py + +init_vcpkg: + @echo "=== Initializing Vcpkg..." + source $(CI_DIR)/scripts/init_vcpkg.sh && init_vcpkg + +configure: + @echo "=== Configuring..." + export PATH="$$HOME/.cargo/bin:$$PATH" && \ + source $$HOME/.cargo/env 2>/dev/null || true && \ + VCPKG_ROOT=$(VCPKG) cmake --preset=default -DPython3_EXECUTABLE="$(VENV)/bin/python3" -B $(BUILD) $(PROJECT) + +build: + @echo "=== Building..." + cmake --build $(BUILD) + +test: + @echo "=== Testing..." + ctest --test-dir $(BUILD) + +clean_all: + @echo "=== Cleaning..." + rm -rf $(VENV) $(BUILD) $(VCPKG) + +.PHONY: all init_all os init init_py init_vcpkg configure build test clean_all diff --git a/clang-format.py b/clang-format.py new file mode 100644 index 0000000..ff1108e --- /dev/null +++ b/clang-format.py @@ -0,0 +1,187 @@ +import os +import sys +import subprocess +import re + +# Paths relative to the project root +PROJECT_DIR = os.path.abspath(os.path.dirname(__file__)) +SRC_DIR = os.path.join(PROJECT_DIR, "core") +TEST_DIR = os.path.join(PROJECT_DIR, "test") +MOCK_DIR = os.path.join(TEST_DIR, "mock") +SRC_REFLECTION = "core" # Set to "" if no reflection component, or to SRC_DIR if it matches the source directory name +CLANG_FORMAT = "/usr/local/bin/clang-format" # Path to the clang-format executable + + +def log(message): + print(f"[DEBUG] {message}") + + +def find_clang_format(): + log(f"Searching for .clang-format in project directory: {PROJECT_DIR}") + clang_format_path = os.path.join(PROJECT_DIR, '.clang-format') + if os.path.isfile(clang_format_path): + log(f"Found .clang-format at: {clang_format_path}") + return clang_format_path + log("No .clang-format file found.") + return None + + +def find_main_file(file_path): + file_name = os.path.basename(file_path) + file_dir = os.path.dirname(file_path) + name, ext = os.path.splitext(file_name) + + log(f"Finding main file for: {file_path}") + + def search_candidates(file_dir, base_name, suffix): + candidates = [] + + candidates.append(os.path.join(file_dir, base_name + suffix + ".hpp")) + candidates.append(os.path.join(file_dir, base_name + ".hpp")) + + if os.path.basename(file_dir) == "impl": + parent_dir = os.path.dirname(file_dir) + candidates.append(os.path.join(parent_dir, base_name + ".hpp")) + + log(f"search: dir={file_dir} base={base_name} suffix={suffix}") + return candidates + + def find_first_existing(candidates): + # Return the first existing file from the candidate list + log(f"Evaluating candidates: {candidates}") + for candidate in candidates: + if candidate and os.path.isfile(candidate): + log(f"Selected candidate: {candidate}") + return candidate + log("No candidates found.") + return None + + seen = set() + candidates = [] + if ext == ".cpp" or ext == ".hpp": + if name.endswith("_mock") and MOCK_DIR in file_dir: + base_name = name[:-5] # Remove `_mock` + candidates.extend(search_candidates(file_dir, base_name, "_mock")) + src_dir = file_dir.replace(MOCK_DIR + "/" + SRC_REFLECTION, SRC_DIR) + log(f"MOCK_DIR={MOCK_DIR} SRC_REFLECTION={SRC_REFLECTION} +={MOCK_DIR + "/" + SRC_REFLECTION} SRC_DIR={SRC_DIR} src={src_dir}") + candidates.extend(search_candidates(src_dir, base_name, "")) + candidates = [x for x in candidates if not (x in seen or seen.add(x)) and x != file_path] + return find_first_existing(candidates) + + if name.endswith("_test") and TEST_DIR in file_dir: + base_name = name[:-5] # Remove `_test` + candidates.extend(search_candidates(file_dir, base_name, "_test")) + src_dir = file_dir.replace(TEST_DIR + "/" + SRC_REFLECTION, SRC_DIR) + candidates.extend(search_candidates(src_dir, "impl/" + base_name, "_impl")) + src_dir = file_dir.replace(TEST_DIR + "/" + SRC_REFLECTION, SRC_DIR) + candidates.extend(search_candidates(src_dir, "impl/" + base_name, "")) + src_dir = file_dir.replace(TEST_DIR + "/" + SRC_REFLECTION, SRC_DIR) + candidates.extend(search_candidates(src_dir, base_name, "_impl")) + src_dir = file_dir.replace(TEST_DIR + "/" + SRC_REFLECTION, SRC_DIR) + candidates.extend(search_candidates(src_dir, base_name, "")) + candidates = [x for x in candidates if not (x in seen or seen.add(x)) and x != file_path] + return find_first_existing(candidates) + + if name.endswith("_impl") and SRC_DIR in file_dir: + base_name = name[:-5] # Remove `_impl` + candidates.extend(search_candidates(file_dir, base_name, "_impl")) + candidates = [x for x in candidates if not (tuple(x) in seen or seen.add(tuple(x))) and x != file_path] + return find_first_existing(candidates) + + base_name = name + candidates.extend(search_candidates(file_dir, base_name, "")) + candidates = [x for x in candidates if not (x in seen or seen.add(x)) and x != file_path] + return find_first_existing(candidates) + + log("No main file found.") + return None + + +def make_relative_to_base(base_path, target_path): + return os.path.relpath(target_path, base_path) + + +def modify_clang_format(base_clang_format, main_file, output_clang_format): + if TEST_DIR in main_file: + relative_main_file = make_relative_to_base(TEST_DIR, main_file) + elif MOCK_DIR in main_file: + relative_main_file = make_relative_to_base(MOCK_DIR, main_file) + else: + relative_main_file = make_relative_to_base(SRC_DIR, main_file) + + log(f"Using relative path for IncludeIsMainRegex: {relative_main_file}") + + with open(base_clang_format, 'r') as file: + config_lines = file.readlines() + + # include_regex = f"IncludeIsMainRegex: '^{re.escape(relative_main_file)}$'\n" + # updated = False + + for i, line in enumerate(config_lines): + line = line.replace("MAIN_INCLUDE_FILE", f"{relative_main_file}" + # f"{re.escape(relative_main_file)}" + , 1) + config_lines[i] = line + + # if line.startswith(" - Regex: 'MAIN_INCLUDE_FILE'"): + # config_lines[i] = f" - Regex: '^{re.escape(relative_main_file)}$'\n" + # # if line.startswith("IncludeIsMainRegex:"): + # # config_lines[i] = include_regex + # log(f"Line in config: [{config_lines[i]}]") + # # updated = True + # break + + # if not updated: + # config_lines.append(include_regex) + + with open(output_clang_format, 'w') as file: + file.writelines(config_lines) + log(f"Modified .clang-format written to: {output_clang_format}") + + +def format_file(file_path, clang_format_path): + log(f"Formatting file: {file_path} using .clang-format: {clang_format_path}") + subprocess.run([CLANG_FORMAT, "-i", f"--style=file:{clang_format_path}", file_path], + env={"CLANG_FORMAT_STYLE": clang_format_path}, check=True) + + +def main(): + if len(sys.argv) < 2: + print("Usage: python script.py ...") + sys.exit(1) + + arguments = sys.argv + + for i, arg in enumerate(arguments): + if i == 0: continue + file_path = os.path.abspath(arg) + log(f"Processing file: {file_path}") + + if not os.path.isfile(file_path): + print("Error: The specified file does not exist.") + sys.exit(1) + + base_clang_format = find_clang_format() + if not base_clang_format: + print("Error: No .clang-format file found in the project directory.") + sys.exit(1) + + main_file = find_main_file(file_path) + + if main_file: + log(f"Main file found: {main_file}") + temp_clang_format = base_clang_format + ".tmp" + modify_clang_format(base_clang_format, main_file, temp_clang_format) + try: + format_file(file_path, temp_clang_format) + finally: + if os.path.isfile(temp_clang_format): + # os.remove(temp_clang_format) + log(f"Temporary .clang-format file removed: {temp_clang_format}") + else: + log("No main file found. Using base .clang-format.") + format_file(file_path, base_clang_format) + + +if __name__ == "__main__": + main() diff --git a/docker/ci-docker b/docker/ci-docker new file mode 100755 index 0000000..3ba1a4a --- /dev/null +++ b/docker/ci-docker @@ -0,0 +1,71 @@ +#!/usr/bin/env -S bash -e + + + +# TODO(docker-volume): /root/.cache/pip +# TODO(docker-volume): /root/.cache/vcpkg +# TODO(docker-volume): /root/.cargo + + + +# paths +PROJECT=/cpp-jam +VENV=$PROJECT.venv +BUILD=$PROJECT.build +VCPKG=/vcpkg + + + +# DOCKER: FROM ubuntu:24.04 AS base + +# apt install +apt update +DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y \ + autoconf \ + automake \ + build-essential \ + cargo \ + curl \ + git \ + libtool \ + ninja-build \ + pkg-config \ + python3 \ + python3-dev \ + python3-pip \ + python3-venv \ + unzip \ + zip \ + +# create python venv +python3 -m venv $VENV +PATH=$VENV/bin:$PATH + +# install cmake +pip3 install cmake + +# install vcpkg +export VCPKG_FORCE_SYSTEM_BINARIES=1 +if [[ ! -e $VCPKG ]]; then + git clone https://github.com/microsoft/vcpkg.git $VCPKG +fi +if [[ ! -e $VCPKG/vcpkg ]]; then + $VCPKG/bootstrap-vcpkg.sh -disableMetrics +fi + + + +# DOCKER: FROM base AS ci + +# install project requirements +pip3 install asn1tools + +# configure +rm -rf $BUILD +VCPKG_ROOT=$VCPKG cmake --preset=default -B $BUILD $PROJECT + +# build +cmake --build $BUILD + +# run tests +ctest --test-dir $BUILD diff --git a/docker/ci-docker-run b/docker/ci-docker-run new file mode 100644 index 0000000..74c79a0 --- /dev/null +++ b/docker/ci-docker-run @@ -0,0 +1,9 @@ +#!/usr/bin/env -S bash -e + +LOCAL=$PWD + +[[ -d $LOCAL/docker ]] + +docker run --rm -it \ + -v $LOCAL:/cpp-jam \ + ubuntu:24.04 /cpp-jam/docker/ci-docker diff --git a/example/config.yaml b/example/config.yaml new file mode 100644 index 0000000..0fbba9d --- /dev/null +++ b/example/config.yaml @@ -0,0 +1,44 @@ +general: + name: NameFromConfig + base_path: /tmp/lean_eth_node + modules_dir: modules + spec_file: jamduna-spec.json + +database: + directory: db + cache_size: 1G + +metrics: + enabled: true + host: 127.0.0.1 + port: 9615 + +logging: + sinks: + - name: console + type: console + stream: stdout + thread: name + color: true + latency: 0 + groups: + - name: main + sink: console + level: trace + is_fallback: true + children: + - name: jam + children: + - name: modules + children: + - name: example_module + - name: synchronizer_module + - name: networking_module + - name: injector + - name: application + - name: rpc + - name: metrics + - name: threads + - name: storage + children: + - name: block_storage diff --git a/example/jamduna-spec.json b/example/jamduna-spec.json new file mode 100644 index 0000000..cf7c2ab --- /dev/null +++ b/example/jamduna-spec.json @@ -0,0 +1,34 @@ +{ + "bootnodes": [ + "e6srwvvha36ldoqzzcufiq7fevkxlqub6y2ll5wyzgnaeanvrwiha@127.0.0.1:40000", + "e5rxuvijvsfmre4dnolttxyjfa4yncnfcm2r7dkuxsnjfzzm7gdub@127.0.0.1:40001", + "ek5kkz37kij5alih6gmvustxi2vafc6pb3cvnvknj6pum4echyhpb@127.0.0.1:40002", + "e7lyaziunridjg5athudtcg5zclntartcjjgddvc377fjgmido2ma@127.0.0.1:40003", + "eikzzpoh4oycigw3jlhut5qqd3dl7styzucpn237opt55pulnt5yb@127.0.0.1:40004", + "erx3gfjrqky6per3avfs7jl5bgjegimvkpwl4g5g2mnsfffiozkha@127.0.0.1:40005" + ], + "id": "dev", + "genesis_header": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da293d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d022351e22105a19aabb42589162ad7f1ea0df1c25cebf0e4a9fcd261301274862aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bce68e0cf7f26c59f963b5846202d2327cc8bc0c4eff8cb9abd4012f9a71decf007f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33b3e0e096b02e2ec98a3441410aeddd78c95e27a0da6f411a09c631c0f2bea6e948e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e35c7f34a4bd4f2d04076a8c6f9060a0c8d2c6bdd082ceb3eda7df381cb260fafff16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d837ce344bc9defceb0d7de7e9e9925096768b7adb4dad932e532eb6551e0ea020000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "genesis_state": { + "00b000040000000040641f50717f10e73c8fd443128f04e3abe2f4e0a94f2b": "0100000000", + "00b60011000000006dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf0": "0100000000", + "00fe00ff00ff00ff571677cde4d330fd3cd1d6d32ecbab502c92924433297a": "09626f6f74737472617042010008000011000000016e756c6c5f617574686f72697a6572206761732063616c6c20206761735f726573756c743a2000000000010000000000190000000000000019000100000000000d000000000000001e0000000000000018000000000000000800000000000000200000000000000026000000000000002e00000000000000307830303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839394b598638d6c56d345310000027028e390300bc00c7007301e201fa0169028102f3020b039a033c046005320656068c06b606db06e2060c07c207e807ef07a408c7098a0ade0aff0a420b6e0b830bac0bc40b1e0cd30c6c0dcb0dda0d1e0e287e01951178ff7b108000648280883484891052092884882052085281778979859801977a2098aa209a77da9a0764298210800095118800287008817a3308810095177f33035701648b8bac0484aa0f88a80a330930da8309bea90978799577ff95b8ff64ca520ce22830817a3308810095177f33033701648b8bac0484aa0f88a80a330930da8309bea90978799577ff95b8ff64ca520ce295b7fe562780002a9a8c8100641bc87b0b3309780001330801330a0264275010041e098210800095118800320000951178ff7b108000648280883484891052091b84882052084582773308016429821080009511880028b907827a3308810095177f33035701648b98ac0484aa0f88a80a330930da8309bea90978799577ff95b8ff64ca520ce22830827a3308810095177f33033701648b98ac0484aa0f88a80a330930da8309bea90978799577ff95b8ff64ca520ce295b7fe562780002a9a8c8100641bc87b0b3309780001330801330a02642750100867088210800095118800320000951150ff7b10a8007b15a0007b169800491118fffffe4811240a7b17289517247b17783307027b17107b1780009517287b1788003307067b17087b17900033072800017b177b1748491150024911689518787b18584911600295173095184850100ab103821538821b4082163033070233083309645a33000c0a6401821718c8670753370000ff2533071000039577000001018278c98709ab590fc9680901827aab8af27b7901481124010a7b17289517247b17788217107b1780009517287b1788008217087b17900082177b1748491150024911689518787b18584911600295173095184850100e2a03821538821b4082163033070233083309645a3300100a6401821718c8670753370000ff2533071000039577000001018278c98709ab590fc9680901827aab8af27b79013305027a15240a7b17289517247b17783307027b1780009517287b1788003307067b17900033072800017b17487b15504911689517787b17587b1560951730951848501012a002821538821b4082163033070233083309645a3300140a6401821718c8670753370000ff2733071000039577000001018278c98709ab5911c9680901827aab8af27b79330b01330700000133088210a8008215a000821698009511b00032009511b87b10407b15387b1630c88909ac895264758277977801330a08e4a909e498068568ff98883f5107128259087b1918491120017b17282806491120016417951a18646950101632821882170851081414080100000000000080aa870d520718007b57087b56018210408215388216309511483200009511c87b10307b15287b16206495647251087982a70851077c82a9105109aa0082a833061000033303000001c8360601826ac85a07aca7cf00957b0188bc01d8b30bd4cb0b520bbf00826bababe57b67330c7b1218330a100003330b0000017b1ac8ba0ac97a077b17107b18086496501018bb035436000001980082121882181033077b280828b50049120833070128ac0033091000033307000001c8790901829ac85a08aca85f958b0188bc01d8b70bd4cb0b520b50829bababe77b98330c01ababdf283133091000033307000001c8790901829ac85a08aca82b958b0188bc01d8b70bd4cb0b520b1c829bababe77b98330c013307100004c9870833077b280828393307017b27082831646c82179577000001821218821608018278c98709ab6911c9c80901827aab8af27b79330b0182181033077b2808017b25107b2782103082152882162095113832009511b87b10407b15387b163095880151085364758277977901330a08e4a808e489068568ff98883f5107128259087b1918491120017b17282806491120016417951a18646950101a6dfe821882170851081414080100000000000080aa870d520718007b57087b56018210408215388216309511483200009511c87b10307b15287b1620828a086475510a3582893307959b080182bcc8c70795aaff95bb10520af5828a18510a248299088e99887a1085aa01d4a909897ae0a90952090a3307330901284a977701015107405707736452330b1000033309000001c89b0b0182bcc87c0aacca5995a501885601d85905d4650552054a82b6abc6e77bba3309100004c9a90964252806330901017b177b1908491110641750101c290652072282171082180882197b57107b58087b598210308215288216209511383200000000827233090000ff952afffffeac9a288277083309100003959900000101829ac9a90bab7b12c92a0b01829cabacf27b9b3308320032009511e07b10187b15107b16086475827a108277c9a7076496ac972a7b1a825708c8a707646950102241018217c876067b56103307821018821510821608951120320064577b1864a864695010247bfc8218825a1028c79511d87b10207b15187b16106486838833098000647aae982b82a81082a7ab781264a764a55010280ffe645a8258100182a708c8870778769588017ba81028ab008b670b48110c52071c9867068677c00078170c84673f8677800078170d33060228638b671052072998670c8677e00078170c97673498773a9577800078170d84673f8677800078170e330603283797672b98773d9577f00078170c97672e98773a9577800078170d97673498773a9577800078170e84673f8677800078170f3306040182a51082a7c95707ac672d7b1a82a708c8570795180c646950102a34c8560682177b7610013307821020821518821610951128320064a76458646964a550102c6efb645a82551028c42867049511e07b10187b15107b1608531910648d7a84a207c8270b510215648c6475017cca785a95550195cc01acb5f6c82803c929028424f8843807c84b0a51083b58044b973803848038843cf882c58d8884883895cc080182c6d00505cf8609d459097bb995bb0895cc086465acabeb281c647a520921282f58041364380182897bb995bb08958808acabf6c84308842907510914c8a909017c8b78ab95aa01958801ac9af68210188215108216089511203200827701289511a07b10587b15507b16487b19087b18987a04330927532a71028100330927951b463a080000037b1818491110f0d80033037b1433057a00013300ffe0f505016474821718d6740798770b821810c08702c8420a97a230982832ca3808988c119888108488fe0796cc9cc8ca0a97aa3198aa30c858087c8c7c8801c85a0a7ca6017caa78b8ff78bcfe78b60178ba9599fc95bbfcac40ac5417633b97783098883296887b14988811968a9cc8a7079777319877309599fe330a7a0001c8a7077c7a017c77951b21c89b0b78ba0178b764870155170a149599ff951821c89808837730788728219777019599fe33087a0001c887077c78017c77951a21c89a0a78a80178a701951b21c89b0b9a9c2733097800018217088218330a5010321282105882155082164895116032009511987b10607b15587b16507b1b1851084f807634846801330b2b3305000011db8b057b1528c8c8028468045108427b19303308510a1a64a5017d9b89bbc085bb01c8b8089555ff9599015205efc882027b1a3882787b1c20520823284181763495c2014911282d8468045208c44911307b1a3882787b1c20510824827508ae521e8466085206507c7938c9250a5a1901a1005209ae007b1a1028ad0082752082762864576468821928821a30821b385010346d013308015207bc00826c186457821818821920821060821558821650951168320c7b12408178307b18087c79387b19827920827628481730307b1710461738017b194864976468821928821a30821b385010361901520767821740c97505955501019555ff5105c600826920330830821748b49001385107ec28435219021298a90195a8019888017b1810280864a9491110018278207b18488276288177307b1740959501019555ff510527826920821748821840b490013a5107ed330801016487821060821558821650951168320082154864576468821928821a30821b3850103c89003308015207d8826a186457821818821920b4a0013e3308015207c38217109a777b17383305ff01821738c857075117ff42826920821748821840b49001409555015107e62837826a18821748821818821920b4a0014233080152078233088217108219087a79308219787938286fff821510821710d875082863ff821710d87508285aff9511d87b10207b15187b1610839c330200001164a66485647aaa2c20825c2064a764987b1b087b1ab4c00144821a821b08647833070152082051061a825c1864a7646864b9821020821518821610951128320c3307018210208215188216109511283200951178ff7b1080007b15787b1670491160203309037819684911304911408286207b175033074800017b17585106060182872851077301330982857b18088288107b18189578ff9788039888039588017b189555089678389566183307307b1710017b18207b19288259510917821a588217508258f882aa18b4a0014652074e018167107a17607c67187817688167147a17648268f8826751082652180116977704821818c88707827808821910aa98073308280f827782773308012806330801017b18307b17388268e88267f051082652180116977704821818c88707827808821910aa98073308280f827782773308012806330801017b18407b1748826708977704821818c878088287828908951830b49001485207b5009555108218209588c8956638821928959901520836ff2869828718510771828510330a977904c859097b19207b180882869577ff9777049877049577017b17956608017b1a288269510916821a588217508268f882aa18b4a0014a5207568257825908951830b490014c520747956610955510821a2895aa01821720ab75c7821808821a828708ac7a0d282c330a828708540725828797a804c88709821a58821750829882990882aa18b4a0014e51070833070128053307018210800082157882167095118800320089284952494a112992254952522a922549525212a284a410d1889224a58814c99224292915c99224292909514252886844449049924824114a2489242449952641084bb234264924122592441292a44a93208425599a4c9248244292244948922a4d82109664a9a188889224a924499244ca529404904c93a44952491285902592244a258492a410484a9284902549925253085992242935a49454215992a5a64c495292249524499248590a250124d3246992a4549624494a5252caa410b22449520aa92c29242929497a2124212cc9529524a5949242494952552825494549a950262549924822294924892492924812492289644a4a52a42c49aa0aa5244994a45692244992a49225292995d24a92242b4992d449929220090922044b9224491249929294a424914492242129498d24a92424256992908a2429499224214992d29224494a4a22494a922409912495242452922425494924524942924c24891449529924c92449244b92524942929248523289249194249194a42449922449a14a2a452949aa24a94952441249928444a2922449499249499288244952224992aa9449924892a42a6592242511499244494a9292a44c4992484924499294545292944452238900", + "00fe00ff00ff00ffca4b52ff29ea7c74d6bd301d1cae77ef7f24c8d294c847": "09626f6f747374726170000000000000010000000197040000060284067900300196010902a402e702280d00000028ae0000002803039511e07b10187b15107b1608648664783309043307000001ac967c9566fc5106769587047d7833050159083a8489ff003305025329c0002d3305035329e000253305045329f0001d3305055329f800153305065329fc000d8898fe009a850801ac564564587b17501002f4026478e45607c95707d88709e48707c98707887720d479098217c8750594983307000001da95072805330801821018821510821608951120320000951150ff7b10a8007b15a0007b169800330908ac985c013309fcaa973c0251073802958af8957508510a457d583306015908408489ff003306025329c0002d3306035329e000253306045329f0001d3306055329f800153306065329fc000d8898fe009a860801ae6a092805013306017b166457646864a65010043d02821a5107f000e4a607c9a707531760e600c85a089576a09587607b1751064c7d783305015908378489ff003305025329c0002d3305035329e000253305045329f0001d3305055329f800153305065329fc000d8898fe009a850801ac567f016458501006d70128073305330701e45608c95808e47808c97808330920ac98768219c89505c857079585e09577207b1751055e7d783306015908378489ff003306025329c0002d3306035329e000253306045329f0001d3306055329f800153306065329fc000d8898fe009a860801ac650d0164685010086401ae56198217c867077c7851082933083307289c0033065605ed33083307000001018210a8008215a000821698009511b0003200e46508c96808957501330701e47807330902957affae98093306330828517d573306015907378477ff003306025327c0002d3306035327e000253306045327f0001d3306055327f800153306065327fc000d8877fe009a7608017b1aac6a746457646850100ac9006478821a01c86507e46a09c96909e6890801c878088088fc330964330a640a0964757b1708481114951714330804951908330a040a0395171833098000330850100c33330820a107330964951a1864570a0b8117083d0700000233070000023308202824ff000000003307000001330832008d7a84aa07c8a70b510a0e647c0178c895cc01acbcfbc9a903843cf8c8cb0a580c1d8482ff0014090101010101010101ca920c017bbc95bb08acabfb843907520905280ec8a9090178a895aa01ac9afb320051089b0064797c77510791005127ff0090006c7a570a09330a330828735527c0000d330a01330b80284a5527e0000e330a02330b40ff283c5527f0000e330a03330b20ff282e5527f8000e330a04330b10ff28205527fc000e330a05330b08ff2812887afe00330b04ff93ab02ff85aa0701ae8a2b3308c8b70764ab01c8b90c7ccc97880895bbffd4c808520bf28aa903cf9707c88707320032000000002124492a21494a22212121212132154a9224a5909a248d88482422494924242424244426ad0a45129294924848484848888c4235499292a4944442424242426414524aa5144644942449529544424242424264a950936492549224258a905442483e54495a92241140962465495111942a24854421514814124544a6342549923a", + "01000000000000000000000000000000000000000000000000000000000000": "08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "02000000000000000000000000000000000000000000000000000000000000": "3cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e83cd36dd9ef53b9ba511a9b80d155c78627fccf80f294e02cf068991a457a06e8", + "03000000000000000000000000000000000000000000000000000000000000": "00", + "04000000000000000000000000000000000000000000000000000000000000": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d022351e22105a19aabb42589162ad7f1ea0df1c25cebf0e4a9fcd261301274862a2534be5b2f761dc898160a9b4762eb46bd171222f6cdf87f5127a9e8970a54c44fe7b2e12dda098854a9aaab03c3a47953085668673a84b0cedb4b0391ed6ae2deb1c3e04f0bc618a2bc1287d8599e8a1c47ff715cd4cbd3fe80e2607744d4514b491ed2ef76ae114ecb1af99ba6af32189bf0471c06aa3e6acdaf82e7a959cb24a5c1444cac3a6678f5182459fd8ce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bce68e0cf7f26c59f963b5846202d2327cc8bc0c4eff8cb9abd4012f9a71decf008faee314528448651e50bea6d2e7e5d3176698fea0b932405a4ec0a19775e72325e44a6d28f99fba887e04eb818f13d1b73f75f0161644283df61e7fbaad7713fae0ef79fe92499202834c97f512d744515a57971badf2df62e23697e9fe347f168fed0adb9ace131f49bbd500a324e2469569423f37c5d3b430990204ae17b383fcd582cb864168c8b46be8d779e7ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33b3e0e096b02e2ec98a3441410aeddd78c95e27a0da6f411a09c631c0f2bea6e98dfdac3e2f604ecda637e4969a139ceb70c534bd5edc4210eb5ac71178c1d62f0c977197a2c6a9e8ada6a14395bc9aa3a384d35f40c3493e20cb7efaa799f66d1cedd5b2928f8e34438b07072bbae404d7dfcee3f457f9103173805ee163ba550854e4660ccec49e25fafdb00e6adfbc8e875de1a9541e1721e956b972ef2b135cc7f71682615e12bb7d6acd353d7681000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e35c7f34a4bd4f2d04076a8c6f9060a0c8d2c6bdd082ceb3eda7df381cb260faffb78a95d81f6c7cdc517a36d81191b6f7718dcf44e76c0ce9fb724d3aea39fdb3c5f4ee31eb1f45e55b783b687b1e9087b092a18341c7cda102b4100685b0a014d55f1ccdb7600ec0db14bb90f7fc3126dc2625945bb44f302fc80df0c225546c06fa1952ef05bdc83ceb7a23373de0637cd9914272e3e3d1a455db6c48cc6b2b2c17e1dcf7cd1586a235821308aee0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d837ce344bc9defceb0d7de7e9e9925096768b7adb4dad932e532eb6551e0ea02b0b9121622bf8a9a9e811ee926740a876dd0d9036f2f3060ebfab0c7c489a338a7728ee2da4a265696edcc389fe02b2caf20b5b83aeb64aaf4184bedf127f4eea1d737875854411d58ca4a2b69b066b0a0c09d2a0b7121ade517687c51954df913fe930c227723dd8f58aa2415946044dc3fb15c367a2185d0fc1f7d2bb102ff14a230d5f81cfc8ad445e51efddbf426000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000085f9095f4abd040839d793d89ab5ff25c61e50c844ab6765e2c0b22373b5a8f6fbe5fc0cd61fdde580b3d44fe1be127197e33b91960b10d2c6fc75aec03f36e16c2a8204961097dbc2c5ba7655543385399cc9ef08bf2e520ccf3b0a7569d88492e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf01aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bc5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d48e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e33d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d03d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d05e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161df16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d3d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d0aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bcaa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bc3d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d048e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e300", + "05000000000000000000000000000000000000000000000000000000000000": "00000000", + "06000000000000000000000000000000000000000000000000000000000000": "6f6ad2224d7d58aec6573c623ab110700eaca20a48dc2965d535e466d524af2a835ac82bfa2ce8390bb50680d4b7a73dfa2a4cff6d8c30694b24a605f9574eafd2d34655ebcad804c56d2fd5f932c575b6a5dbb3f5652c5202bcc75ab9c2cc958a715731759b7fceaae288bcb70a605c31cbdfae83d0f4a45dfc2b2458dc9fae", + "07000000000000000000000000000000000000000000000000000000000000": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d022351e22105a19aabb42589162ad7f1ea0df1c25cebf0e4a9fcd261301274862a2534be5b2f761dc898160a9b4762eb46bd171222f6cdf87f5127a9e8970a54c44fe7b2e12dda098854a9aaab03c3a47953085668673a84b0cedb4b0391ed6ae2deb1c3e04f0bc618a2bc1287d8599e8a1c47ff715cd4cbd3fe80e2607744d4514b491ed2ef76ae114ecb1af99ba6af32189bf0471c06aa3e6acdaf82e7a959cb24a5c1444cac3a6678f5182459fd8ce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bce68e0cf7f26c59f963b5846202d2327cc8bc0c4eff8cb9abd4012f9a71decf008faee314528448651e50bea6d2e7e5d3176698fea0b932405a4ec0a19775e72325e44a6d28f99fba887e04eb818f13d1b73f75f0161644283df61e7fbaad7713fae0ef79fe92499202834c97f512d744515a57971badf2df62e23697e9fe347f168fed0adb9ace131f49bbd500a324e2469569423f37c5d3b430990204ae17b383fcd582cb864168c8b46be8d779e7ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33b3e0e096b02e2ec98a3441410aeddd78c95e27a0da6f411a09c631c0f2bea6e98dfdac3e2f604ecda637e4969a139ceb70c534bd5edc4210eb5ac71178c1d62f0c977197a2c6a9e8ada6a14395bc9aa3a384d35f40c3493e20cb7efaa799f66d1cedd5b2928f8e34438b07072bbae404d7dfcee3f457f9103173805ee163ba550854e4660ccec49e25fafdb00e6adfbc8e875de1a9541e1721e956b972ef2b135cc7f71682615e12bb7d6acd353d7681000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e35c7f34a4bd4f2d04076a8c6f9060a0c8d2c6bdd082ceb3eda7df381cb260faffb78a95d81f6c7cdc517a36d81191b6f7718dcf44e76c0ce9fb724d3aea39fdb3c5f4ee31eb1f45e55b783b687b1e9087b092a18341c7cda102b4100685b0a014d55f1ccdb7600ec0db14bb90f7fc3126dc2625945bb44f302fc80df0c225546c06fa1952ef05bdc83ceb7a23373de0637cd9914272e3e3d1a455db6c48cc6b2b2c17e1dcf7cd1586a235821308aee0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d837ce344bc9defceb0d7de7e9e9925096768b7adb4dad932e532eb6551e0ea02b0b9121622bf8a9a9e811ee926740a876dd0d9036f2f3060ebfab0c7c489a338a7728ee2da4a265696edcc389fe02b2caf20b5b83aeb64aaf4184bedf127f4eea1d737875854411d58ca4a2b69b066b0a0c09d2a0b7121ade517687c51954df913fe930c227723dd8f58aa2415946044dc3fb15c367a2185d0fc1f7d2bb102ff14a230d5f81cfc8ad445e51efddbf4260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "08000000000000000000000000000000000000000000000000000000000000": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d022351e22105a19aabb42589162ad7f1ea0df1c25cebf0e4a9fcd261301274862a2534be5b2f761dc898160a9b4762eb46bd171222f6cdf87f5127a9e8970a54c44fe7b2e12dda098854a9aaab03c3a47953085668673a84b0cedb4b0391ed6ae2deb1c3e04f0bc618a2bc1287d8599e8a1c47ff715cd4cbd3fe80e2607744d4514b491ed2ef76ae114ecb1af99ba6af32189bf0471c06aa3e6acdaf82e7a959cb24a5c1444cac3a6678f5182459fd8ce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bce68e0cf7f26c59f963b5846202d2327cc8bc0c4eff8cb9abd4012f9a71decf008faee314528448651e50bea6d2e7e5d3176698fea0b932405a4ec0a19775e72325e44a6d28f99fba887e04eb818f13d1b73f75f0161644283df61e7fbaad7713fae0ef79fe92499202834c97f512d744515a57971badf2df62e23697e9fe347f168fed0adb9ace131f49bbd500a324e2469569423f37c5d3b430990204ae17b383fcd582cb864168c8b46be8d779e7ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33b3e0e096b02e2ec98a3441410aeddd78c95e27a0da6f411a09c631c0f2bea6e98dfdac3e2f604ecda637e4969a139ceb70c534bd5edc4210eb5ac71178c1d62f0c977197a2c6a9e8ada6a14395bc9aa3a384d35f40c3493e20cb7efaa799f66d1cedd5b2928f8e34438b07072bbae404d7dfcee3f457f9103173805ee163ba550854e4660ccec49e25fafdb00e6adfbc8e875de1a9541e1721e956b972ef2b135cc7f71682615e12bb7d6acd353d7681000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e35c7f34a4bd4f2d04076a8c6f9060a0c8d2c6bdd082ceb3eda7df381cb260faffb78a95d81f6c7cdc517a36d81191b6f7718dcf44e76c0ce9fb724d3aea39fdb3c5f4ee31eb1f45e55b783b687b1e9087b092a18341c7cda102b4100685b0a014d55f1ccdb7600ec0db14bb90f7fc3126dc2625945bb44f302fc80df0c225546c06fa1952ef05bdc83ceb7a23373de0637cd9914272e3e3d1a455db6c48cc6b2b2c17e1dcf7cd1586a235821308aee0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d837ce344bc9defceb0d7de7e9e9925096768b7adb4dad932e532eb6551e0ea02b0b9121622bf8a9a9e811ee926740a876dd0d9036f2f3060ebfab0c7c489a338a7728ee2da4a265696edcc389fe02b2caf20b5b83aeb64aaf4184bedf127f4eea1d737875854411d58ca4a2b69b066b0a0c09d2a0b7121ade517687c51954df913fe930c227723dd8f58aa2415946044dc3fb15c367a2185d0fc1f7d2bb102ff14a230d5f81cfc8ad445e51efddbf4260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "09000000000000000000000000000000000000000000000000000000000000": "5e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d5e5a51aab2b048f8686ecd79712a80e3265a114cc73f14bdb2a59233fb66d022351e22105a19aabb42589162ad7f1ea0df1c25cebf0e4a9fcd261301274862a2534be5b2f761dc898160a9b4762eb46bd171222f6cdf87f5127a9e8970a54c44fe7b2e12dda098854a9aaab03c3a47953085668673a84b0cedb4b0391ed6ae2deb1c3e04f0bc618a2bc1287d8599e8a1c47ff715cd4cbd3fe80e2607744d4514b491ed2ef76ae114ecb1af99ba6af32189bf0471c06aa3e6acdaf82e7a959cb24a5c1444cac3a6678f5182459fd8ce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa2b95f7572875b0d0f186552ae745ba8222fc0b5bd456554bfe51c68938f8bce68e0cf7f26c59f963b5846202d2327cc8bc0c4eff8cb9abd4012f9a71decf008faee314528448651e50bea6d2e7e5d3176698fea0b932405a4ec0a19775e72325e44a6d28f99fba887e04eb818f13d1b73f75f0161644283df61e7fbaad7713fae0ef79fe92499202834c97f512d744515a57971badf2df62e23697e9fe347f168fed0adb9ace131f49bbd500a324e2469569423f37c5d3b430990204ae17b383fcd582cb864168c8b46be8d779e7ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f6190116d118d643a98878e294ccf62b509e214299931aad8ff9764181a4e33b3e0e096b02e2ec98a3441410aeddd78c95e27a0da6f411a09c631c0f2bea6e98dfdac3e2f604ecda637e4969a139ceb70c534bd5edc4210eb5ac71178c1d62f0c977197a2c6a9e8ada6a14395bc9aa3a384d35f40c3493e20cb7efaa799f66d1cedd5b2928f8e34438b07072bbae404d7dfcee3f457f9103173805ee163ba550854e4660ccec49e25fafdb00e6adfbc8e875de1a9541e1721e956b972ef2b135cc7f71682615e12bb7d6acd353d7681000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048e5fcdce10e0b64ec4eebd0d9211c7bac2f27ce54bca6f7776ff6fee86ab3e35c7f34a4bd4f2d04076a8c6f9060a0c8d2c6bdd082ceb3eda7df381cb260faffb78a95d81f6c7cdc517a36d81191b6f7718dcf44e76c0ce9fb724d3aea39fdb3c5f4ee31eb1f45e55b783b687b1e9087b092a18341c7cda102b4100685b0a014d55f1ccdb7600ec0db14bb90f7fc3126dc2625945bb44f302fc80df0c225546c06fa1952ef05bdc83ceb7a23373de0637cd9914272e3e3d1a455db6c48cc6b2b2c17e1dcf7cd1586a235821308aee0010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f16e5352840afb47e206b5c89f560f2611835855cf2e6ebad1acc9520a72591d837ce344bc9defceb0d7de7e9e9925096768b7adb4dad932e532eb6551e0ea02b0b9121622bf8a9a9e811ee926740a876dd0d9036f2f3060ebfab0c7c489a338a7728ee2da4a265696edcc389fe02b2caf20b5b83aeb64aaf4184bedf127f4eea1d737875854411d58ca4a2b69b066b0a0c09d2a0b7121ade517687c51954df913fe930c227723dd8f58aa2415946044dc3fb15c367a2185d0fc1f7d2bb102ff14a230d5f81cfc8ad445e51efddbf4260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0a000000000000000000000000000000000000000000000000000000000000": "0000", + "0b000000000000000000000000000000000000000000000000000000000000": "00000000", + "0c000000000000000000000000000000000000000000000000000000000000": "00000000000000000000000000", + "0d000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0e000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "0f000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "ff000000000000000000000000000000000000000000000000000000000000": "fcca4b52ff29ea7c74d6bd301d1cae77ef7f24c8d294c847bc82f93c5a70c26e00e40b540200000064000000000000006400000000000000081700000000000004000000" + } +} \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..69a672e --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,105 @@ +# Python Version Check Script + +This script checks for Python version 3.12 or higher and offers installation if requirements are not met. + +## Supported Operating Systems + +- **macOS** - uses Homebrew +- **Ubuntu/Debian** - uses apt with deadsnakes PPA +- **Arch Linux** - uses pacman + +## Usage + +1. Make the script executable: + ```bash + chmod +x scripts/check_python.sh + ``` + +2. Run the script: + ```bash + ./scripts/check_python.sh + ``` + +## What the Script Does + +1. **Python Version Check**: Searches for available Python commands and checks their versions +2. **OS Detection**: Automatically detects the operating system +3. **Installation Offer**: If a suitable version is not found, offers to install +4. **Installation**: Uses the appropriate package manager for installation +5. **Result Verification**: Ensures the installation was successful + +## Example Output + +### Successful Check +``` +[INFO] Checking Python version requirements (>= 3.12) +Found python3.12 version: 3.12.1 +[SUCCESS] Python requirement satisfied with python3.12 +[SUCCESS] Python version check passed! +``` + +### Installation on Ubuntu +``` +[INFO] Checking Python version requirements (>= 3.12) +Found python3 version: 3.10.6 +[WARNING] Python 3.12+ not found or version is too old +[INFO] Detected OS: ubuntu +[WARNING] Do you want to install/update Python? (y/N) +y +[INFO] Installing Python on Ubuntu... +[INFO] Updating package list... +[INFO] Adding deadsnakes PPA... +[INFO] Installing Python 3.12... +[INFO] Verifying installation... +Found python3.12 version: 3.12.7 +[SUCCESS] Installation successful! Python requirement satisfied with python3.12 +``` + +## Requirements + +### macOS +- Homebrew must be installed +- If Homebrew is not installed, the script will provide installation instructions + +### Ubuntu/Debian +- Sudo privileges for package installation +- Internet access to add PPA + +### Arch Linux +- Sudo privileges to use pacman +- Internet access for package updates + +## Security + +The script: +- Always asks for permission before installation +- Uses official package repositories +- Does not modify system files without permission +- Can be safely interrupted at any time (Ctrl+C) + +## Troubleshooting + +### Script cannot find Python after installation +Restart your terminal or run: +```bash +source ~/.bashrc # for bash +source ~/.zshrc # for zsh +``` + +### Permission errors +Make sure you have sudo privileges and your user can install packages. + +### Homebrew not found (macOS) +Install Homebrew: +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +## Script Features + +- **Multi-platform support**: Works on macOS, Ubuntu/Debian, and Arch Linux +- **Smart detection**: Checks multiple Python command variants (python3.12, python3.13, python3, python) +- **Safe installation**: Always prompts before making changes +- **Colored output**: Easy-to-read status messages +- **Error handling**: Graceful handling of edge cases and errors +- **Verification**: Post-installation verification to ensure success \ No newline at end of file diff --git a/scripts/check_python.sh b/scripts/check_python.sh new file mode 100755 index 0000000..35f91e7 --- /dev/null +++ b/scripts/check_python.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +# Script to check and install Python 3.12 or higher +# Supports: macOS, Ubuntu, Arch Linux + +set -e + +REQUIRED_MAJOR=3 +REQUIRED_MINOR=12 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check Python version +check_python_version() { + local python_cmd=$1 + + if ! command -v "$python_cmd" &> /dev/null; then + return 1 + fi + + local version_output + version_output=$($python_cmd --version 2>&1) + + # Extract version numbers + local version + version=$(echo "$version_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + + if [[ -z "$version" ]]; then + return 1 + fi + + local major minor patch + IFS='.' read -r major minor patch <<< "$version" + + echo "Found $python_cmd version: $version" + + if [[ $major -gt $REQUIRED_MAJOR ]] || + [[ $major -eq $REQUIRED_MAJOR && $minor -ge $REQUIRED_MINOR ]]; then + return 0 + else + return 1 + fi +} + +# Function to detect OS +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "macos" + elif [[ -f /etc/arch-release ]]; then + echo "arch" + elif [[ -f /etc/debian_version ]] || [[ -f /etc/ubuntu-release ]]; then + echo "ubuntu" + else + echo "unknown" + fi +} + +# Function to install Python on macOS +install_python_macos() { + print_info "Installing Python on macOS..." + + if ! command -v brew &> /dev/null; then + print_error "Homebrew is not installed. Please install Homebrew first:" + echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + exit 1 + fi + + print_info "Updating Homebrew..." + brew update + + print_info "Installing Python 3.12..." + brew install python@3.12 + + # Create symlinks if needed + if [[ ! -L /usr/local/bin/python3.12 ]]; then + brew link python@3.12 + fi +} + +# Function to install Python on Ubuntu +install_python_ubuntu() { + print_info "Installing Python on Ubuntu..." + + print_info "Updating package list..." + sudo apt update + + # Add deadsnakes PPA for newer Python versions + print_info "Adding deadsnakes PPA..." + sudo apt install -y software-properties-common + sudo add-apt-repository -y ppa:deadsnakes/ppa + sudo apt update + + print_info "Installing Python 3.12..." + sudo apt install -y python3.12 python3.12-venv python3.12-pip python3.12-dev + + # Set up alternatives + sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 +} + +# Function to install Python on Arch Linux +install_python_arch() { + print_info "Installing Python on Arch Linux..." + + print_info "Updating package database..." + sudo pacman -Sy + + print_info "Installing Python..." + sudo pacman -S --noconfirm python python-pip +} + +# Main function +main() { + print_info "Checking Python version requirements (>= ${REQUIRED_MAJOR}.${REQUIRED_MINOR})" + + # Check various Python commands + local python_found=false + local python_commands=("python3.12" "python3.13" "python3.14" "python3" "python") + + for cmd in "${python_commands[@]}"; do + if check_python_version "$cmd"; then + print_success "Python requirement satisfied with $cmd" + python_found=true + break + fi + done + + if [[ "$python_found" == "true" ]]; then + print_success "Python version check passed!" + exit 0 + fi + + print_warning "Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+ not found or version is too old" + + # Detect OS + local os + os=$(detect_os) + print_info "Detected OS: $os" + + # Ask user for permission to install + echo + print_warning "Do you want to install/update Python? (y/N)" + read -r response + + if [[ ! "$response" =~ ^[Yy]$ ]]; then + print_info "Installation cancelled by user" + exit 0 + fi + + # Install based on OS + case $os in + "macos") + install_python_macos + ;; + "ubuntu") + install_python_ubuntu + ;; + "arch") + install_python_arch + ;; + *) + print_error "Unsupported operating system: $os" + print_info "Please install Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+ manually" + exit 1 + ;; + esac + + # Verify installation + print_info "Verifying installation..." + sleep 2 + + python_found=false + for cmd in "${python_commands[@]}"; do + if check_python_version "$cmd"; then + print_success "Installation successful! Python requirement satisfied with $cmd" + python_found=true + break + fi + done + + if [[ "$python_found" == "false" ]]; then + print_error "Installation completed but Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+ still not found" + print_info "You may need to restart your terminal or add Python to your PATH" + exit 1 + fi +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/scripts/get_version.sh b/scripts/get_version.sh new file mode 100755 index 0000000..b7d7e35 --- /dev/null +++ b/scripts/get_version.sh @@ -0,0 +1,96 @@ +#!/bin/sh -eu +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# +# +# Version String Generator for Git Repositories +# +# This script generates a version string based on the current Git state. +# It retrieves the latest tag, calculates the number of commits since the last tag, +# includes the current branch (if different from master), and appends the commit hash. +# If the working directory has uncommitted changes, it marks the version as "dirty". +# +# Usage: +# ./get_version.sh [--sanitized] +# +# Options: +# --sanitized Replaces non-alphanumeric characters in the version string +# with hyphens for compatibility with package managers. +# +# Output: +# Prints a version string in the format: +# ---- +# If the repository is dirty, "-dirty" is appended. +# +# Example: +# v1.2.3-5-feature-branch-2-a1b2c3d-dirty +# +# Requirements: +# - Git +# - sed (for --sanitized mode) +# +# Notes: +# - If the repository has no tags, an initial tag "_init" is created. +# - If the repository is not a Git repo, outputs "Unknown(no git)". +# - If Git is not installed, it exits with an error. +# + +sanitize_version() { + echo "$1" | sed -E 's/[^a-zA-Z0-9.+~:-]/-/g' +} + +realpath() { + case "$1" in + /*) echo "$1" ;; + *) echo "$(pwd)/$1" ;; + esac +} + +cd "$(dirname "$(realpath "$0")")" + +SANITIZED=false +[ "$#" -gt 0 ] && [ "$1" = "--sanitized" ] && SANITIZED=true + +if [ -x "$(command -v git)" ] && [ -d "$(git rev-parse --git-dir 2>/dev/null)" ]; then + HEAD=$(git rev-parse --short HEAD) + + # Determine the main branch (fallback to default names if necessary) + MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + if [ -z "$MAIN_BRANCH" ]; then + MAIN_BRANCH=$(git branch --format='%(refname:short)' | grep -E '^(main|master)$' | head -n1) + fi + if [ -z "$MAIN_BRANCH" ]; then + MAIN_BRANCH="master" # Fallback to "master" if no main branch detected + fi + + COMMON=$(git merge-base HEAD "$MAIN_BRANCH" 2>/dev/null || echo "$HEAD") + + if ! git tag | grep -q .; then + ROOT_COMMIT=$(git rev-list --max-parents=0 HEAD || echo "") + [ -n "$ROOT_COMMIT" ] && ! git tag | grep -q "_init" && git tag _init "$ROOT_COMMIT" + fi + + DESCR=$(git describe --tags --long "$COMMON" 2>/dev/null || echo "") + [ -z "$DESCR" ] && DESCR="$HEAD-0-g$HEAD" + + TAG_IN_MASTER=$(echo "$DESCR" | sed -E "s/v?(.*)-([0-9]+)-g[a-f0-9]+/\1/") + TAG_TO_FORK_DISTANCE=$(echo "$DESCR" | sed -E "s/v?(.*)-([0-9]+)-g[a-f0-9]+/\2/") + + BRANCH=$(git branch --show-current 2>/dev/null || echo "$HEAD") + FORK_TO_HEAD_DISTANCE=$(git rev-list --count "$COMMON..HEAD" 2>/dev/null || echo "0") + + RESULT=$TAG_IN_MASTER + [ "$TAG_TO_FORK_DISTANCE" != "0" ] && RESULT="$RESULT-$TAG_TO_FORK_DISTANCE" + [ "$BRANCH" != "$MAIN_BRANCH" ] && RESULT="$RESULT-$BRANCH-$FORK_TO_HEAD_DISTANCE-$HEAD" + + git diff --quiet || DIRTY="-dirty" + RESULT="$RESULT${DIRTY:-}" +else + RESULT="Unknown(no git)" +fi + +[ "$SANITIZED" = true ] && RESULT=$(sanitize_version "$RESULT") + +printf "%s" "$RESULT" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..ca71f33 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,41 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +# Executables (should contain `main()` function) +add_subdirectory(executable) + +# Application's thinks +add_subdirectory(app) + +# Blockchain +add_subdirectory(blockchain) + +# Cryptography thinks +add_subdirectory(crypto) + +# Dependency injection +add_subdirectory(injector) + +# Logging subsystem +add_subdirectory(log) + +# Metrics subsystem +add_subdirectory(metrics) + +# Clocks and time subsystem +add_subdirectory(clock) + +# Subscription Engine subsystem +add_subdirectory(se) + +# Modules subsystem +add_subdirectory(modules) + +# Storage +add_subdirectory(storage) + +# Utilities +add_subdirectory(utils) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 0000000..12cd652 --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,71 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +set(BUILD_VERSION_CPP "${CMAKE_BINARY_DIR}/generated/app/build_version.cpp") +set(GET_VERSION_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/get_version.sh") +add_custom_command( + OUTPUT ${BUILD_VERSION_CPP} + COMMAND echo "// Auto-generated file" > ${BUILD_VERSION_CPP} + COMMAND echo "#include " >> ${BUILD_VERSION_CPP} + COMMAND echo "namespace lean {" >> ${BUILD_VERSION_CPP} + COMMAND echo " const std::string &buildVersion() {" >> ${BUILD_VERSION_CPP} + COMMAND printf " static const std::string buildVersion(\"" >> ${BUILD_VERSION_CPP} + COMMAND ${GET_VERSION_SCRIPT} >> ${BUILD_VERSION_CPP} + COMMAND echo "\");" >> ${BUILD_VERSION_CPP} + COMMAND echo " return buildVersion;" >> ${BUILD_VERSION_CPP} + COMMAND echo " }" >> ${BUILD_VERSION_CPP} + COMMAND echo "}" >> ${BUILD_VERSION_CPP} + COMMENT "Generate build_version.cpp" + DEPENDS ${GET_VERSION_SCRIPT} + VERBATIM +) +add_library(build_version + ${CMAKE_BINARY_DIR}/generated/app/build_version.cpp +) + +add_library(app_configuration SHARED + configuration.cpp +) +target_link_libraries(app_configuration + Boost::boost + fmt::fmt +) + +add_library(app_configurator SHARED + configurator.cpp +) +target_link_libraries(app_configurator + app_configuration + yaml-cpp::yaml-cpp + Boost::program_options + build_version +) + +add_library(app_state_manager SHARED + impl/state_manager_impl.cpp +) +target_link_libraries(app_state_manager + qtils::qtils + logger +) + +add_library(chain_spec SHARED + impl/chain_spec_impl.cpp +) +target_link_libraries(chain_spec + logger + Boost::property_tree + app_configuration +) + +add_library(application SHARED + impl/application_impl.cpp +) +target_link_libraries(application + qtils::qtils + app_configuration + metrics +) diff --git a/src/app/application.hpp b/src/app/application.hpp new file mode 100644 index 0000000..72d4698 --- /dev/null +++ b/src/app/application.hpp @@ -0,0 +1,22 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean::app { + + /// @class Application - Lean-application interface + class Application : private Singleton { + public: + virtual ~Application() = default; + + /// Runs node + virtual void run() = 0; + }; + +} // namespace lean::app diff --git a/src/app/build_version.hpp b/src/app/build_version.hpp new file mode 100644 index 0000000..02cb81d --- /dev/null +++ b/src/app/build_version.hpp @@ -0,0 +1,19 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean { + /** + * @returns String indicating current build version. Might to contain: tag, + * number of commits from tag to fork, commit branch and number of commits + * from fork to current commit. + * @note Definition is generating by cmake + */ + const std::string &buildVersion(); +} // namespace lean diff --git a/src/app/chain_spec.hpp b/src/app/chain_spec.hpp new file mode 100644 index 0000000..173327e --- /dev/null +++ b/src/app/chain_spec.hpp @@ -0,0 +1,38 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include + +#include "lean_types/types.hpp" + +namespace lean { + + using NodeAddress = Stub; + +} + +namespace lean::app { + + class ChainSpec { + public: + virtual ~ChainSpec() = default; + + [[nodiscard]] virtual const std::string &id() const = 0; + + [[nodiscard]] virtual const std::vector &bootNodes() const = 0; + + [[nodiscard]] virtual const qtils::ByteVec &genesisHeader() const = 0; + + [[nodiscard]] virtual const std::map & + genesisState() const = 0; + }; + +} // namespace lean::app diff --git a/src/app/configuration.cpp b/src/app/configuration.cpp new file mode 100644 index 0000000..e18e794 --- /dev/null +++ b/src/app/configuration.cpp @@ -0,0 +1,50 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "app/configuration.hpp" + +namespace lean::app { + + Configuration::Configuration() + : version_("undefined"), + name_("unnamed"), + database_{ + .directory = "db", + .cache_size = 1 << 30, + }, + metrics_{ + .endpoint{}, + .enabled{}, + } {} + + const std::string &Configuration::nodeVersion() const { + return version_; + } + + const std::string &Configuration::nodeName() const { + return name_; + } + + const std::filesystem::path &Configuration::basePath() const { + return base_path_; + } + + const std::filesystem::path &Configuration::specFile() const { + return spec_file_; + } + + const std::filesystem::path &Configuration::modulesDir() const { + return modules_dir_; + } + + const Configuration::DatabaseConfig &Configuration::database() const { + return database_; + } + + const Configuration::MetricsConfig &Configuration::metrics() const { + return metrics_; + } + +} // namespace lean::app diff --git a/src/app/configuration.hpp b/src/app/configuration.hpp new file mode 100644 index 0000000..b6e9e11 --- /dev/null +++ b/src/app/configuration.hpp @@ -0,0 +1,58 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include +#include + +namespace lean::app { + class Configuration : Singleton { + public: + using Endpoint = boost::asio::ip::tcp::endpoint; + + struct DatabaseConfig { + std::filesystem::path directory = "db"; + size_t cache_size = 1 << 30; // 1GiB + }; + + struct MetricsConfig { + Endpoint endpoint; + std::optional enabled; + }; + + Configuration(); + virtual ~Configuration() = default; + + // /// Generate yaml-file with actual config + // virtual void generateConfigFile() const = 0; + + [[nodiscard]] virtual const std::string &nodeVersion() const; + [[nodiscard]] virtual const std::string &nodeName() const; + [[nodiscard]] virtual const std::filesystem::path &basePath() const; + [[nodiscard]] virtual const std::filesystem::path &specFile() const; + [[nodiscard]] virtual const std::filesystem::path &modulesDir() const; + + [[nodiscard]] virtual const DatabaseConfig &database() const; + + [[nodiscard]] virtual const MetricsConfig &metrics() const; + + private: + friend class Configurator; // for external configure + + std::string version_; + std::string name_; + std::filesystem::path base_path_; + std::filesystem::path spec_file_; + std::filesystem::path modules_dir_; + + DatabaseConfig database_; + MetricsConfig metrics_; + }; + +} // namespace lean::app diff --git a/src/app/configurator.cpp b/src/app/configurator.cpp new file mode 100644 index 0000000..288b349 --- /dev/null +++ b/src/app/configurator.cpp @@ -0,0 +1,572 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "app/configurator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "app/build_version.hpp" +#include "app/configuration.hpp" +#include "utils/parsers.hpp" + +using Endpoint = boost::asio::ip::tcp::endpoint; + +OUTCOME_CPP_DEFINE_CATEGORY(lean::app, Configurator::Error, e) { + using E = lean::app::Configurator::Error; + switch (e) { + case E::CliArgsParseFailed: + return "CLI Arguments parse failed"; + case E::ConfigFileParseFailed: + return "Config file parse failed"; + case E::InvalidValue: + return "Result config has invalid values"; + } + BOOST_UNREACHABLE_RETURN("Unknown log::Error"); +} + +namespace { + template + void find_argument(boost::program_options::variables_map &vm, + const char *name, + Func &&f) { + assert(nullptr != name); + if (auto it = vm.find(name); it != vm.end()) { + if (it->second.defaulted()) { + return; + } + std::forward(f)(it->second.as()); + } + } + + template + std::optional find_argument(boost::program_options::variables_map &vm, + const std::string &name) { + if (auto it = vm.find(name); it != vm.end()) { + if (!it->second.defaulted()) { + return it->second.as(); + } + } + return std::nullopt; + } + + bool find_argument(boost::program_options::variables_map &vm, + const std::string &name) { + if (auto it = vm.find(name); it != vm.end()) { + if (!it->second.defaulted()) { + return true; + } + } + return false; + } + +} // namespace + +namespace lean::app { + + Configurator::Configurator(int argc, const char **argv, const char **env) + : argc_(argc), argv_(argv), env_(env) { + config_ = std::make_shared(); + + config_->version_ = buildVersion(); + config_->name_ = "noname"; + + config_->database_.directory = "db"; + config_->database_.cache_size = 512 << 20; // 512MiB + + config_->metrics_.endpoint = {boost::asio::ip::address_v4::any(), 9615}; + config_->metrics_.enabled = std::nullopt; + + namespace po = boost::program_options; + + // clang-format off + + po::options_description general_options("General options", 120, 100); + general_options.add_options() + ("help,h", "Show this help message.") + ("version,v", "Show version information.") + ("base_path", po::value(), "Set base path. All relative paths will be resolved based on this path.") + ("config,c", po::value(), "Optional. Filepath to load configuration from. Overrides default configuration values.") + ("spec_file", po::value(), "Set path to spec file.") + ("modules_dir", po::value(), "Set path to directory containing modules.") + ("name,n", po::value(), "Set name of node.") + ("log,l", po::value>(), + "Sets a custom logging filter.\n" + "Syntax: =, e.g., -llibp2p=off.\n" + "Log levels: trace, debug, verbose, info, warn, error, critical, off.\n" + "Default: all targets log at `info`.\n" + "Global log level can be set with: -l.") + ; + + po::options_description storage_options("Storage options"); + storage_options.add_options() + ("db_path", po::value()->default_value(config_->database_.directory), "Path to DB directory. Can be relative on base path.") + // ("db-tmp", "Use temporary storage path.") + ("db_cache_size", po::value()->default_value(config_->database_.cache_size), "Limit the memory the database cache can use .") + ; + + po::options_description metrics_options("Metric options"); + metrics_options.add_options() + ("prometheus_disable", "Set to disable OpenMetrics.") + ("prometheus_host", po::value(), "Set address for OpenMetrics over HTTP.") + ("prometheus_port", po::value(), "Set port for OpenMetrics over HTTP.") + ; + + // clang-format on + + cli_options_ + .add(general_options) // + .add(storage_options) + .add(metrics_options); + } + + outcome::result Configurator::step1() { // read min cli-args and config + namespace po = boost::program_options; + namespace fs = std::filesystem; + + po::options_description options; + options.add_options()("help,h", "show help")("version,v", "show version")( + "config,c", po::value(), "config-file path"); + + po::variables_map vm; + + // first-run parse to read-only general options and to lookup for "help", + // "config" and "version". all the rest options are ignored + try { + po::parsed_options parsed = po::command_line_parser(argc_, argv_) + .options(options) + .allow_unregistered() + .run(); + po::store(parsed, vm); + po::notify(vm); + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << '\n' + << "Try run with option '--help' for more information\n"; + return Error::CliArgsParseFailed; + } + + if (vm.contains("help")) { + std::cout << "Lean-node version " << buildVersion() << '\n'; + std::cout << cli_options_ << '\n'; + return true; + } + + if (vm.contains("version")) { + std::cout << "Lean-node version " << buildVersion() << '\n'; + return true; + } + + if (vm.contains("config")) { + auto path = vm["config"].as(); + try { + config_file_ = YAML::LoadFile(path); + } catch (const std::exception &exception) { + std::cerr << "Error: Can't parse file " + << std::filesystem::weakly_canonical(path) << ": " + << exception.what() << "\n" + << "Option --config must be path to correct yaml-file\n" + << "Try run with option '--help' for more information\n"; + return Error::ConfigFileParseFailed; + } + } + + return false; + } + + outcome::result Configurator::step2() { + namespace po = boost::program_options; + namespace fs = std::filesystem; + + try { + // second-run parse to gather all known options + // with reporting about any unrecognized input + po::parsed_options parsed = + po::command_line_parser(argc_, argv_).options(cli_options_).run(); + po::store(parsed, cli_values_map_); + po::notify(cli_values_map_); + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << '\n' + << "Try run with option '--help' for more information\n"; + return Error::CliArgsParseFailed; + } + + return false; + } + + outcome::result Configurator::getLoggingConfig() { + auto logging = (*config_file_)["logging"]; + if (logging.IsDefined()) { + return logging; + } + return YAML::Node{}; // TODO return default logging config + } + + outcome::result> Configurator::calculateConfig( + qtils::SharedRef logger) { + logger_ = std::move(logger); + OUTCOME_TRY(initGeneralConfig()); + OUTCOME_TRY(initDatabaseConfig()); + OUTCOME_TRY(initOpenMetricsConfig()); + + return config_; + } + + outcome::result Configurator::initGeneralConfig() { + // Init by config-file + if (config_file_.has_value()) { + auto section = (*config_file_)["general"]; + if (section.IsDefined()) { + if (section.IsMap()) { + auto name = section["name"]; + if (name.IsDefined()) { + if (name.IsScalar()) { + auto value = name.as(); + config_->name_ = value; + } else { + file_errors_ << "E: Value 'general.name' must be scalar\n"; + file_has_error_ = true; + } + } + auto base_path = section["base_path"]; + if (base_path.IsDefined()) { + if (base_path.IsScalar()) { + auto value = base_path.as(); + config_->base_path_ = value; + } else { + file_errors_ << "E: Value 'general.base_path' must be scalar\n"; + file_has_error_ = true; + } + } + auto spec_file = section["spec_file"]; + if (spec_file.IsDefined()) { + if (spec_file.IsScalar()) { + auto value = spec_file.as(); + config_->spec_file_ = value; + } else { + file_errors_ << "E: Value 'general.spec_file' must be scalar\n"; + file_has_error_ = true; + } + } + auto modules_dir = section["modules_dir"]; + if (modules_dir.IsDefined()) { + if (modules_dir.IsScalar()) { + auto value = modules_dir.as(); + config_->modules_dir_ = value; + } else { + file_errors_ << "E: Value 'general.modules_dir' must be scalar\n"; + file_has_error_ = true; + } + } + } else { + file_errors_ << "E: Section 'general' defined, but is not map\n"; + file_has_error_ = true; + } + } + } + + if (file_has_error_) { + std::string path; + find_argument( + cli_values_map_, "config", [&](const std::string &value) { + path = value; + }); + SL_ERROR(logger_, "Config file `{}` has some problems:", path); + std::istringstream iss(file_errors_.str()); + std::string line; + while (std::getline(iss, line)) { + SL_ERROR(logger_, " {}", std::string_view(line).substr(3)); + } + return Error::ConfigFileParseFailed; + } + + // Adjust by CLI arguments + bool fail; + + fail = false; + find_argument( + cli_values_map_, "name", [&](const std::string &value) { + config_->name_ = value; + }); + find_argument( + cli_values_map_, "base_path", [&](const std::string &value) { + config_->base_path_ = value; + }); + find_argument( + cli_values_map_, "modules_dir", [&](const std::string &value) { + config_->modules_dir_ = value; + }); + find_argument( + cli_values_map_, "spec_file", [&](const std::string &value) { + config_->spec_file_ = value; + }); + if (fail) { + return Error::CliArgsParseFailed; + } + + // Check values + if (not config_->base_path_.is_absolute()) { + SL_ERROR(logger_, + "The 'base_path' must be defined as absolute: {}", + config_->base_path_.c_str()); + return Error::InvalidValue; + } + if (not is_directory(config_->base_path_)) { + SL_ERROR(logger_, + "The 'base_path' does not exist or is not a directory: {}", + config_->base_path_.c_str()); + return Error::InvalidValue; + } + current_path(config_->base_path_); + + auto make_absolute = [&](const std::filesystem::path &path) { + return weakly_canonical(config_->base_path_.is_absolute() + ? path + : (config_->base_path_ / path)); + }; + + config_->modules_dir_ = make_absolute(config_->modules_dir_); + if (not is_directory(config_->modules_dir_)) { + SL_ERROR(logger_, + "The 'modules_dir' does not exist or is not a directory: {}", + config_->modules_dir_.c_str()); + return Error::InvalidValue; + } + + config_->spec_file_ = make_absolute(config_->spec_file_); + if (not is_regular_file(config_->spec_file_)) { + SL_ERROR(logger_, + "The 'spec_file' does not exist or is not a file: {}", + config_->spec_file_.c_str()); + return Error::InvalidValue; + } + + return outcome::success(); + } + + outcome::result Configurator::initDatabaseConfig() { + // Init by config-file + if (config_file_.has_value()) { + auto section = (*config_file_)["database"]; + if (section.IsDefined()) { + if (section.IsMap()) { + auto path = section["path"]; + if (path.IsDefined()) { + if (path.IsScalar()) { + auto value = path.as(); + config_->database_.directory = value; + } else { + file_errors_ << "E: Value 'database.path' must be scalar\n"; + file_has_error_ = true; + } + } + auto spec_file = section["cache_size"]; + if (spec_file.IsDefined()) { + if (spec_file.IsScalar()) { + auto value = util::parseByteQuantity(spec_file.as()); + if (value.has_value()) { + config_->database_.cache_size = value.value(); + } else { + file_errors_ << "E: Bad 'cache_size' value; " + "Expected: 4096, 512Mb, 1G, etc.\n"; + } + } else { + file_errors_ << "E: Value 'database.cache_size' must be scalar\n"; + file_has_error_ = true; + } + } + } else { + file_errors_ << "E: Section 'database' defined, but is not map\n"; + file_has_error_ = true; + } + } + } + + if (file_has_error_) { + std::string path; + find_argument( + cli_values_map_, "config", [&](const std::string &value) { + path = value; + }); + SL_ERROR(logger_, "Config file `{}` has some problems:", path); + std::istringstream iss(file_errors_.str()); + std::string line; + while (std::getline(iss, line)) { + SL_ERROR(logger_, " {}", std::string_view(line).substr(3)); + } + return Error::ConfigFileParseFailed; + } + + // Adjust by CLI arguments + bool fail; + + fail = false; + find_argument( + cli_values_map_, "db_path", [&](const std::string &value) { + config_->database_.directory = value; + }); + find_argument( + cli_values_map_, "db_cache_size", [&](const uint32_t &value) { + config_->database_.cache_size = value; + }); + if (fail) { + return Error::CliArgsParseFailed; + } + + // Check values + auto make_absolute = [&](const std::filesystem::path &path) { + return weakly_canonical(config_->base_path_.is_absolute() + ? path + : (config_->base_path_ / path)); + }; + + config_->database_.directory = make_absolute(config_->database_.directory); + + return outcome::success(); + } + + outcome::result Configurator::initOpenMetricsConfig() { + if (config_file_.has_value()) { + auto section = (*config_file_)["metrics"]; + if (section.IsDefined()) { + if (section.IsMap()) { + auto enabled = section["enabled"]; + if (enabled.IsDefined()) { + if (enabled.IsScalar()) { + auto value = enabled.as(); + if (value == "true") { + config_->metrics_.enabled = true; + } else if (value == "false") { + config_->metrics_.enabled = false; + } else { + file_errors_ + << "E: Value 'network.metrics.enabled' has wrong value. " + "Expected 'true' or 'false'\n"; + file_has_error_ = true; + } + } else { + file_errors_ << "E: Value 'metrics.enabled' must be scalar\n"; + file_has_error_ = true; + } + } + + auto host = section["host"]; + if (host.IsDefined()) { + if (host.IsScalar()) { + auto value = host.as(); + boost::beast::error_code ec; + auto address = boost::asio::ip::make_address(value, ec); + if (!ec) { + config_->metrics_.endpoint = { + address, config_->metrics_.endpoint.port()}; + if (not config_->metrics_.enabled.has_value()) { + config_->metrics_.enabled = true; + } + } else { + file_errors_ << "E: Value 'network.metrics.host' defined, " + "but has invalid value\n"; + } + } else { + file_errors_ << "E: Value 'network.metrics.host' defined, " + "but is not scalar\n"; + file_has_error_ = true; + } + } + + auto port = section["port"]; + if (port.IsDefined()) { + if (port.IsScalar()) { + auto value = port.as(); + if (value > 0 and value <= 65535) { + config_->metrics_.endpoint = { + config_->metrics_.endpoint.address(), + static_cast(value)}; + if (not config_->metrics_.enabled.has_value()) { + config_->metrics_.enabled = true; + } + } else { + file_errors_ << "E: Value 'network.metrics.port' defined, " + "but has invalid value\n"; + file_has_error_ = true; + } + } else { + file_errors_ << "E: Value 'network.metrics.port' defined, " + "but is not scalar\n"; + file_has_error_ = true; + } + } + + } else { + file_errors_ << "E: Section 'metrics' defined, but is not map\n"; + file_has_error_ = true; + } + } + } + + bool fail; + + fail = false; + find_argument( + cli_values_map_, "prometheus_host", [&](const std::string &value) { + boost::beast::error_code ec; + auto address = boost::asio::ip::make_address(value, ec); + if (!ec) { + config_->metrics_.endpoint = {address, + config_->metrics_.endpoint.port()}; + if (not config_->metrics_.enabled.has_value()) { + config_->metrics_.enabled = true; + } + } else { + std::cerr << "Option --prometheus_host has invalid value\n" + << "Try run with option '--help' for more information\n"; + fail = true; + } + }); + if (fail) { + return Error::CliArgsParseFailed; + } + + fail = false; + find_argument( + cli_values_map_, "prometheus_port", [&](const uint16_t &value) { + if (value > 0 and value <= 65535) { + config_->metrics_.endpoint = {config_->metrics_.endpoint.address(), + static_cast(value)}; + if (not config_->metrics_.enabled.has_value()) { + config_->metrics_.enabled = true; + } + } else { + std::cerr << "Option --prometheus_port has invalid value\n" + << "Try run with option '--help' for more information\n"; + fail = true; + } + }); + if (fail) { + return Error::CliArgsParseFailed; + } + + if (find_argument(cli_values_map_, "prometheus_disabled")) { + config_->metrics_.enabled = false; + }; + if (not config_->metrics_.enabled.has_value()) { + config_->metrics_.enabled = false; + } + + return outcome::success(); + } + +} // namespace lean::app diff --git a/src/app/configurator.hpp b/src/app/configurator.hpp new file mode 100644 index 0000000..8246f23 --- /dev/null +++ b/src/app/configurator.hpp @@ -0,0 +1,80 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "injector/dont_inject.hpp" + +namespace soralog { + class Logger; +} // namespace soralog + +namespace lean::app { + class Configuration; +} // namespace lean::app + +namespace lean::app { + + class Configurator final { + public: + enum class Error : uint8_t { + CliArgsParseFailed, + ConfigFileParseFailed, + InvalidValue, + }; + + DONT_INJECT(Configurator); + + Configurator() = delete; + Configurator(Configurator &&) noexcept = delete; + Configurator(const Configurator &) = delete; + ~Configurator() = default; + Configurator &operator=(Configurator &&) noexcept = delete; + Configurator &operator=(const Configurator &) = delete; + + Configurator(int argc, const char **argv, const char **env); + + // Parse CLI args for help, version and config + outcome::result step1(); + + // Parse remaining CLI args + outcome::result step2(); + + outcome::result getLoggingConfig(); + + outcome::result> calculateConfig( + qtils::SharedRef logger); + + private: + outcome::result initGeneralConfig(); + outcome::result initDatabaseConfig(); + outcome::result initOpenMetricsConfig(); + + int argc_; + const char **argv_; + const char **env_; + + std::shared_ptr config_; + std::shared_ptr logger_; + + std::optional config_file_; + bool file_has_warn_ = false; + bool file_has_error_ = false; + std::ostringstream file_errors_; + + boost::program_options::options_description cli_options_; + boost::program_options::variables_map cli_values_map_; + }; + +} // namespace lean::app + +OUTCOME_HPP_DECLARE_ERROR(lean::app, Configurator::Error); diff --git a/src/app/impl/application_impl.cpp b/src/app/impl/application_impl.cpp new file mode 100644 index 0000000..e805202 --- /dev/null +++ b/src/app/impl/application_impl.cpp @@ -0,0 +1,79 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "app/impl/application_impl.hpp" + +#include +#include + +#include "app/configuration.hpp" +#include "app/impl/watchdog.hpp" +#include "app/state_manager.hpp" +#include "clock/clock.hpp" +#include "log/logger.hpp" +#include "metrics/histogram_timer.hpp" +#include "metrics/metrics.hpp" +#include "se/impl/subscription_manager.hpp" + +namespace lean::app { + + SeHolder::SeHolder(SePtr se) : se_(std::move(se)) {} + + SeHolder::~SeHolder() { + se_->dispose(); + } + + ApplicationImpl::ApplicationImpl( + qtils::SharedRef logsys, + qtils::SharedRef config, + qtils::SharedRef state_manager, + qtils::SharedRef watchdog, + qtils::SharedRef metrics_exposer, + qtils::SharedRef system_clock, + std::shared_ptr) + : logger_(logsys->getLogger("Application", "application")), + app_config_(std::move(config)), + state_manager_(std::move(state_manager)), + watchdog_(std::move(watchdog)), + metrics_exposer_(std::move(metrics_exposer)), + system_clock_(std::move(system_clock)), + metrics_registry_(metrics::createRegistry()) { + // Metric for exposing name and version of node + metrics::GaugeHelper( + "lean_build_info", + "A metric with a constant '1' value labeled by name, version", + std::map{ + {"name", app_config_->nodeName()}, + {"version", app_config_->nodeVersion()}}) + ->set(1); + } + + void ApplicationImpl::run() { + logger_->info("Start as node version '{}' named as '{}' with PID {}", + app_config_->nodeVersion(), + app_config_->nodeName(), + getpid()); + + std::thread watchdog_thread([this] { + soralog::util::setThreadName("watchdog"); + watchdog_->checkLoop(kWatchdogDefaultTimeout); + }); + + state_manager_->atShutdown([this] { watchdog_->stop(); }); + + // Metric storing start time + metrics::GaugeHelper("lean_process_start_time_seconds", + "UNIX timestamp of the moment the process started") + ->set(system_clock_->nowSec()); + + state_manager_->run(); + + watchdog_->stop(); + + watchdog_thread.join(); + } + +} // namespace lean::app diff --git a/src/app/impl/application_impl.hpp b/src/app/impl/application_impl.hpp new file mode 100644 index 0000000..a86bbf3 --- /dev/null +++ b/src/app/impl/application_impl.hpp @@ -0,0 +1,101 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "app/application.hpp" +#include "se/subscription_fwd.hpp" + +namespace lean { + class Watchdog; +} // namespace lean + +namespace lean::app { + class Configuration; + class StateManager; +} // namespace lean::app + +namespace lean::clock { + class SystemClock; +} // namespace lean::clock + +namespace soralog { + class Logger; +} // namespace soralog + +namespace lean::log { + class LoggingSystem; +} // namespace lean::log + +namespace lean::metrics { + class Registry; + class Gauge; + class Exposer; +} // namespace lean::metrics + +namespace lean::app { + + /** + * @brief RAII holder for subscription engine management + * + * SeHolder is responsible for managing the lifetime of subscription engine + * components. It ensures proper initialization and cleanup of the + * subscription system during application lifecycle. + */ + struct SeHolder final { + using SePtr = std::shared_ptr; + SePtr se_; + + // Disable copying - subscription engine should not be copied + SeHolder(const SeHolder &) = delete; + SeHolder &operator=(const SeHolder &) = delete; + + // Disable moving - subscription engine should not be moved + SeHolder(SeHolder &&) = delete; + SeHolder &operator=(SeHolder &&) = delete; + + /** + * @brief Constructs SeHolder with subscription engine instance + * @param se Shared pointer to subscription engine + */ + SeHolder(SePtr se); + + /** + * @brief Destructor ensures proper cleanup of subscription engine + */ + ~SeHolder(); + }; + + class ApplicationImpl final : public Application { + public: + ApplicationImpl(qtils::SharedRef logsys, + qtils::SharedRef config, + qtils::SharedRef state_manager, + qtils::SharedRef watchdog, + qtils::SharedRef metrics_exposer, + qtils::SharedRef system_clock, + std::shared_ptr); + + void run() override; + + private: + qtils::SharedRef logger_; + qtils::SharedRef app_config_; + qtils::SharedRef state_manager_; + qtils::SharedRef watchdog_; + qtils::SharedRef metrics_exposer_; + qtils::SharedRef system_clock_; + + // Metrics + std::unique_ptr metrics_registry_; + }; + +} // namespace lean::app diff --git a/src/app/impl/chain_spec_impl.cpp b/src/app/impl/chain_spec_impl.cpp new file mode 100644 index 0000000..b3686d8 --- /dev/null +++ b/src/app/impl/chain_spec_impl.cpp @@ -0,0 +1,86 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "app/impl/chain_spec_impl.hpp" + +#include +#include +#include + +#include + +OUTCOME_CPP_DEFINE_CATEGORY(lean::app, ChainSpecImpl::Error, e) { + using E = lean::app::ChainSpecImpl::Error; + switch (e) { + case E::MISSING_ENTRY: + return "A required entry is missing in the config file"; + case E::MISSING_PEER_ID: + return "Peer id is missing in a multiaddress provided in the config file"; + case E::PARSER_ERROR: + return "Internal parser error"; + case E::NOT_IMPLEMENTED: + return "Known entry name, but parsing not implemented"; + } + return "Unknown error in ChainSpecImpl"; +} + +namespace lean::app { + + namespace pt = boost::property_tree; + + ChainSpecImpl::ChainSpecImpl(qtils::SharedRef logsys, + qtils::SharedRef app_config) + : log_(logsys->getLogger("ChainSpec", "application")) { + if (auto res = loadFromJson(app_config->specFile()); res.has_error()) { + SL_CRITICAL(log_, "Can't init chain spec by json-file: {}", res.error()); + qtils::raise(res.error()); + } + } + + outcome::result ChainSpecImpl::loadFromJson( + const std::filesystem::path &file_path) { + pt::ptree tree; + try { + pt::read_json(file_path, tree); + } catch (pt::json_parser_error &e) { + log_->error( + "Parser error: {}, line {}: {}", e.filename(), e.line(), e.message()); + return Error::PARSER_ERROR; + } + + OUTCOME_TRY(loadFields(tree)); + OUTCOME_TRY(loadGenesis(tree)); + OUTCOME_TRY(loadBootNodes(tree)); + + return outcome::success(); + } + + outcome::result ChainSpecImpl::loadFields( + const boost::property_tree::ptree &tree) { + OUTCOME_TRY(id, ensure("id", tree.get_child_optional("id"))); + id_ = id.get(""); + + return outcome::success(); + } + + outcome::result ChainSpecImpl::loadGenesis( + const boost::property_tree::ptree &tree) { + OUTCOME_TRY( + genesis_header_hex, + ensure("genesis_header", tree.get_child_optional("genesis_header"))); + OUTCOME_TRY(genesis_header_encoded, + qtils::ByteVec::fromHex(genesis_header_hex.data())); + genesis_header_ = std::move(genesis_header_encoded); + return outcome::success(); + } + + outcome::result ChainSpecImpl::loadBootNodes( + const boost::property_tree::ptree &tree) { + // TODO Not implemented + return outcome::success(); + } + +} // namespace lean::app diff --git a/src/app/impl/chain_spec_impl.hpp b/src/app/impl/chain_spec_impl.hpp new file mode 100644 index 0000000..02fd429 --- /dev/null +++ b/src/app/impl/chain_spec_impl.hpp @@ -0,0 +1,82 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "app/chain_spec.hpp" +#include "log/logger.hpp" + +namespace lean::app { + class Configuration; +} +namespace lean::log { + class LoggingSystem; +} + +namespace lean::app { + + class ChainSpecImpl : public ChainSpec { + public: + enum class Error { + MISSING_ENTRY = 1, + MISSING_PEER_ID, + PARSER_ERROR, + NOT_IMPLEMENTED + }; + + ChainSpecImpl(qtils::SharedRef logsys, + qtils::SharedRef app_config); + + const std::string &id() const override { + return id_; + } + + const std::vector &bootNodes() const override { + return boot_nodes_; + } + + const qtils::ByteVec &genesisHeader() const override { + return genesis_header_; + } + + const std::map &genesisState() + const override { + return genesis_state_; + } + + private: + outcome::result loadFromJson(const std::filesystem::path &file_path); + outcome::result loadFields(const boost::property_tree::ptree &tree); + outcome::result loadGenesis(const boost::property_tree::ptree &tree); + outcome::result loadBootNodes( + const boost::property_tree::ptree &tree); + + template + outcome::result> ensure(std::string_view entry_name, + boost::optional opt_entry) { + if (not opt_entry) { + log_->error("Required '{}' entry not found in the chain spec", + entry_name); + return Error::MISSING_ENTRY; + } + return opt_entry.value(); + } + + log::Logger log_; + std::string id_; + std::vector boot_nodes_; + qtils::ByteVec genesis_header_; + std::map genesis_state_; + }; + +} // namespace lean::app + +OUTCOME_HPP_DECLARE_ERROR(lean::app, ChainSpecImpl::Error) diff --git a/src/app/impl/state_manager_impl.cpp b/src/app/impl/state_manager_impl.cpp new file mode 100644 index 0000000..148e62c --- /dev/null +++ b/src/app/impl/state_manager_impl.cpp @@ -0,0 +1,284 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "app/impl/state_manager_impl.hpp" + +#include +#include + +#include "log/logger.hpp" + +namespace lean::app { + std::weak_ptr StateManagerImpl::wp_to_myself; + + std::atomic_bool StateManagerImpl::shutting_down_signals_enabled{false}; + + void StateManagerImpl::shuttingDownSignalsEnable() { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = shuttingDownSignalsHandler; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGINT); + sigaddset(&act.sa_mask, SIGTERM); + sigaddset(&act.sa_mask, SIGQUIT); + sigprocmask(SIG_BLOCK, &act.sa_mask, nullptr); + sigaction(SIGINT, &act, nullptr); + sigaction(SIGTERM, &act, nullptr); + sigaction(SIGQUIT, &act, nullptr); + shutting_down_signals_enabled.store(true); + sigprocmask(SIG_UNBLOCK, &act.sa_mask, nullptr); + } + + void StateManagerImpl::shuttingDownSignalsDisable() { + auto expected = true; + if (not shutting_down_signals_enabled.compare_exchange_strong(expected, + false)) { + return; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(SIGINT, &act, nullptr); + sigaction(SIGTERM, &act, nullptr); + sigaction(SIGQUIT, &act, nullptr); + } + + void StateManagerImpl::shuttingDownSignalsHandler(int signal) { + shuttingDownSignalsDisable(); + if (auto self = wp_to_myself.lock()) { + SL_TRACE(self->logger_, "Shutdown signal {} received", signal); + self->shutdown(); + } + } + + std::atomic_bool StateManagerImpl::log_rotate_signals_enabled{false}; + + void StateManagerImpl::logRotateSignalsEnable() { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = logRotateSignalsHandler; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGHUP); + sigprocmask(SIG_BLOCK, &act.sa_mask, nullptr); + sigaction(SIGHUP, &act, nullptr); + log_rotate_signals_enabled.store(true); + sigprocmask(SIG_UNBLOCK, &act.sa_mask, nullptr); + } + + void StateManagerImpl::logRotateSignalsDisable() { + auto expected = true; + if (not log_rotate_signals_enabled.compare_exchange_strong(expected, + false)) { + return; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(SIGHUP, &act, nullptr); + } + + void StateManagerImpl::logRotateSignalsHandler(int signal) { + if (auto self = wp_to_myself.lock()) { + SL_TRACE(self->logger_, "Log rotate signal {} received", signal); + self->logging_system_->doLogRotate(); + } + } + + StateManagerImpl::StateManagerImpl( + qtils::SharedRef logging_system) + : logger_(logging_system->getLogger("StateManager", "application")), + logging_system_(std::move(logging_system)) { + shuttingDownSignalsEnable(); + logRotateSignalsEnable(); + SL_TRACE(logger_, "Signal handlers set up"); + } + + StateManagerImpl::~StateManagerImpl() { + shuttingDownSignalsDisable(); + logRotateSignalsDisable(); + wp_to_myself.reset(); + } + + void StateManagerImpl::reset() { + std::lock_guard lg(mutex_); + + std::queue empty_prepare; + std::swap(prepare_, empty_prepare); + + std::queue empty_launch; + std::swap(launch_, empty_launch); + + std::queue empty_shutdown; + std::swap(shutdown_, empty_shutdown); + + state_ = State::Init; + } + + void StateManagerImpl::atPrepare(OnPrepare &&cb) { + std::lock_guard lg(mutex_); + if (state_ > State::Prepare) { + throw AppStateException("adding callback for stage 'prepare'"); + } + prepare_.emplace(std::move(cb)); + } + + void StateManagerImpl::atLaunch(OnLaunch &&cb) { + std::lock_guard lg(mutex_); + if (state_ > State::Starting) { + throw AppStateException("adding callback for stage 'launch'"); + } + launch_.emplace(std::move(cb)); + } + + void StateManagerImpl::atShutdown(OnShutdown &&cb) { + std::lock_guard lg(mutex_); + if (state_ > State::ShuttingDown) { + throw AppStateException("adding callback for stage 'shutdown'"); + } + shutdown_.emplace(std::move(cb)); + } + + void StateManagerImpl::doPrepare() { + std::lock_guard lg(mutex_); + + auto state = State::Init; + if (not state_.compare_exchange_strong(state, State::Prepare)) { + if (state != State::ShuttingDown) { + throw AppStateException("running stage 'preparing'"); + } + } + + if (not prepare_.empty()) { + SL_TRACE(logger_, "Running stage 'preparing'…"); + } + + while (!prepare_.empty()) { + auto &cb = prepare_.front(); + if (state_ == State::Prepare) { + auto success = cb(); + if (not success) { + SL_ERROR(logger_, "Stage 'preparing' is failed"); + state = State::Prepare; + state_.compare_exchange_strong(state, State::ShuttingDown); + } + } + prepare_.pop(); + } + + state = State::Prepare; + state_.compare_exchange_strong(state, State::ReadyToStart); + } + + void StateManagerImpl::doLaunch() { + std::lock_guard lg(mutex_); + + auto state = State::ReadyToStart; + if (not state_.compare_exchange_strong(state, State::Starting)) { + if (state != State::ShuttingDown) { + throw AppStateException("running stage 'launch'"); + } + } + + if (not launch_.empty()) { + SL_TRACE(logger_, "Running stage 'launch'…"); + } + + while (!launch_.empty()) { + auto &cb = launch_.front(); + if (state_.load() == State::Starting) { + auto success = cb(); + if (not success) { + SL_ERROR(logger_, "Stage 'launch' is failed"); + state = State::Starting; + state_.compare_exchange_strong(state, State::ShuttingDown); + } + } + launch_.pop(); + } + + state = State::Starting; + state_.compare_exchange_strong(state, State::Works); + } + + void StateManagerImpl::doShutdown() { + std::lock_guard lg(mutex_); + + auto state = State::Works; + if (not state_.compare_exchange_strong(state, State::ShuttingDown)) { + if (state != State::ShuttingDown) { + throw AppStateException("running stage 'shutting down'"); + } + } + + std::queue empty_prepare; + std::swap(prepare_, empty_prepare); + + std::queue empty_launch; + std::swap(launch_, empty_launch); + + while (!shutdown_.empty()) { + auto &cb = shutdown_.front(); + cb(); + shutdown_.pop(); + } + + state = State::ShuttingDown; + state_.compare_exchange_strong(state, State::ReadyToStop); + } + + void StateManagerImpl::run() { + wp_to_myself = weak_from_this(); + if (wp_to_myself.expired()) { + throw std::logic_error( + "StateManager must be instantiated on shared pointer before run"); + } + + doPrepare(); + + doLaunch(); + + if (state_.load() == State::Works) { + SL_TRACE(logger_, "All components started; waiting shutdown request…"); + shutdownRequestWaiting(); + } + + SL_TRACE(logger_, "Start doing shutdown…"); + doShutdown(); + SL_TRACE(logger_, "Shutdown is done"); + + if (state_.load() != State::ReadyToStop) { + throw std::logic_error( + "StateManager is expected in stage 'ready to stop'"); + } + } + + void StateManagerImpl::shutdownRequestWaiting() { + std::unique_lock lock(cv_mutex_); + cv_.wait(lock, [&] { return state_ == State::ShuttingDown; }); + } + + void StateManagerImpl::shutdown() { + shuttingDownSignalsDisable(); + if (state_.load() == State::ReadyToStop) { + SL_TRACE(logger_, "Shutting down requested, but app is ready to stop"); + return; + } + + if (state_.load() == State::ShuttingDown) { + SL_TRACE(logger_, "Shutting down requested, but it's in progress"); + return; + } + + SL_TRACE(logger_, "Shutting down requested…"); + std::lock_guard lg(cv_mutex_); + state_.store(State::ShuttingDown); + cv_.notify_one(); + } +} // namespace lean::app diff --git a/src/app/impl/state_manager_impl.hpp b/src/app/impl/state_manager_impl.hpp new file mode 100644 index 0000000..0004812 --- /dev/null +++ b/src/app/impl/state_manager_impl.hpp @@ -0,0 +1,84 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include + +#include "app/state_manager.hpp" +#include "utils/ctor_limiters.hpp" + +namespace soralog { + class Logger; +} // namespace soralog +namespace lean::log { + class LoggingSystem; +} // namespace lean::log + +namespace lean::app { + + class StateManagerImpl // left non-final on purpose to be accessible in tests + : Singleton, + public StateManager, + public std::enable_shared_from_this { + public: + StateManagerImpl(qtils::SharedRef logging_system); + + ~StateManagerImpl() override; + + void atPrepare(OnPrepare &&cb) override; + void atLaunch(OnLaunch &&cb) override; + void atShutdown(OnShutdown &&cb) override; + + void run() override; + void shutdown() override; + + State state() const override { + return state_; + } + + protected: + void reset(); + + void doPrepare() override; + void doLaunch() override; + void doShutdown() override; + + private: + static std::weak_ptr wp_to_myself; + + static std::atomic_bool shutting_down_signals_enabled; + static void shuttingDownSignalsEnable(); + static void shuttingDownSignalsDisable(); + static void shuttingDownSignalsHandler(int); + + static std::atomic_bool log_rotate_signals_enabled; + static void logRotateSignalsEnable(); + static void logRotateSignalsDisable(); + static void logRotateSignalsHandler(int); + + void shutdownRequestWaiting(); + + qtils::SharedRef logger_; + qtils::SharedRef logging_system_; + + std::atomic state_ = State::Init; + + std::recursive_mutex mutex_; + + std::mutex cv_mutex_; + std::condition_variable cv_; + + std::queue prepare_; + std::queue launch_; + std::queue shutdown_; + }; + +} // namespace lean::app diff --git a/src/app/impl/watchdog.hpp b/src/app/impl/watchdog.hpp new file mode 100644 index 0000000..a4bd833 --- /dev/null +++ b/src/app/impl/watchdog.hpp @@ -0,0 +1,186 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "app/configuration.hpp" +#include "injector/dont_inject.hpp" +#include "log/logger.hpp" + +namespace soralog { + class Logger; +} // namespace soralog + +namespace lean::log { + class LoggingSystem; +} // namespace lean::log + +#ifdef __APPLE__ + +#include +#include +#include + +namespace { + inline uint64_t getPlatformThreadId() { + thread_identifier_info_data_t info; + mach_msg_type_number_t size = THREAD_IDENTIFIER_INFO_COUNT; + auto r = thread_info(mach_thread_self(), + THREAD_IDENTIFIER_INFO, + (thread_info_t)&info, + &size); + if (r != KERN_SUCCESS) { + throw std::logic_error{"thread_info"}; + } + return info.thread_id; + } +} // namespace + +#else + +#include + +#include + +inline uint64_t getPlatformThreadId() { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + return syscall(SYS_gettid); +} + +#endif + +namespace lean { + + constexpr auto kWatchdogDefaultTimeout = std::chrono::minutes{15}; + + class Watchdog { + public: + using Count = uint32_t; + using Atomic = std::atomic; + using Clock = std::chrono::steady_clock; + using Timeout = std::chrono::seconds; + + Watchdog(std::shared_ptr logsys, + std::shared_ptr config) + : logger_(logsys->getLogger("Watchdog", "threads")), + granularity_(1 /*config->granularity()*/) { + // BOOST_ASSERT(granularity != granularity.zero()); + } + + struct Ping { + std::shared_ptr count_; + + void operator()() const { + count_->fetch_add(1); + } + }; + + void checkLoop(Timeout timeout) { + // or `io_context` with timer + while (not stopped_) { + std::this_thread::sleep_for(granularity_); + check(timeout); + } + } + + // periodic check + void check(const Timeout timeout) { + std::unique_lock lock{mutex_}; + const auto now = Clock::now(); + for (auto it = threads_.begin(); it != threads_.end();) { + if (it->second.count.use_count() == 1) { + it = threads_.erase(it); + } else { + auto count = it->second.count->load(); + if (it->second.last_count != count) { + it->second.last_count = count; + it->second.last_time = now; + } else { + auto lag = now - it->second.last_time; + if (lag > timeout) { + std::stringstream s; + s << it->first; + SL_CRITICAL(logger_, + "ALERT Watchdog: " + "thread id={}, platform_id={}, name={} timeout\n", + s.str(), + it->second.platform_id, + it->second.name); + std::abort(); + } + } + ++it; + } + } + } + + [[nodiscard]] Ping add() { + std::unique_lock lock{mutex_}; + auto &thread = threads_[std::this_thread::get_id()]; + if (not thread.count) { + thread = {.last_time = Clock::now(), + .last_count = 0, + .count = std::make_shared(), + .platform_id = getPlatformThreadId(), + .name = soralog::util::getThreadName()}; + } + return Ping{thread.count}; + } + + void run(std::shared_ptr io) { + auto ping = add(); + while (not stopped_ and io.use_count() != 1) { +#define WAIT_FOR_BETTER_BOOST_IMPLEMENTATION +#ifndef WAIT_FOR_BETTER_BOOST_IMPLEMENTATION + // this is the desired implementation + // cannot be used rn due to the method visibility settings + // bug(boost): wait_one is private + boost::system::error_code ec; + io->impl_.wait_one(TODO, ec); + ping(); + io->poll_one(); +#else + // `run_one_for` run time is sum of `wait_one(time)` and `poll_one` + // may cause false-positive timeout + io->run_one_for(granularity_); +#endif + ping(); + io->restart(); + } + } + + void stop() { + stopped_ = true; + } + + private: + struct Thread { + Clock::time_point last_time; + Count last_count = 0; + std::shared_ptr count; + uint64_t platform_id; + std::string name; + }; + + std::shared_ptr logger_; + std::chrono::milliseconds granularity_; + std::mutex mutex_; + std::unordered_map threads_; + std::atomic_bool stopped_ = false; + }; +} // namespace lean diff --git a/src/app/state_manager.hpp b/src/app/state_manager.hpp new file mode 100644 index 0000000..f096254 --- /dev/null +++ b/src/app/state_manager.hpp @@ -0,0 +1,130 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include + +namespace lean::app { + + // Concepts that check if an object has a method that is called by app state + // manager. Deliberately avoid checking that the method returns bool, + // because if there's a method with an appropriate name, and it doesn't return + // bool, we want it to be a compile error instead of silently ignoring it + // because the concept is not satisfied. + template + concept AppStatePreparable = requires(T &t) { t.prepare(); }; + template + concept AppStateStartable = requires(T &t) { t.start(); }; + template + concept AppStateStoppable = requires(T &t) { t.stop(); }; + + // if an object is registered with AppStateManager but has no method + // that is called by AppStateManager, there's probably something wrong + template + concept AppStateControllable = + AppStatePreparable || AppStateStoppable || AppStateStartable; + + template + concept ActionRetBool = std::same_as, bool>; + + template + concept ActionRetVoid = std::is_void_v>; + + class Action { + public: + Action(ActionRetBool auto &&f) + : f_([f = std::move(f)]() mutable { return f(); }) {} + + Action(ActionRetVoid auto &&f) + : f_([f = std::move(f)]() mutable { return f(), true; }) {} + + bool operator()() { + return f_(); + } + + private: + std::function f_; + }; + + class StateManager { + public: + using OnPrepare = Action; + using OnLaunch = Action; + using OnShutdown = Action; + + enum class State : uint8_t { + Init, + Prepare, + ReadyToStart, + Starting, + Works, + ShuttingDown, + ReadyToStop, + }; + + virtual ~StateManager() = default; + + /** + * @brief Execute {@param cb} at stage 'preparations' of application + * @param cb + */ + virtual void atPrepare(OnPrepare &&cb) = 0; + + /** + * @brief Execute {@param cb} immediately before start application + * @param cb + */ + virtual void atLaunch(OnLaunch &&cb) = 0; + + /** + * @brief Execute {@param cb} at stage of shutting down application + * @param cb + */ + virtual void atShutdown(OnShutdown &&cb) = 0; + + public: + /** + * @brief Registration special methods (if any) of object as handlers + * for stages of application life-cycle + * @param entity is registered entity + */ + template + void takeControl(Controlled &entity) { + if constexpr (AppStatePreparable) { + atPrepare([&entity] { return entity.prepare(); }); + } + if constexpr (AppStateStartable) { + atLaunch([&entity] { return entity.start(); }); + } + if constexpr (AppStateStoppable) { + atShutdown([&entity] { return entity.stop(); }); + } + } + + /// Start application life cycle + virtual void run() = 0; + + /// Initiate shutting down (at any time) + virtual void shutdown() = 0; + + /// Get current stage + virtual State state() const = 0; + + protected: + virtual void doPrepare() = 0; + virtual void doLaunch() = 0; + virtual void doShutdown() = 0; + }; + + struct AppStateException : public std::runtime_error { + explicit AppStateException(std::string message) + : std::runtime_error("Wrong workflow at " + std::move(message)) {} + }; +} // namespace lean::app diff --git a/src/blockchain/CMakeLists.txt b/src/blockchain/CMakeLists.txt new file mode 100644 index 0000000..6f7999e --- /dev/null +++ b/src/blockchain/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(blockchain + impl/storage_util.cpp + impl/block_storage_impl.cpp + impl/block_storage_error.cpp + impl/block_storage_initializer.cpp +) +target_link_libraries(blockchain + Boost::boost + sszpp +) diff --git a/src/blockchain/block_storage.hpp b/src/blockchain/block_storage.hpp new file mode 100644 index 0000000..b01a25f --- /dev/null +++ b/src/blockchain/block_storage.hpp @@ -0,0 +1,176 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "lean_types/block.hpp" +#include "lean_types/block_body.hpp" +#include "lean_types/block_data.hpp" +#include "lean_types/block_header.hpp" +#include "lean_types/justification.hpp" +#include "lean_types/signed_block.hpp" +#include "lean_types/types.hpp" + +namespace lean::blockchain { + + /** + * A wrapper for a storage of blocks + * Provides a convenient interface to work with it + */ + class BlockStorage { + public: + virtual ~BlockStorage() = default; + + /** + * Gets leaves of a block tree + * @returns hashes of block tree leaves + */ + [[nodiscard]] virtual outcome::result> + getBlockTreeLeaves() const = 0; + + /** + * Saves provided block tree leaves + * @returns result of saving + */ + virtual outcome::result setBlockTreeLeaves( + std::vector leaves) = 0; + + /** + * Get the last finalized block + * @return BlockInfo of the block + */ + [[nodiscard]] virtual outcome::result getLastFinalized() + const = 0; + + // -- hash -- + + /** + * Adds slot-to-hash record for the provided block index to block + * storage + * @returns success or failure + */ + virtual outcome::result assignHashToSlot( + const BlockIndex &block_index) = 0; + + /** + * Removes slot-to-hash record for provided block index from block storage + * @returns success or failure + */ + virtual outcome::result deassignHashToSlot( + const BlockIndex &block_index) = 0; + + /** + * Tries to get block hashes by slot + * @returns vector of hashes or error + */ + [[nodiscard]] virtual outcome::result> getBlockHash( + BlockNumber slot) const = 0; + + // -- headers -- + + /** + * Check if header existing by provided block {@param block_hash} + * @returns result or error + */ + [[nodiscard]] virtual outcome::result hasBlockHeader( + const BlockHash &block_hash) const = 0; + + /** + * Saves block header to block storage + * @returns hash of saved header or error + */ + virtual outcome::result putBlockHeader( + const BlockHeader &header) = 0; + + /** + * Tries to get a block header by hash + * @returns block header or error + */ + [[nodiscard]] virtual outcome::result getBlockHeader( + const BlockHash &block_hash) const = 0; + + /** + * Attempts to retrieve the block header for the given hash}. + * @param block_hash The hash of the block whose header is to be retrieved. + * @returns An optional containing the block header if found, std::nullopt + * if not found, or an error if the operation fails. + */ + [[nodiscard]] virtual outcome::result> + tryGetBlockHeader(const BlockHash &block_hash) const = 0; + + // -- body -- + + /** + * Saves provided body of block to block storage + * @returns result of saving + */ + virtual outcome::result putBlockBody(const BlockHash &block_hash, + const BlockBody &block_body) = 0; + + /** + * Tries to get block body + * @returns block body or error + */ + [[nodiscard]] virtual outcome::result> + getBlockBody(const BlockHash &block_hash) const = 0; + + /** + * Removes body of block with hash {@param block_hash} from block storage + * @returns result of saving + */ + virtual outcome::result removeBlockBody( + const BlockHash &block_hash) = 0; + + // -- justification -- + + /** + * Saves {@param justification} of block with hash {@param block_hash} to + * block storage + * @returns result of saving + */ + virtual outcome::result putJustification( + const Justification &justification, const BlockHash &block_hash) = 0; + + /** + * Tries to get justification of block finality by {@param block_hash} + * @returns justification or error + */ + virtual outcome::result> getJustification( + const BlockHash &block_hash) const = 0; + + /** + * Removes justification of block with hash {@param block_hash} from block + * storage + * @returns result of saving + */ + virtual outcome::result removeJustification( + const BlockHash &block_hash) = 0; + + // -- combined + + /** + * Saves block to block storage + * @returns hash of saved header or error + */ + virtual outcome::result putBlock(const BlockData &block) = 0; + + /** + * Tries to get block data + * @returns block data or error + */ + [[nodiscard]] virtual outcome::result> getBlock( + const BlockHash &block_hash) const = 0; + + /** + * Removes all data of block by hash from block storage + * @returns nothing or error + */ + virtual outcome::result removeBlock(const BlockHash &block_hash) = 0; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/block_storage_error.hpp b/src/blockchain/block_storage_error.hpp new file mode 100644 index 0000000..bc94ab5 --- /dev/null +++ b/src/blockchain/block_storage_error.hpp @@ -0,0 +1,26 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +namespace lean::blockchain { + + enum class BlockStorageError : uint8_t { + BLOCK_EXISTS = 1, + HEADER_NOT_FOUND, + GENESIS_BLOCK_ALREADY_EXISTS, + GENESIS_BLOCK_NOT_FOUND, + FINALIZED_BLOCK_NOT_FOUND, + BLOCK_TREE_LEAVES_NOT_FOUND + }; + +} + +OUTCOME_HPP_DECLARE_ERROR(lean::blockchain, BlockStorageError); diff --git a/src/blockchain/genesis_block_header.hpp b/src/blockchain/genesis_block_header.hpp new file mode 100644 index 0000000..6f22537 --- /dev/null +++ b/src/blockchain/genesis_block_header.hpp @@ -0,0 +1,18 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "lean_types/block_header.hpp" + +namespace lean::blockchain { + + class GenesisBlockHeader : public BlockHeader { + public: + using BlockHeader::BlockHeader; + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_storage_error.cpp b/src/blockchain/impl/block_storage_error.cpp new file mode 100644 index 0000000..a958fcd --- /dev/null +++ b/src/blockchain/impl/block_storage_error.cpp @@ -0,0 +1,26 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/block_storage_error.hpp" + +OUTCOME_CPP_DEFINE_CATEGORY(lean::blockchain, BlockStorageError, e) { + using E = BlockStorageError; + switch (e) { + case E::BLOCK_EXISTS: + return "Block already exists on the chain"; + case E::HEADER_NOT_FOUND: + return "Block header was not found"; + case E::GENESIS_BLOCK_ALREADY_EXISTS: + return "Genesis block already exists"; + case E::FINALIZED_BLOCK_NOT_FOUND: + return "Finalized block not found. Possibly storage is corrupted"; + case E::GENESIS_BLOCK_NOT_FOUND: + return "Genesis block not found"; + case E::BLOCK_TREE_LEAVES_NOT_FOUND: + return "Block tree leaves not found"; + } + return "Unknown error"; +} diff --git a/src/blockchain/impl/block_storage_impl.cpp b/src/blockchain/impl/block_storage_impl.cpp new file mode 100644 index 0000000..96f6a99 --- /dev/null +++ b/src/blockchain/impl/block_storage_impl.cpp @@ -0,0 +1,367 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/impl/block_storage_impl.hpp" + +#include + +#include "blockchain/block_storage_error.hpp" +#include "blockchain/impl/storage_util.hpp" +#include "lean_types/block_data.hpp" +#include "sszpp/ssz++.hpp" +#include "storage/predefined_keys.hpp" + +namespace lean::blockchain { + + BlockStorageImpl::BlockStorageImpl( + qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef hasher, + std::shared_ptr) + : logger_(logsys->getLogger("BlockStorage", "block_storage")), + storage_(std::move(storage)), + hasher_(std::move(hasher)) {} + + outcome::result> BlockStorageImpl::getBlockTreeLeaves() + const { + if (block_tree_leaves_.has_value()) { + return block_tree_leaves_.value(); + } + + auto default_space = storage_->getSpace(storage::Space::Default); + OUTCOME_TRY(leaves_opt, + default_space->tryGet(storage::kBlockTreeLeavesLookupKey)); + if (not leaves_opt.has_value()) { + return BlockStorageError::BLOCK_TREE_LEAVES_NOT_FOUND; + } + auto &encoded_leaves = leaves_opt.value(); + + OUTCOME_TRY(leaves, decode>(encoded_leaves)); + + block_tree_leaves_.emplace(std::move(leaves)); + + return block_tree_leaves_.value(); + } + + outcome::result BlockStorageImpl::setBlockTreeLeaves( + std::vector leaves) { + std::ranges::sort(leaves); + + if (block_tree_leaves_.has_value() + and block_tree_leaves_.value() == leaves) { + return outcome::success(); + } + + auto default_space = storage_->getSpace(storage::Space::Default); + OUTCOME_TRY(encoded_leaves, encode(leaves)); + OUTCOME_TRY(default_space->put(storage::kBlockTreeLeavesLookupKey, + qtils::ByteVec{std::move(encoded_leaves)})); + + block_tree_leaves_.emplace(std::move(leaves)); + + return outcome::success(); + } + + outcome::result BlockStorageImpl::getLastFinalized() const { + OUTCOME_TRY(leaves, getBlockTreeLeaves()); + auto current_hash = leaves[0]; + for (;;) { + // OUTCOME_TRY(j_opt, getJustification(current_hash)); + // if (j_opt.has_value()) { + // break; + // } // FIXME + OUTCOME_TRY(header, getBlockHeader(current_hash)); + if (header.slot == 0) { + SL_TRACE(logger_, + "Not found block with justification. " + "Genesis block will be used as last finalized ({})", + current_hash); + return {0, current_hash}; // genesis + } + current_hash = header.parent_root; + } + + OUTCOME_TRY(header, getBlockHeader(current_hash)); + auto found_block = BlockIndex{header.slot, current_hash}; + SL_TRACE(logger_, + "Justification is found in block {}. " + "This block will be used as last finalized", + found_block); + return found_block; + } + + outcome::result BlockStorageImpl::assignHashToSlot( + const BlockInfo &block_index) { + SL_DEBUG(logger_, "Add slot-to-hash for {}", block_index); + auto slot_to_hash_key = slotToHashLookupKey(block_index.slot); + auto storage = storage_->getSpace(storage::Space::LookupKey); + OUTCOME_TRY(hashes, getBlockHash(block_index.slot)); + if (not qtils::cxx23::ranges::contains(hashes, block_index.hash)) { + hashes.emplace_back(block_index.hash); + OUTCOME_TRY(storage->put(slot_to_hash_key, block_index.hash)); + } + return outcome::success(); + } + + outcome::result BlockStorageImpl::deassignHashToSlot( + const BlockIndex &block_index) { + SL_DEBUG(logger_, "Remove num-to-idx for {}", block_index); + auto slot_to_hash_key = slotToHashLookupKey(block_index.slot); + auto storage = storage_->getSpace(storage::Space::LookupKey); + OUTCOME_TRY(hashes, getBlockHash(block_index.slot)); + auto to_erase = std::ranges::remove(hashes, block_index.hash); + if (not to_erase.empty()) { + hashes.erase(to_erase.begin(), to_erase.end()); + if (hashes.empty()) { + OUTCOME_TRY(storage->remove(slot_to_hash_key)); + } else { + OUTCOME_TRY(storage->put(slot_to_hash_key, encode(hashes).value())); + } + } + return outcome::success(); + } + + outcome::result> BlockStorageImpl::getBlockHash( + Slot slot) const { + auto storage = storage_->getSpace(storage::Space::LookupKey); + OUTCOME_TRY(data_opt, storage->tryGet(slotToHashLookupKey(slot))); + if (data_opt.has_value()) { + return decode>(data_opt.value()); + } + return {{}}; + } + + // outcome::result> + // BlockStorageImpl::getBlockHash(const BlockId &block_id) const + // { + // return visit_in_place( + // block_id, + // [&](const BlockNumber &block_number) + // -> outcome::result> { + // auto key_space = storage_->getSpace(storage::Space::kLookupKey); + // OUTCOME_TRY(data_opt, + // key_space->tryGet(slotToHashLookupKey(block_number))); + // if (data_opt.has_value()) { + // OUTCOME_TRY(block_hash, + // BlockHash::fromSpan(data_opt.value())); + // return block_hash; + // } + // return std::nullopt; + // }, + // [](const Hash256 &block_hash) { return block_hash; }); + // } + + outcome::result BlockStorageImpl::hasBlockHeader( + const BlockHash &block_hash) const { + return hasInSpace(*storage_, storage::Space::Header, block_hash); + } + + outcome::result BlockStorageImpl::putBlockHeader( + const BlockHeader &header) { + OUTCOME_TRY(encoded_header, encode(header)); + header.updateHash(*hasher_); + const auto &block_hash = header.hash(); + OUTCOME_TRY(putToSpace(*storage_, + storage::Space::Header, + block_hash, + std::move(encoded_header))); + return block_hash; + } + + outcome::result BlockStorageImpl::getBlockHeader( + const BlockHash &block_hash) const { + OUTCOME_TRY(header_opt, fetchBlockHeader(block_hash)); + if (header_opt.has_value()) { + return header_opt.value(); + } + return BlockStorageError::HEADER_NOT_FOUND; + } + + outcome::result> + BlockStorageImpl::tryGetBlockHeader(const BlockHash &block_hash) const { + return fetchBlockHeader(block_hash); + } + + outcome::result BlockStorageImpl::putBlockBody( + const BlockHash &block_hash, const BlockBody &block_body) { + OUTCOME_TRY(encoded_body, encode(block_body)); + return putToSpace( + *storage_, storage::Space::Body, block_hash, std::move(encoded_body)); + } + + outcome::result> BlockStorageImpl::getBlockBody( + const BlockHash &block_hash) const { + OUTCOME_TRY(encoded_block_body_opt, + getFromSpace(*storage_, storage::Space::Body, block_hash)); + if (encoded_block_body_opt.has_value()) { + OUTCOME_TRY(block_body, + decode(encoded_block_body_opt.value())); + return std::make_optional(std::move(block_body)); + } + return std::nullopt; + } + + outcome::result BlockStorageImpl::removeBlockBody( + const BlockHash &block_hash) { + auto space = storage_->getSpace(storage::Space::Body); + return space->remove(block_hash); + } + + outcome::result BlockStorageImpl::putJustification( + const Justification &justification, const BlockHash &hash) { + BOOST_ASSERT(not justification.empty()); + + OUTCOME_TRY(encoded_justification, encode(justification)); + OUTCOME_TRY(putToSpace(*storage_, + storage::Space::Justification, + hash, + std::move(encoded_justification))); + + return outcome::success(); + } + + outcome::result> + BlockStorageImpl::getJustification(const BlockHash &block_hash) const { + OUTCOME_TRY( + encoded_justification_opt, + getFromSpace(*storage_, storage::Space::Justification, block_hash)); + if (encoded_justification_opt.has_value()) { + OUTCOME_TRY(justification, + decode(encoded_justification_opt.value())); + return justification; + } + return std::nullopt; + } + + outcome::result BlockStorageImpl::removeJustification( + const BlockHash &block_hash) { + auto space = storage_->getSpace(storage::Space::Justification); + return space->remove(block_hash); + } + + outcome::result BlockStorageImpl::putBlock( + const BlockData &block) { + // insert provided block's parts into the database + OUTCOME_TRY(block_hash, putBlockHeader(block.header.value())); + + OUTCOME_TRY(encoded_header, encode(block.header)); + OUTCOME_TRY(putToSpace(*storage_, + storage::Space::Header, + block_hash, + std::move(encoded_header))); + + OUTCOME_TRY(encoded_body, encode(block.body)); + OUTCOME_TRY(putToSpace( + *storage_, storage::Space::Body, block_hash, std::move(encoded_body))); + + logger_->info("Added block {} as child of {}", + BlockIndex{block.header->slot, block_hash}, + block.header->parent_root); + return BlockHash{}; // block_hash; + } + + outcome::result> BlockStorageImpl::getBlock( + const BlockHash &block_hash) const { + SignedBlock block_data{ + // .hash = block_hash + }; + + // // Block header + // OUTCOME_TRY(header, getBlockHeader(block_hash)); + // block_data.header = std::move(header); + // + // // Block body + // OUTCOME_TRY(body_opt, getBlockBody(block_hash)); + // block_data.extrinsic = std::move(body_opt); + // + // // // Justification + // OUTCOME_TRY(justification_opt, getJustification(block_hash)); + // block_data.justification = std::move(justification_opt); + + return block_data; + } + + outcome::result BlockStorageImpl::removeBlock( + const BlockHash &block_hash) { + // Check if block still in storage + OUTCOME_TRY(header_opt, fetchBlockHeader(block_hash)); + if (not header_opt) { + return outcome::success(); + } + const auto &header = header_opt.value(); + + auto block_index = header.index(); + + SL_TRACE(logger_, "Removing block {}…", block_index); + + { // Remove slot-to-hash assigning + auto num_to_hash_key = slotToHashLookupKey(block_index.slot); + + auto key_space = storage_->getSpace(storage::Space::LookupKey); + OUTCOME_TRY(hash_opt, key_space->tryGet(num_to_hash_key.view())); + if (hash_opt == block_hash) { + if (auto res = key_space->remove(num_to_hash_key); res.has_error()) { + SL_ERROR(logger_, + "could not remove slot-to-hash of {} from the storage: {}", + block_index, + res.error()); + return res; + } + SL_DEBUG(logger_, "Removed slot-to-hash of {}", block_index); + } + } + + // TODO(xDimon): needed to clean up trie storage if block deleted + + // Remove the block body + if (auto res = removeBlockBody(block_index.hash); res.has_error()) { + SL_ERROR(logger_, + "could not remove body of block {} from the storage: {}", + block_index, + res.error()); + return res; + } + + // Remove justification for a block + if (auto res = removeJustification(block_index.hash); res.has_error()) { + SL_ERROR( + logger_, + "could not remove justification of block {} from the storage: {}", + block_index, + res.error()); + return res; + } + + { // Remove the block header + auto header_space = storage_->getSpace(storage::Space::Header); + if (auto res = header_space->remove(block_index.hash); res.has_error()) { + SL_ERROR(logger_, + "could not remove header of block {} from the storage: {}", + block_index, + res.error()); + return res; + } + } + + logger_->info("Removed block {}", block_index); + + return outcome::success(); + } + + outcome::result> + BlockStorageImpl::fetchBlockHeader(const BlockHash &block_hash) const { + OUTCOME_TRY(encoded_header_opt, + getFromSpace(*storage_, storage::Space::Header, block_hash)); + if (encoded_header_opt.has_value()) { + auto &encoded_header = encoded_header_opt.value(); + OUTCOME_TRY(header, decode(encoded_header)); + header.hash_opt.emplace(block_hash); + return std::make_optional(std::move(header)); + } + return std::nullopt; + } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_storage_impl.hpp b/src/blockchain/impl/block_storage_impl.hpp new file mode 100644 index 0000000..ea0f0d4 --- /dev/null +++ b/src/blockchain/impl/block_storage_impl.hpp @@ -0,0 +1,101 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "blockchain/block_storage.hpp" +#include "blockchain/impl/block_storage_initializer.hpp" +#include "log/logger.hpp" +#include "storage/spaced_storage.hpp" + +namespace lean::blockchain { + + class BlockStorageImpl : public BlockStorage, Singleton { + public: + friend class BlockStorageInitializer; + + BlockStorageImpl(qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef hasher, + std::shared_ptr); + + ~BlockStorageImpl() override = default; + + outcome::result setBlockTreeLeaves( + std::vector leaves) override; + + outcome::result> getBlockTreeLeaves() const override; + + outcome::result getLastFinalized() const override; + + // -- hash -- + + outcome::result assignHashToSlot(const BlockIndex &block) override; + + outcome::result deassignHashToSlot( + const BlockIndex &block_index) override; + + outcome::result> getBlockHash( + BlockNumber slot) const override; + + // -- header -- + + outcome::result hasBlockHeader( + const BlockHash &block_hash) const override; + + outcome::result putBlockHeader( + const BlockHeader &header) override; + + outcome::result getBlockHeader( + const BlockHash &block_hash) const override; + + outcome::result> tryGetBlockHeader( + const BlockHash &block_hash) const override; + + // -- body -- + + outcome::result putBlockBody(const BlockHash &block_hash, + const BlockBody &block_body) override; + + outcome::result> getBlockBody( + const BlockHash &block_hash) const override; + + outcome::result removeBlockBody(const BlockHash &block_hash) override; + + // -- justification -- + + outcome::result putJustification( + const Justification &justification, + const BlockHash &block_hash) override; + + outcome::result> getJustification( + const BlockHash &block_hash) const override; + + outcome::result removeJustification( + const BlockHash &block_hash) override; + + // -- combined + + outcome::result putBlock(const BlockData &block) override; + + outcome::result> getBlock( + const BlockHash &block_hash) const override; + + outcome::result removeBlock(const BlockHash &block_hash) override; + + private: + outcome::result> fetchBlockHeader( + const BlockHash &block_hash) const; + + log::Logger logger_; + + qtils::SharedRef storage_; + + std::shared_ptr hasher_; + + mutable std::optional> block_tree_leaves_; + }; +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_storage_initializer.cpp b/src/blockchain/impl/block_storage_initializer.cpp new file mode 100644 index 0000000..78fa7e9 --- /dev/null +++ b/src/blockchain/impl/block_storage_initializer.cpp @@ -0,0 +1,84 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "blockchain/impl/block_storage_initializer.hpp" + +#include +#include +#include + +#include "blockchain/block_storage_error.hpp" +#include "blockchain/genesis_block_header.hpp" +#include "blockchain/impl/block_storage_impl.hpp" +#include "blockchain/impl/storage_util.hpp" +#include "lean_types/block.hpp" + +namespace lean::blockchain { + + BlockStorageInitializer::BlockStorageInitializer( + qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef genesis_header, + qtils::SharedRef chain_spec, + qtils::SharedRef hasher) { + // temporary instance of block storage + BlockStorageImpl block_storage(std::move(logsys), storage, hasher, {}); + + auto genesis_block_hash = genesis_header->hash(); + + // Try to get genesis header from storage + auto genesis_block_existing_res = + block_storage.hasBlockHeader(genesis_block_hash); + if (genesis_block_existing_res.has_error()) { + block_storage.logger_->critical( + "Database error at check existing genesis block: {}", + genesis_block_existing_res.error()); + qtils::raise(genesis_block_existing_res.error()); + } + auto genesis_header_is_exist = genesis_block_existing_res.value(); + + if (not genesis_header_is_exist) { + // genesis block initialization + BlockData genesis_block{ + .header = *genesis_header, + }; + + auto res = block_storage.putBlock(genesis_block); + if (res.has_error()) { + block_storage.logger_->critical( + "Database error at store genesis block into: {}", res.error()); + qtils::raise(res.error()); + } + BOOST_ASSERT(genesis_block_hash == res.value()); + + auto assignment_res = + block_storage.assignHashToSlot(genesis_header->index()); + if (assignment_res.has_error()) { + block_storage.logger_->critical( + "Database error at assigning genesis block hash: {}", + assignment_res.error()); + qtils::raise(assignment_res.error()); + } + + auto sel_leaves_res = + block_storage.setBlockTreeLeaves({genesis_header->hash()}); + if (sel_leaves_res.has_error()) { + block_storage.logger_->critical( + "Database error at set genesis block as leaf: {}", + sel_leaves_res.error()); + qtils::raise(sel_leaves_res.error()); + } + + // TODO Save genesis state here + + block_storage.logger_->info("Genesis block {}, state {}", + genesis_block_hash, + genesis_header->state_root); + } + } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/block_storage_initializer.hpp b/src/blockchain/impl/block_storage_initializer.hpp new file mode 100644 index 0000000..688d07f --- /dev/null +++ b/src/blockchain/impl/block_storage_initializer.hpp @@ -0,0 +1,40 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace lean::log { + class LoggingSystem; +} +namespace lean::storage { + class SpacedStorage; +} +namespace lean::blockchain { + class GenesisBlockHeader; +} +namespace lean::app { + class ChainSpec; +} +namespace lean::crypto { + class Hasher; +} + +namespace lean::blockchain { + + class BlockStorageInitializer final : Singleton { + public: + BlockStorageInitializer(qtils::SharedRef logsys, + qtils::SharedRef storage, + qtils::SharedRef genesis_header, + qtils::SharedRef chain_spec, + qtils::SharedRef hasher); + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/genesis_block_header_impl.cpp b/src/blockchain/impl/genesis_block_header_impl.cpp new file mode 100644 index 0000000..8559ff0 --- /dev/null +++ b/src/blockchain/impl/genesis_block_header_impl.cpp @@ -0,0 +1,35 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "blockchain/impl/genesis_block_header_impl.hpp" + +#include "app/chain_spec.hpp" +#include "crypto/hasher.hpp" +#include "log/logger.hpp" +#include "sszpp/ssz++.hpp" + +namespace lean::blockchain { + + GenesisBlockHeaderImpl::GenesisBlockHeaderImpl( + const qtils::SharedRef &logsys, + const qtils::SharedRef &chain_spec, + const qtils::SharedRef &hasher) { + scale::impl::memory::DecoderFromSpan decoder(chain_spec->genesisHeader(), + test_vectors::config::tiny); + try { + decode(static_cast(*this), decoder); + } catch (std::system_error &e) { + auto logger = logsys->getLogger("ChainSpec", "application"); + SL_CRITICAL(logger, + "Failed to decode genesis block header from chain spec: {}", + e.code()); + qtils::raise(e.code()); + } + updateHash(*hasher); + } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/genesis_block_header_impl.hpp b/src/blockchain/impl/genesis_block_header_impl.hpp new file mode 100644 index 0000000..eaeb834 --- /dev/null +++ b/src/blockchain/impl/genesis_block_header_impl.hpp @@ -0,0 +1,32 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "blockchain/genesis_block_header.hpp" + +namespace lean::app { + class ChainSpec; +} +namespace lean::log { + class LoggingSystem; +} +namespace lean::crypto { + class Hasher; +} + +namespace lean::blockchain { + + class GenesisBlockHeaderImpl final : public GenesisBlockHeader { + public: + GenesisBlockHeaderImpl(const qtils::SharedRef &logsys, + const qtils::SharedRef &chain_spec, + const qtils::SharedRef &hasher); + }; + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/storage_util.cpp b/src/blockchain/impl/storage_util.cpp new file mode 100644 index 0000000..3ef13f1 --- /dev/null +++ b/src/blockchain/impl/storage_util.cpp @@ -0,0 +1,81 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "blockchain/impl/storage_util.hpp" + +#include + +#include "storage/storage_error.hpp" + +using qtils::ByteVec; +// using lean::Hash256; +// using lean::primitives::BlockId; +// using lean::primitives::BlockNumber; +// using lean::storage::Space; + +namespace lean::blockchain { + + outcome::result> blockIdToBlockHash( + storage::SpacedStorage &storage, const BlockId &block_id) { + return visit_in_place( + block_id, + [&](const BlockNumber &block_number) + -> outcome::result> { + auto key_space = storage.getSpace(storage::Space::LookupKey); + return key_space->tryGet(slotToHashLookupKey(block_number)); + }, + [](const BlockHash &block_hash) { + return std::make_optional(ByteVec(block_hash)); + }); + } + + outcome::result> blockHashByNumber( + storage::SpacedStorage &storage, BlockNumber block_number) { + auto key_space = storage.getSpace(storage::Space::LookupKey); + OUTCOME_TRY(data_opt, key_space->tryGet(slotToHashLookupKey(block_number))); + if (data_opt.has_value()) { + OUTCOME_TRY(hash, BlockHash::fromSpan(data_opt.value())); + return hash; + } + return std::nullopt; + } + + outcome::result hasInSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockId &block_id) { + OUTCOME_TRY(key, blockIdToBlockHash(storage, block_id)); + if (not key.has_value()) { + return false; + } + + auto target_space = storage.getSpace(space); + return target_space->contains(key.value()); + } + + outcome::result putToSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash, + qtils::ByteVecOrView &&value) { + auto target_space = storage.getSpace(space); + return target_space->put(block_hash, std::move(value)); + } + + outcome::result> getFromSpace( + storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash) { + auto target_space = storage.getSpace(space); + return target_space->tryGet(block_hash); + } + + outcome::result removeFromSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash) { + auto target_space = storage.getSpace(space); + return target_space->remove(block_hash); + } + +} // namespace lean::blockchain diff --git a/src/blockchain/impl/storage_util.hpp b/src/blockchain/impl/storage_util.hpp new file mode 100644 index 0000000..c4baf52 --- /dev/null +++ b/src/blockchain/impl/storage_util.hpp @@ -0,0 +1,106 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "lean_types/types.hpp" + +// #include "primitives/block_id.hpp" +#include "serde/serialization.hpp" +#include "storage/spaced_storage.hpp" + +/** + * Storage schema overview + * + * A key-value approach is used for block storage. + * Different parts containing a block are stored in multiple storage spaces but + * have to be addressed with the same key. + * + * A key is the combination of block's number concatenated with its hash. Let's + * name it as NumHashKey. + * + * There is also an auxilary space named Space::kLookupKey where + * BlockId->NumHashKey mappings are stored. Effectively there are could be two + * types of mappings: either BlockNumber->NumHashKey or BlockHash->NumHashKey. + * Anyways, the resulting NumHashKey is good to be used for further manipulating + * with the Block in other storage spaces. + */ + +/** + * Auxiliary functions to simplify usage of persistant map based storage + * as a Blockchain storage + */ + +namespace lean::blockchain { + + /** + * Convert slot into a short lookup key (LE representation) + */ + inline qtils::ByteVec slotToHashLookupKey(Slot slot) { + BOOST_STATIC_ASSERT(std::is_same_v); + auto data = encode(slot); + BOOST_STATIC_ASSERT(sizeof(std::byte) == sizeof(uint8_t)); + return std::move( + *static_cast *>(reinterpret_cast(&data))); + } + + /** + * Returns block hash by number if any + */ + outcome::result> blockHashByNumber( + storage::SpacedStorage &storage, BlockNumber block_number); + + /** + * Check if an entry is contained in the database + * @param storage - to get the entry from + * @param space - key space in the storage to which the entry belongs + * @param block_id - id of the block to get entry for + * @return true if the entry exists, false if does not, and error at fail + */ + outcome::result hasInSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockId &block_id); + + /** + * Put an entry to the key space \param space + * @param storage to put the entry to + * @param space keyspace for the entry value + * @param block_hash block hash that could be used to retrieve the value + * @param value data to be put to the storage + * @return storage error if any + */ + outcome::result putToSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash, + qtils::ByteVecOrView &&value); + + /** + * Get an entry from the database + * @param storage - to get the entry from + * @param space - key space in the storage to which the entry belongs + * @param block_hash - hash of the block to get entry for + * @return error, or an encoded entry, if any, or std::nullopt, if none + */ + outcome::result> getFromSpace( + storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash); + + /** + * Remove an entry from the key space \param space and corresponding lookup + * keys + * @param storage to put the entry to + * @param space keyspace for the entry value + * @param block_hash block hash that could be used to retrieve the value + * @return storage error if any + */ + outcome::result removeFromSpace(storage::SpacedStorage &storage, + storage::Space space, + const BlockHash &block_hash); + +} // namespace lean::blockchain diff --git a/src/clock/CMakeLists.txt b/src/clock/CMakeLists.txt new file mode 100644 index 0000000..d77bd65 --- /dev/null +++ b/src/clock/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(clock + impl/clock_impl.cpp +) diff --git a/src/clock/clock.hpp b/src/clock/clock.hpp new file mode 100644 index 0000000..dba064a --- /dev/null +++ b/src/clock/clock.hpp @@ -0,0 +1,66 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean::clock { + + /** + * An interface for a clock + * @tparam clock type is an underlying clock type, such as std::steady_clock + */ + template + class Clock { + public: + /** + * Difference between two time points + */ + using Duration = typename ClockType::duration; + + /** + * A moment in time, stored in milliseconds since Unix epoch start + */ + using TimePoint = typename ClockType::time_point; + + virtual ~Clock() = default; + + /** + * @return a time point representing the current time + */ + [[nodiscard]] virtual TimePoint now() const = 0; + + /** + * @return uint64_t representing number of seconds since the beginning of + * epoch (Jan 1, 1970) + */ + [[nodiscard]] virtual uint64_t nowSec() const = 0; + + /** + * @return uint64_t representing number of milliseconds since the beginning + * of epoch (Jan 1, 1970) + */ + [[nodiscard]] virtual uint64_t nowMsec() const = 0; + + static TimePoint zero() { + return TimePoint{}; + } + }; + + /** + * SystemClock alias over Clock. Should be used when we need to watch current + * time + */ + class SystemClock : public virtual Clock {}; + + /** + * SteadyClock alias over Clock. Should be used when we need to measure + * interval between two moments in time + */ + class SteadyClock : public virtual Clock {}; + +} // namespace lean::clock diff --git a/src/clock/impl/clock_impl.cpp b/src/clock/impl/clock_impl.cpp new file mode 100644 index 0000000..1c09513 --- /dev/null +++ b/src/clock/impl/clock_impl.cpp @@ -0,0 +1,33 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "clock/impl/clock_impl.hpp" + +namespace lean::clock { + + template + typename Clock::TimePoint ClockImpl::now() const { + return ClockType::now(); + } + + template + uint64_t ClockImpl::nowSec() const { + return std::chrono::duration_cast( + now().time_since_epoch()) + .count(); + } + + template + uint64_t ClockImpl::nowMsec() const { + return std::chrono::duration_cast( + now().time_since_epoch()) + .count(); + } + + template class ClockImpl; + template class ClockImpl; + +} // namespace lean::clock diff --git a/src/clock/impl/clock_impl.hpp b/src/clock/impl/clock_impl.hpp new file mode 100644 index 0000000..0dc2920 --- /dev/null +++ b/src/clock/impl/clock_impl.hpp @@ -0,0 +1,26 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "clock/clock.hpp" + +namespace lean::clock { + + template + class ClockImpl : virtual public Clock { + public: + typename Clock::TimePoint now() const override; + uint64_t nowSec() const override; + uint64_t nowMsec() const override; + }; + + class SystemClockImpl : public SystemClock, + public ClockImpl {}; + class SteadyClockImpl : public SteadyClock, + public ClockImpl {}; + +} // namespace lean::clock diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt new file mode 100644 index 0000000..b4ff0d7 --- /dev/null +++ b/src/crypto/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +#add_subdirectory(blake2) +#add_subdirectory(sha) +#add_subdirectory(twox) + +add_library(hasher + hasher/hasher_impl.cpp +) +target_link_libraries(hasher + qtils::qtils +# blake2 +# twox +# sha +# keccak +) diff --git a/src/crypto/blake.hpp b/src/crypto/blake.hpp new file mode 100644 index 0000000..0cb68df --- /dev/null +++ b/src/crypto/blake.hpp @@ -0,0 +1,35 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +namespace lean::crypto { + struct Blake { + using Hash = qtils::ByteArr<32>; + blake2b_state state; + Blake() { + blake2b_init(&state, sizeof(Hash)); + } + Blake &update(qtils::BytesIn input) { + blake2b_update(&state, input.data(), input.size()); + return *this; + } + Hash hash() const { + Hash hash; + auto state2 = state; + blake2b_final(&state2, hash.data(), sizeof(Hash)); + return hash; + } + static Hash hash(qtils::BytesIn input) { + return Blake{}.update(input).hash(); + } + }; +} // namespace lean::crypto diff --git a/src/crypto/blake2/CMakeLists.txt b/src/crypto/blake2/CMakeLists.txt new file mode 100644 index 0000000..2633ffe --- /dev/null +++ b/src/crypto/blake2/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(blake2 + blake2s.cpp + blake2b.cpp +) +target_link_libraries(blake2 + qtils::qtils +) diff --git a/src/crypto/blake2/blake2b.cpp b/src/crypto/blake2/blake2b.cpp new file mode 100644 index 0000000..b56317f --- /dev/null +++ b/src/crypto/blake2/blake2b.cpp @@ -0,0 +1,208 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +// taken from here +// https://github.com/mjosaarinen/blake2_mjosref/blob/master/blake2b.c + +// blake2b.c +// A simple BLAKE2b Reference Implementation. + +#include "blake2b.h" + +namespace lean::crypto { + + // Cyclic right rotation. + +#ifndef ROTR64 +#define ROTR64(x, y) (((x) >> (y)) ^ ((x) << (64 - (y)))) +#endif + + // Little-endian byte access. + +#define B2B_GET64(p) \ + (((uint64_t)((uint8_t *)(p))[0]) ^ (((uint64_t)((uint8_t *)(p))[1]) << 8) \ + ^ (((uint64_t)((uint8_t *)(p))[2]) << 16) \ + ^ (((uint64_t)((uint8_t *)(p))[3]) << 24) \ + ^ (((uint64_t)((uint8_t *)(p))[4]) << 32) \ + ^ (((uint64_t)((uint8_t *)(p))[5]) << 40) \ + ^ (((uint64_t)((uint8_t *)(p))[6]) << 48) \ + ^ (((uint64_t)((uint8_t *)(p))[7]) << 56)) + + // G Mixing function. + +#define B2B_G(a, b, c, d, x, y) \ + { \ + v[a] = v[a] + v[b] + (x); \ + v[d] = ROTR64(v[d] ^ v[a], 32); \ + v[c] = v[c] + v[d]; \ + v[b] = ROTR64(v[b] ^ v[c], 24); \ + v[a] = v[a] + v[b] + (y); \ + v[d] = ROTR64(v[d] ^ v[a], 16); \ + v[c] = v[c] + v[d]; \ + v[b] = ROTR64(v[b] ^ v[c], 63); \ + } + + // Initialization Vector. + + static const uint64_t blake2b_iv[8] = {0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179}; + + // Compression function. "last" flag indicates last block. + + static void blake2b_compress(blake2b_ctx *ctx, int last) { + const uint8_t sigma[12][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}}; + int i; + uint64_t v[16]; + uint64_t m[16]; + + for (i = 0; i < 8; i++) { // init work variables + v[i] = ctx->h[i]; + v[i + 8] = blake2b_iv[i]; + } + + v[12] ^= ctx->t[0]; // low 64 bits of offset + v[13] ^= ctx->t[1]; // high 64 bits + if (last) { // last block flag set ? + v[14] = ~v[14]; + } + + for (i = 0; i < 16; i++) { // get little-endian words + m[i] = B2B_GET64(&ctx->b[8 * i]); + } + + for (i = 0; i < 12; i++) { // twelve rounds + B2B_G(0, 4, 8, 12, m[sigma[i][0]], m[sigma[i][1]]); + B2B_G(1, 5, 9, 13, m[sigma[i][2]], m[sigma[i][3]]); + B2B_G(2, 6, 10, 14, m[sigma[i][4]], m[sigma[i][5]]); + B2B_G(3, 7, 11, 15, m[sigma[i][6]], m[sigma[i][7]]); + B2B_G(0, 5, 10, 15, m[sigma[i][8]], m[sigma[i][9]]); + B2B_G(1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]); + B2B_G(2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]); + B2B_G(3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]); + } + + for (i = 0; i < 8; ++i) { + ctx->h[i] ^= v[i] ^ v[i + 8]; + } + } + + // Initialize the hashing context "ctx" with optional key "key". + // 1 <= outlen <= 64 gives the digest size in bytes. + // Secret key (also <= 64 bytes) is optional (keylen = 0). + + int blake2b_init(blake2b_ctx *ctx, + size_t outlen, + const void *key, + size_t keylen) // (keylen=0: no key) + { + size_t i; + + if (outlen == 0 || outlen > 64 || keylen > 64) { + return -1; // illegal parameters + } + + for (i = 0; i < 8; i++) { // state, "param block" + ctx->h[i] = blake2b_iv[i]; + } + ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen; + + ctx->t[0] = 0; // input count low word + ctx->t[1] = 0; // input count high word + ctx->c = 0; // pointer within buffer + ctx->outlen = outlen; + + for (i = keylen; i < 128; i++) { // zero input block + ctx->b[i] = 0; + } + if (keylen > 0) { + blake2b_update(ctx, key, keylen); + ctx->c = 128; // at the end + } + + return 0; + } + + // Add "inlen" bytes from "in" into the hash. + + void blake2b_update(blake2b_ctx *ctx, + const void *in, + size_t inlen) // data bytes + { + size_t i; + + for (i = 0; i < inlen; i++) { + if (ctx->c == 128) { // buffer full ? + ctx->t[0] += ctx->c; // add counters + if (ctx->t[0] < ctx->c) { // carry overflow ? + ctx->t[1]++; // high word + } + blake2b_compress(ctx, 0); // compress (not last) + ctx->c = 0; // counter to zero + } + ctx->b[ctx->c++] = ((const uint8_t *)in)[i]; + } + } + + // Generate the message digest (size given in init). + // Result placed in "out". + + void blake2b_final(blake2b_ctx *ctx, void *out) { + size_t i; + + ctx->t[0] += ctx->c; // mark last block offset + if (ctx->t[0] < ctx->c) { // carry overflow + ctx->t[1]++; // high word + } + + while (ctx->c < 128) { // fill up with zeros + ctx->b[ctx->c++] = 0; + } + blake2b_compress(ctx, 1); // final block flag = 1 + + // little endian convert and store + for (i = 0; i < ctx->outlen; i++) { + ((uint8_t *)out)[i] = (ctx->h[i >> 3] >> (8 * (i & 7))) & 0xFF; + } + } + + // Convenience function for all-in-one computation. + + int blake2b(void *out, + size_t outlen, + const void *key, + size_t keylen, + const void *in, + size_t inlen) { + blake2b_ctx ctx; + + if (blake2b_init(&ctx, outlen, key, keylen)) { + return -1; + } + blake2b_update(&ctx, in, inlen); + blake2b_final(&ctx, out); + + return 0; + } + +} // namespace lean::crypto diff --git a/src/crypto/blake2/blake2b.h b/src/crypto/blake2/blake2b.h new file mode 100644 index 0000000..b007e93 --- /dev/null +++ b/src/crypto/blake2/blake2b.h @@ -0,0 +1,65 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +// taken from here +// https://github.com/mjosaarinen/blake2_mjosref/blob/master/blake2b.h + +#ifndef CORE_BLAKE2B_H +#define CORE_BLAKE2B_H + +#include +#include + +#include +#include + +namespace lean::crypto { + + // state context + typedef struct { + uint8_t b[128]; // input buffer + uint64_t h[8]; // chained state + uint64_t t[2]; // total number of bytes + size_t c; // pointer for b[] + size_t outlen; // digest size + } blake2b_ctx; + + // Initialize the hashing context "ctx" with optional key "key". + // 1 <= outlen <= 64 gives the digest size in bytes. + // Secret key (also <= 64 bytes) is optional (keylen = 0). + int blake2b_init(blake2b_ctx *ctx, + size_t outlen, + const void *key, + size_t keylen); // secret key + + // Add "inlen" bytes from "in" into the hash. + void blake2b_update(blake2b_ctx *ctx, // context + const void *in, + size_t inlen); // data to be hashed + + // Generate the message digest (size given in init). + // Result placed in "out". + void blake2b_final(blake2b_ctx *ctx, void *out); + + // All-in-one convenience function. + int blake2b(void *out, + size_t outlen, // return buffer for digest + const void *key, + size_t keylen, // optional secret key + const void *in, + size_t inlen); // data to be hashed + + template + inline qtils::ByteArr blake2b(qtils::ByteView buf) { + qtils::ByteArr out; + BOOST_VERIFY(blake2b(out.data(), N, nullptr, 0, buf.data(), buf.size()) + == 0); + return out; + } + +} // namespace lean::crypto + +#endif diff --git a/src/crypto/blake2/blake2s.cpp b/src/crypto/blake2/blake2s.cpp new file mode 100644 index 0000000..7d01f58 --- /dev/null +++ b/src/crypto/blake2/blake2s.cpp @@ -0,0 +1,220 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +// taken from here +// https://github.com/mjosaarinen/blake2_mjosref/blob/master/blake2s.c + +// blake2s.c +// A simple blake2s Reference Implementation. + +#include "blake2s.h" + +#include + +namespace lean::crypto { + +#define _256_bits 32 + + // Cyclic right rotation. + +#ifndef ROTR32 +#define ROTR32(x, y) (((x) >> (y)) ^ ((x) << (32 - (y)))) +#endif + + // Little-endian byte access. + + // state context + typedef struct { + uint8_t b[64]; // input buffer + uint32_t h[8]; // chained state + uint32_t t[2]; // total number of bytes + size_t c; // pointer for b[] + size_t outlen; // digest size + } blake2s_ctx_full; + +#define B2S_GET32(p) \ + (((uint32_t)((uint8_t *)(p))[0]) ^ (((uint32_t)((uint8_t *)(p))[1]) << 8) \ + ^ (((uint32_t)((uint8_t *)(p))[2]) << 16) \ + ^ (((uint32_t)((uint8_t *)(p))[3]) << 24)) + + // Mixing function G. + +#define B2S_G(a, b, c, d, x, y) \ + { \ + v[a] = v[a] + v[b] + (x); \ + v[d] = ROTR32(v[d] ^ v[a], 16); \ + v[c] = v[c] + v[d]; \ + v[b] = ROTR32(v[b] ^ v[c], 12); \ + v[a] = v[a] + v[b] + (y); \ + v[d] = ROTR32(v[d] ^ v[a], 8); \ + v[c] = v[c] + v[d]; \ + v[b] = ROTR32(v[b] ^ v[c], 7); \ + } + + // Initialization Vector. + + static const uint32_t blake2s_iv[8] = {0x6A09E667, + 0xBB67AE85, + 0x3C6EF372, + 0xA54FF53A, + 0x510E527F, + 0x9B05688C, + 0x1F83D9AB, + 0x5BE0CD19}; + + // Compression function. "last" flag indicates last block. + + static void blake2s_compress(blake2s_ctx_full *ctx, int last) { + const uint8_t sigma[10][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}}; + int i; + uint32_t v[16]; + uint32_t m[16]; + + for (i = 0; i < 8; i++) { // init work variables + v[i] = ctx->h[i]; + v[i + 8] = blake2s_iv[i]; + } + + v[12] ^= ctx->t[0]; // low 32 bits of offset + v[13] ^= ctx->t[1]; // high 32 bits + if (last) { // last block flag set ? + v[14] = ~v[14]; + } + + for (i = 0; i < 16; i++) { // get little-endian words + m[i] = B2S_GET32(&ctx->b[4 * i]); + } + + for (i = 0; i < 10; i++) { // ten rounds + B2S_G(0, 4, 8, 12, m[sigma[i][0]], m[sigma[i][1]]); + B2S_G(1, 5, 9, 13, m[sigma[i][2]], m[sigma[i][3]]); + B2S_G(2, 6, 10, 14, m[sigma[i][4]], m[sigma[i][5]]); + B2S_G(3, 7, 11, 15, m[sigma[i][6]], m[sigma[i][7]]); + B2S_G(0, 5, 10, 15, m[sigma[i][8]], m[sigma[i][9]]); + B2S_G(1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]); + B2S_G(2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]); + B2S_G(3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]); + } + + for (i = 0; i < 8; ++i) { + ctx->h[i] ^= v[i] ^ v[i + 8]; + } + } + + // Add "inlen" bytes from "in" into the hash. + void blake2s_update(blake2s_ctx *ctx_opaque, const void *in, size_t inlen) { + blake2s_ctx_full *ctx = (blake2s_ctx_full *)ctx_opaque->opaque; + + size_t i; + + for (i = 0; i < inlen; i++) { + if (ctx->c == 64) { // buffer full ? + ctx->t[0] += ctx->c; // add counters + if (ctx->t[0] < ctx->c) { // carry overflow ? + ctx->t[1]++; // high word + } + blake2s_compress(ctx, 0); // compress (not last) + ctx->c = 0; // counter to zero + } + ctx->b[ctx->c++] = ((const uint8_t *)in)[i]; + } + } + + // Generate the message digest (size given in init). + // Result placed in "out". + + void blake2s_final(blake2s_ctx *ctx_opaque, void *out) { + blake2s_ctx_full *ctx = (blake2s_ctx_full *)ctx_opaque->opaque; + + size_t i; + + ctx->t[0] += ctx->c; // mark last block offset + if (ctx->t[0] < ctx->c) { // carry overflow + ctx->t[1]++; // high word + } + + while (ctx->c < 64) { // fill up with zeros + ctx->b[ctx->c++] = 0; + } + blake2s_compress(ctx, 1); // final block flag = 1 + + // little endian convert and store + for (i = 0; i < ctx->outlen; i++) { + ((uint8_t *)out)[i] = (uint8_t)((ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF); + } + } + + int blake2s_init(blake2s_ctx *ctx_opaque, + size_t outlen, + const void *key, + size_t keylen) { + blake2s_ctx_full *ctx = (blake2s_ctx_full *)ctx_opaque->opaque; + + size_t i; + + if (outlen == 0 || outlen > 32 || keylen > 32) { + return -1; // illegal parameters + } + + for (i = 0; i < 8; i++) { // state, "param block" + ctx->h[i] = blake2s_iv[i]; + } + + ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen; + + ctx->t[0] = 0; // input count low word + ctx->t[1] = 0; // input count high word + ctx->c = 0; // pointer within buffer + ctx->outlen = outlen; + + for (i = keylen; i < 64; i++) { // zero input block + ctx->b[i] = 0; + } + + if (keylen > 0) { + blake2s_update(ctx_opaque, key, keylen); + ctx->c = 64; // at the end + } + + return 0; + } + + int blake2s(void *out, + size_t outlen, + const void *key, + size_t keylen, + const void *in, + size_t inlen) { + blake2s_ctx ctx; + + if (blake2s_init(&ctx, outlen, key, keylen)) { + return -1; + } + + blake2s_update(&ctx, in, inlen); + blake2s_final(&ctx, out); + + return 0; + } + + void blake2s_256_init(blake2s_ctx *ctx_opaque) { + blake2s_init(ctx_opaque, _256_bits, NULL, 0); + } + + void blake2s_256(void *out, const void *in, size_t inlen) { + blake2s(out, _256_bits, NULL, 0, in, inlen); + } +} // namespace lean::crypto diff --git a/src/crypto/blake2/blake2s.h b/src/crypto/blake2/blake2s.h new file mode 100644 index 0000000..162afa7 --- /dev/null +++ b/src/crypto/blake2/blake2s.h @@ -0,0 +1,86 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +// taken from here +// https://github.com/mjosaarinen/blake2_mjosref/blob/master/blake2s.h + +#ifndef CORE_BLAKE2S_HASH +#define CORE_BLAKE2S_HASH + +#include + +namespace lean::crypto { + + typedef struct { + unsigned char opaque[128]; + } blake2s_ctx; + + /** + * @brief Initialize hash context + * @param ctx context + */ + void blake2s_256_init(blake2s_ctx *ctx); + + /** + * @brief Update context with incoming bytes + * @param ctx context + * @param in {@param inlen} byte array + * @param inlen size of {@param in} + */ + void blake2s_update(blake2s_ctx *ctx, const void *in, size_t inlen); + + /** + * @brief Finalize hash calculation + * @param ctx context + * @param out 32-byte output + * @return + */ + void blake2s_final(blake2s_ctx *ctx, void *out); + + /** + * @brief One-shot convenience function to calculate blake2s_256 hash + * @param out 32-byte buffer + * @param in {@param inlen} bytes input buffer + * @param inlen size of the input buffer + * @return + */ + void blake2s_256(void *out, const void *in, size_t inlen); + + /** + * @brief Generic blake2s init function + * @param ctx context + * @param outlen 1..32 bytes of output buffer size + * @param key optional key + * @param keylen length of {@param key} in bytes. Pass 0 to indicate that key + * is not provided. + * @return -1 in case of wrong input arguments; 0 in case of success. + */ + int blake2s_init(blake2s_ctx *ctx, + size_t outlen, + const void *key, + size_t keylen); + + /** + * @brief All in one blake2s hashing function. + * @param out output buffer + * @param outlen size of {@param out} + * @param key optional key + * @param keylen size of {@param key}. Pass 0 to indicate that key is not + * provided. + * @param in data to be hashed + * @param inlen size of {@param in} + * @return -1 in case of wrong input arguments; 0 in case of success + */ + int blake2s(void *out, + size_t outlen, + const void *key, + size_t keylen, + const void *in, + size_t inlen); + +} // namespace lean::crypto + +#endif // CORE_BLAKE2S_HASH diff --git a/src/crypto/ed25519.hpp b/src/crypto/ed25519.hpp new file mode 100644 index 0000000..705cc58 --- /dev/null +++ b/src/crypto/ed25519.hpp @@ -0,0 +1,38 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +namespace lean::crypto::ed25519 { + using Secret = qtils::ByteArr; + using Public = qtils::ByteArr; + using KeyPair = qtils::ByteArr; + using Signature = qtils::ByteArr; + using Message = qtils::BytesIn; + + inline std::optional sign(const KeyPair &keypair, + Message message) { + Signature sig; + auto res = ed25519_sign( + sig.data(), keypair.data(), message.data(), message.size_bytes()); + if (res != ED25519_RESULT_OK) { + return std::nullopt; + } + return sig; + } + + inline bool verify(const Signature &signature, + Message message, + const Public &public_key) { + auto res = ed25519_verify(signature.data(), + public_key.data(), + message.data(), + message.size_bytes()); + return res == ED25519_RESULT_OK; + } +} // namespace lean::crypto::ed25519 diff --git a/src/crypto/hash_types.hpp b/src/crypto/hash_types.hpp new file mode 100644 index 0000000..2ce6ed1 --- /dev/null +++ b/src/crypto/hash_types.hpp @@ -0,0 +1,16 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean { + using Hash64 = qtils::ByteArr<8>; + using Hash128 = qtils::ByteArr<16>; + using Hash256 = qtils::ByteArr<32>; + using Hash512 = qtils::ByteArr<64>; +} // namespace lean diff --git a/src/crypto/hasher.hpp b/src/crypto/hasher.hpp new file mode 100644 index 0000000..04ec76f --- /dev/null +++ b/src/crypto/hasher.hpp @@ -0,0 +1,89 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "crypto/hash_types.hpp" + +namespace lean::crypto { + + class Hasher { + public: + virtual ~Hasher() = default; + + /** + * @brief twox_64 calculates 8-byte twox hash + * @param data source data + * @return 64-bit hash value + */ + virtual Hash64 twox_64(qtils::ByteView data) const = 0; + + /** + * @brief blake2b_64 function calculates 8-byte blake2b hash + * @param data source value + * @return 64-bit hash value + */ + virtual Hash64 blake2b_64(qtils::ByteView data) const = 0; + + /** + * @brief twox_128 calculates 16-byte twox hash + * @param data source data + * @return 128-bit hash value + */ + virtual Hash128 twox_128(qtils::ByteView data) const = 0; + + /** + * @brief blake2b_128 function calculates 16-byte blake2b hash + * @param data source value + * @return 128-bit hash value + */ + virtual Hash128 blake2b_128(qtils::ByteView data) const = 0; + + /** + * @brief twox_256 calculates 32-byte twox hash + * @param data source data + * @return 256-bit hash value + */ + virtual Hash256 twox_256(qtils::ByteView data) const = 0; + + /** + * @brief blake2b_256 function calculates 32-byte blake2b hash + * @param data source value + * @return 256-bit hash value + */ + virtual Hash256 blake2b_256(qtils::ByteView data) const = 0; + + /** + * @brief blake2b_512 function calculates 64-byte blake2b hash + * @param data source value + * @return 512-bit hash value + */ + virtual Hash512 blake2b_512(qtils::ByteView data) const = 0; + + /** + * @brief keccak_256 function calculates 32-byte keccak hash + * @param data source value + * @return 256-bit hash value + */ + virtual Hash256 keccak_256(qtils::ByteView data) const = 0; + + /** + * @brief blake2s_256 function calculates 32-byte blake2s hash + * @param data source value + * @return 256-bit hash value + */ + virtual Hash256 blake2s_256(qtils::ByteView data) const = 0; + + /** + * @brief sha2_256 function calculates 32-byte sha2-256 hash + * @param data source value + * @return 256-bit hash value + */ + virtual Hash256 sha2_256(qtils::ByteView data) const = 0; + }; +} // namespace lean::crypto diff --git a/src/crypto/hasher/hasher_impl.cpp b/src/crypto/hasher/hasher_impl.cpp new file mode 100644 index 0000000..6698000 --- /dev/null +++ b/src/crypto/hasher/hasher_impl.cpp @@ -0,0 +1,68 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "crypto/hasher/hasher_impl.hpp" + +// #include (data); + // } + // + // Hash128 HasherImpl::twox_128(qtils::ByteView data) const { + // return make_twox128(data); + // } + // + // Hash128 HasherImpl::blake2b_128(qtils::ByteView data) const { + // return blake2b<16>(data); + // } + // + // Hash256 HasherImpl::twox_256(qtils::ByteView data) const { + // return make_twox256(data); + // } + // + // Hash256 HasherImpl::blake2b_256(qtils::ByteView data) const { + // return blake2b<32>(data); + // } + // + // Hash512 HasherImpl::blake2b_512(qtils::ByteView data) const { + // return blake2b<64>(data); + // } + // + // Hash256 HasherImpl::keccak_256(qtils::ByteView data) const { + // Hash256 out; + // sha3_HashBuffer(256, + // SHA3_FLAGS::SHA3_FLAGS_KECCAK, + // data.data(), + // data.size(), + // out.data(), + // 32); + // return out; + // } + // + // Hash256 HasherImpl::blake2s_256(qtils::ByteView data) const { + // Hash256 out; + // blake2s(out.data(), 32, nullptr, 0, data.data(), data.size()); + // return out; + // } + // + // Hash256 HasherImpl::sha2_256(qtils::ByteView data) const { + // return sha256(data); + // } + +} // namespace lean::crypto diff --git a/src/crypto/hasher/hasher_impl.hpp b/src/crypto/hasher/hasher_impl.hpp new file mode 100644 index 0000000..22d41a5 --- /dev/null +++ b/src/crypto/hasher/hasher_impl.hpp @@ -0,0 +1,39 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "crypto/hash_types.hpp" +#include "crypto/hasher.hpp" + +namespace lean::crypto { + + class HasherImpl : public Hasher { + public: + ~HasherImpl() override = default; + + Hash64 twox_64(qtils::ByteView data) const override; + + Hash64 blake2b_64(qtils::ByteView data) const override; + + Hash128 twox_128(qtils::ByteView data) const override; + + Hash128 blake2b_128(qtils::ByteView data) const override; + + Hash256 twox_256(qtils::ByteView data) const override; + + Hash256 blake2b_256(qtils::ByteView data) const override; + + Hash256 keccak_256(qtils::ByteView data) const override; + + Hash256 blake2s_256(qtils::ByteView data) const override; + + Hash256 sha2_256(qtils::ByteView data) const override; + + Hash512 blake2b_512(qtils::ByteView data) const override; + }; + +} // namespace lean::crypto diff --git a/src/crypto/keccak.hpp b/src/crypto/keccak.hpp new file mode 100644 index 0000000..170c840 --- /dev/null +++ b/src/crypto/keccak.hpp @@ -0,0 +1,113 @@ +// https://github.com/nayuki/Bitcoin-Cryptography-Library/blob/master/cpp/Keccak256.hpp +// Not using https://vcpkg.io/en/package/keccak-tiny from vcpkg because it only +// exports sha3, not keccak + +#pragma once + +#include + +/** + * Keccak hash + */ + +namespace lean::crypto { + struct Keccak { + using Hash32 = qtils::ByteArr<32>; + uint64_t state[5][5] = {}; + size_t blockOff = 0; + static constexpr size_t HASH_LEN = 32; + static constexpr size_t BLOCK_SIZE = 200 - HASH_LEN * 2; + void absorb() { + auto rotl64 = [](uint64_t x, uint8_t i) { + return (x << i) | (x >> (64 - i)); + }; + constexpr size_t NUM_ROUNDS = 24; + constexpr uint8_t ROTATION[5][5] = { + {0, 36, 3, 41, 18}, + {1, 44, 10, 45, 2}, + {62, 6, 43, 15, 61}, + {28, 55, 25, 21, 56}, + {27, 20, 39, 8, 14}, + }; + uint64_t(*a)[5] = state; + uint8_t r = 1; // LFSR + for (int i = 0; i < NUM_ROUNDS; i++) { + // Theta step + uint64_t c[5] = {}; + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + c[x] ^= a[x][y]; + } + } + for (int x = 0; x < 5; x++) { + uint64_t d = c[(x + 4) % 5] ^ rotl64(c[(x + 1) % 5], 1); + for (int y = 0; y < 5; y++) { + a[x][y] ^= d; + } + } + + // Rho and pi steps + uint64_t b[5][5]; + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + b[y][(x * 2 + y * 3) % 5] = rotl64(a[x][y], ROTATION[x][y]); + } + } + + // Chi step + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + a[x][y] = b[x][y] ^ (~b[(x + 1) % 5][y] & b[(x + 2) % 5][y]); + } + } + + // Iota step + for (int j = 0; j < 7; j++) { + a[0][0] ^= static_cast(r & 1) << ((1 << j) - 1); + r = static_cast((r << 1) ^ ((r >> 7) * 0x171)); + } + } + } + Hash32 finalize() { + Hash32 hash; + // Final block and padding + { + int i = blockOff >> 3; + state[i % 5][i / 5] ^= UINT64_C(0x01) << ((blockOff & 7) << 3); + blockOff = BLOCK_SIZE - 1; + int j = blockOff >> 3; + state[j % 5][j / 5] ^= UINT64_C(0x80) << ((blockOff & 7) << 3); + absorb(); + } + // Uint64 array to bytes in little endian + for (int i = 0; i < HASH_LEN; i++) { + int j = i >> 3; + hash[i] = static_cast(state[j % 5][j / 5] >> ((i & 7) << 3)); + } + return hash; + } + void update(uint8_t byte) { + int j = blockOff >> 3; + state[j % 5][j / 5] ^= static_cast(byte) + << ((blockOff & 7) << 3); + blockOff++; + if (blockOff == BLOCK_SIZE) { + absorb(); + blockOff = 0; + } + } + Keccak &update(qtils::BytesIn input) { + for (auto &x : input) { + update(x); + } + return *this; + } + Hash32 hash() const { + auto copy = *this; + return copy.finalize(); + } + static Hash32 hash(qtils::BytesIn input) { + return Keccak{}.update(input).hash(); + } + }; +} // namespace lean::crypto diff --git a/src/crypto/keccak/keccak.hpp b/src/crypto/keccak/keccak.hpp new file mode 100644 index 0000000..38108c3 --- /dev/null +++ b/src/crypto/keccak/keccak.hpp @@ -0,0 +1,24 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "keccak/keccak.h" + +namespace lean::crypto { + inline Hash256 keccak(qtils::ByteView buf) { + Hash256 out; + sha3_HashBuffer(256, + SHA3_FLAGS::SHA3_FLAGS_KECCAK, + buf.data(), + buf.size(), + out.data(), + 32); + return out; + } +} // namespace lean::crypto diff --git a/src/crypto/sha/CMakeLists.txt b/src/crypto/sha/CMakeLists.txt new file mode 100644 index 0000000..ad55bc5 --- /dev/null +++ b/src/crypto/sha/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(sha + sha256.hpp + sha256.cpp +) +target_link_libraries(sha + PUBLIC OpenSSL::SSL + OpenSSL::Crypto + qtils::qtils +) diff --git a/src/crypto/sha/sha256.cpp b/src/crypto/sha/sha256.cpp new file mode 100644 index 0000000..5e75dba --- /dev/null +++ b/src/crypto/sha/sha256.cpp @@ -0,0 +1,26 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "crypto/sha/sha256.hpp" + +#include + +namespace lean::crypto { + Hash256 sha256(std::string_view input) { + return sha256( + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + {reinterpret_cast(input.data()), input.size()}); + } + + Hash256 sha256(qtils::ByteView input) { + Hash256 out; + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, input.data(), input.size()); + SHA256_Final(out.data(), &ctx); + return out; + } +} // namespace lean::crypto diff --git a/src/crypto/sha/sha256.hpp b/src/crypto/sha/sha256.hpp new file mode 100644 index 0000000..611d959 --- /dev/null +++ b/src/crypto/sha/sha256.hpp @@ -0,0 +1,29 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "crypto/hash_types.hpp" + +namespace lean::crypto { + + /** + * Take a SHA-256 hash from string + * @param input to be hashed + * @return hashed bytes + */ + Hash256 sha256(std::string_view input); + + /** + * Take a SHA-256 hash from bytes + * @param input to be hashed + * @return hashed bytes + */ + Hash256 sha256(qtils::ByteView input); + +} // namespace lean::crypto diff --git a/src/crypto/twox/CMakeLists.txt b/src/crypto/twox/CMakeLists.txt new file mode 100644 index 0000000..9cdf301 --- /dev/null +++ b/src/crypto/twox/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(twox + twox.cpp +) +target_link_libraries(twox + xxHash::xxhash + qtils::qtils + ) diff --git a/src/crypto/twox/twox.cpp b/src/crypto/twox/twox.cpp new file mode 100644 index 0000000..77c2978 --- /dev/null +++ b/src/crypto/twox/twox.cpp @@ -0,0 +1,64 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "crypto/twox/twox.hpp" + +#include + +namespace lean::crypto { + + void make_twox64(const uint8_t *in, uint32_t len, uint8_t *out) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto *ptr = reinterpret_cast(out); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[0] = XXH64(in, len, 0); + } + + Hash64 make_twox64(qtils::ByteView buf) { + Hash64 hash{}; + make_twox64(buf.data(), buf.size(), hash.data()); + return hash; + } + + void make_twox128(const uint8_t *in, uint32_t len, uint8_t *out) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto *ptr = reinterpret_cast(out); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[0] = XXH64(in, len, 0); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[1] = XXH64(in, len, 1); + } + + Hash128 make_twox128(qtils::ByteView buf) { + Hash128 hash{}; + make_twox128(buf.data(), buf.size(), hash.data()); + return hash; + } + + void make_twox256(const uint8_t *in, uint32_t len, uint8_t *out) { + // Ensure the buffer is aligned to the boundary required for uint64_t + // (required for happy UBSAN) + std::array aligned_out{}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto *ptr = reinterpret_cast(aligned_out.data()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[0] = XXH64(in, len, 0); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[1] = XXH64(in, len, 1); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[2] = XXH64(in, len, 2); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ptr[3] = XXH64(in, len, 3); + std::memcpy(out, aligned_out.data(), 4 * sizeof(uint64_t)); + } + + Hash256 make_twox256(qtils::ByteView buf) { + Hash256 hash{}; + make_twox256(buf.data(), buf.size(), hash.data()); + return hash; + } + +} // namespace lean::crypto diff --git a/src/crypto/twox/twox.hpp b/src/crypto/twox/twox.hpp new file mode 100644 index 0000000..fbccf7f --- /dev/null +++ b/src/crypto/twox/twox.hpp @@ -0,0 +1,20 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +namespace lean::crypto { + + Hash64 make_twox64(qtils::ByteView buf); + + Hash128 make_twox128(qtils::ByteView buf); + + Hash256 make_twox256(qtils::ByteView buf); + +} // namespace lean::crypto diff --git a/src/executable/CMakeLists.txt b/src/executable/CMakeLists.txt new file mode 100644 index 0000000..3e6813f --- /dev/null +++ b/src/executable/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +set(CMAKE_OSX_ARCHITECTURES "arm64") + +set(LIBRARIES + node_injector +) + +include_directories( + ${PROJECT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/generated +) + +# fix for dyld bug on mac +# we should actually check the target platform here, not the host platform, +# but not that we explicitly support cross-compilation anyway +if (CMAKE_HOST_APPLE) + option(MACOS_BIG_EXE_USE_DYLIB "compile dylib instead of exe to work around dyld cache error when loading big exe" ON) +endif () +if (MACOS_BIG_EXE_USE_DYLIB) + add_library(jam_node SHARED jam_node.cpp) + set_target_properties(jam_node PROPERTIES PREFIX "" DEBUG_POSTFIX "") + target_compile_definitions(jam_node PRIVATE BUILD_AS_LIBRARY) + target_link_libraries(jam_node ${LIBRARIES}) + + add_executable(jam_node_dlopen dlopen.cpp) + set_target_properties(jam_node_dlopen PROPERTIES OUTPUT_NAME jam_node) + set_target_properties(jam_node_dlopen PROPERTIES LINKER_LANGUAGE CXX) +else () + add_executable(jam_node jam_node.cpp) + target_link_libraries(jam_node ${LIBRARIES}) +endif () +add_dependencies(jam_node all_modules) + +#if (BACKWARD) +# add_backward(jam_node) +#endif () + + +add_executable(experiment experiment.cpp) +target_link_libraries(experiment fmt::fmt) diff --git a/src/executable/dlopen.cpp b/src/executable/dlopen.cpp new file mode 100644 index 0000000..3de2e90 --- /dev/null +++ b/src/executable/dlopen.cpp @@ -0,0 +1,38 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +extern "C" { +#include +#include +#include +#include + +#include +} + +int main(int argc, const char **argv, const char **env) { + uint32_t size = 0; + _NSGetExecutablePath(NULL, &size); + char *exe = (char *)malloc(size); + _NSGetExecutablePath(exe, &size); + + const char *suffix = ".dylib"; + char *dylib = (char *)malloc(strlen(exe) + strlen(suffix) + 1); + strcpy(dylib, exe); + strcpy(dylib + strlen(exe), suffix); + + void *lib = dlopen(dylib, RTLD_NOW); + if (lib == NULL) { + printf("dlopen: %s\n", dlerror()); + return -1; + } + void *sym = dlsym(lib, "main"); + if (sym == NULL) { + printf("dlsym: %s\n", dlerror()); + return -1; + } + return ((int (*)(int, const char **, const char **))sym)(argc, argv, env); +} diff --git a/src/executable/experiment.cpp b/src/executable/experiment.cpp new file mode 100644 index 0000000..4af153b --- /dev/null +++ b/src/executable/experiment.cpp @@ -0,0 +1,5 @@ + +int main() { + // place for experiments + return 0; +} diff --git a/src/executable/jam_node.cpp b/src/executable/jam_node.cpp new file mode 100644 index 0000000..060ed2e --- /dev/null +++ b/src/executable/jam_node.cpp @@ -0,0 +1,215 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include "app/application.hpp" +#include "app/configuration.hpp" +#include "app/configurator.hpp" +#include "injector/node_injector.hpp" +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "modules/module_loader.hpp" +#include "se/subscription.hpp" + +using std::string_view_literals::operator""sv; + +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +namespace { + void wrong_usage() { + std::cerr << "Wrong usage.\n" + "Run with `--help' argument to print usage\n"; + } + + using lean::app::Application; + using lean::app::Configuration; + using lean::injector::NodeInjector; + using lean::log::LoggingSystem; + + int run_node(std::shared_ptr logsys, + std::shared_ptr appcfg) { + auto injector = std::make_unique(logsys, appcfg); + + // Load modules + std::deque> loaders; + { + auto logger = logsys->getLogger("Modules", "lean"); + const std::string path(appcfg->modulesDir()); + + lean::modules::ModuleLoader module_loader(path); + auto modules_res = module_loader.get_modules(); + if (modules_res.has_error()) { + SL_CRITICAL(logger, "Failed to load modules from path: {}", path); + return EXIT_FAILURE; + } + auto &modules = modules_res.value(); + + for (const auto &module : modules) { + SL_INFO(logger, + "Found module '{}', path: {}", + module->get_module_info(), + module->get_path()); + + auto loader = injector->register_loader(module); + + // Skip unsupported + if (not loader) { + SL_WARN(logger, + "Module '{}' has unsupported loader '{}'; Skipped", + module->get_module_info(), + module->get_loader_id()); + continue; + } + + // Init module + SL_INFO(logger, + "Module '{}' loaded by '{}'", + module->get_module_info(), + module->get_loader_id()); + loaders.emplace_back(std::move(loader)); + } + + // Notify about all modules are loaded + // se_manager->notify(lean::EventTypes::LoadingIsFinished); + } + + auto logger = logsys->getLogger("Main", lean::log::defaultGroupName); + auto app = injector->injectApplication(); + SL_INFO(logger, "Node started. Version: {} ", appcfg->nodeVersion()); + + app->run(); + + SL_INFO(logger, "Node stopped"); + logger->flush(); + + return EXIT_SUCCESS; + } + +} // namespace + +int main(int argc, const char **argv, const char **env) { + soralog::util::setThreadName("lean-node"); + + qtils::FinalAction flush_std_streams_at_exit([] { + std::cout.flush(); + std::cerr.flush(); + }); + + if (argc == 0) { + // Abnormal run + wrong_usage(); + return EXIT_FAILURE; + } + + if (argc == 1) { + // Run without arguments + wrong_usage(); + return EXIT_FAILURE; + } + + auto app_configurator = + std::make_unique(argc, argv, env); + + // Parse CLI args for help, version and config + if (auto res = app_configurator->step1(); res.has_value()) { + if (res.value()) { + return EXIT_SUCCESS; + } + } else { + return EXIT_FAILURE; + } + + // Setup logging system + auto logging_system = ({ + auto log_config = app_configurator->getLoggingConfig(); + if (log_config.has_error()) { + std::cerr << "Logging config is empty.\n"; + return EXIT_FAILURE; + } + + auto log_configurator = std::make_shared( + std::shared_ptr(nullptr), log_config.value()); + + auto logging_system = + std::make_shared(std::move(log_configurator)); + + auto config_result = logging_system->configure(); + if (not config_result.message.empty()) { + (config_result.has_error ? std::cerr : std::cout) + << config_result.message << '\n'; + } + if (config_result.has_error) { + return EXIT_FAILURE; + } + + std::make_shared(std::move(logging_system)); + }); + + // Parse remaining args + if (auto res = app_configurator->step2(); res.has_value()) { + if (res.value()) { + return EXIT_SUCCESS; + } + } else { + return EXIT_FAILURE; + } + + // Setup config + auto configuration = ({ + auto logger = logging_system->getLogger("Configurator", "lean"); + + auto config_res = app_configurator->calculateConfig(logger); + if (config_res.has_error()) { + auto error = config_res.error(); + SL_CRITICAL(logger, "Failed to calculate config: {}", error); + fmt::println(std::cerr, "Failed to calculate config: {}", error); + fmt::println(std::cerr, "See more details in the log"); + return EXIT_FAILURE; + } + + config_res.value(); + }); + + int exit_code; + + { + std::string_view name{argv[1]}; + + if (name.substr(0, 1) == "-") { + // The first argument isn't subcommand, run as node + exit_code = run_node(logging_system, configuration); + } + + // else if (false and name == "subcommand-1"s) { + // exit_code = execute_subcommend_1(argc - 1, argv + 1); + // } + // + // else if (false and name == "subcommand-2"s) { + // exit_code = execute_subcommend_2(argc - 1, argv + 1); + // } + + else { + // No subcommand, but argument is not a valid option: begins not with dash + wrong_usage(); + return EXIT_FAILURE; + } + } + + auto logger = logging_system->getLogger("Main", lean::log::defaultGroupName); + SL_INFO(logger, "All components are stopped"); + logger->flush(); + + return exit_code; +} + +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) diff --git a/src/injector/CMakeLists.txt b/src/injector/CMakeLists.txt new file mode 100644 index 0000000..1d74086 --- /dev/null +++ b/src/injector/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(node_injector + node_injector.cpp +) +target_link_libraries(node_injector + Boost::Boost.DI + logger + app_configurator + chain_spec + app_state_manager + application + metrics + clock + hasher + se_async + modules + storage + blockchain +) diff --git a/src/injector/bind_by_lambda.hpp b/src/injector/bind_by_lambda.hpp new file mode 100644 index 0000000..7a0b02a --- /dev/null +++ b/src/injector/bind_by_lambda.hpp @@ -0,0 +1,69 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean::injector { + /** + * Creates single instance per injector. + * + * `boost::di::bind.to(callable)` is hardcoded to + * `boost::di::scopes::instance` and prevents scope redefinition with + * `.in(scope)`. + * + * `callable` will be called more than once. Caching result with `static` will + * behave like `boost::di::scopes::singleton`. + * + * `BindByLambda` wraps `boost::di::extension::shared` to create single + * instance per injector, and adds `callable` support from + * `boost::di::scopes::instance`. + */ + struct BindByLambda { + template + struct ProviderF { + auto get() const { + return f(provider.super()); + } + F &f; + const Provider &provider; + }; + + template + struct scope { + explicit scope(const F &f) : f_{f} {} + + template + using is_referable = std::true_type; + + template + static boost::di::wrappers::shared + try_create(const Provider &provider); + + template + auto create(const Provider &provider) & { + return base_.template create( + ProviderF{f_, provider}); + } + + template + auto create(const Provider &provider) && { + return std::move(base_).template create( + ProviderF{f_, provider}); + } + + boost::di::extension::detail::shared::scope base_; + F f_; + }; + }; + + template + auto bind_by_lambda(const F &f) { + return boost::di::core::dependency{f}; + } +} // namespace lean::injector diff --git a/src/injector/dont_inject.hpp b/src/injector/dont_inject.hpp new file mode 100644 index 0000000..b3cdcfd --- /dev/null +++ b/src/injector/dont_inject.hpp @@ -0,0 +1,18 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +// Prevent implicit injection. +// Will generate "unresolved symbol" error during linking. +// Not using `= delete` as it would break injector SFINAE. +#define DONT_INJECT(T) explicit T(::lean::injector::DontInjectHelper, ...); + +namespace lean::injector { + struct DontInjectHelper { + explicit DontInjectHelper() = default; + }; +} // namespace lean::injector diff --git a/src/injector/lazy.hpp b/src/injector/lazy.hpp new file mode 100644 index 0000000..fa33ed9 --- /dev/null +++ b/src/injector/lazy.hpp @@ -0,0 +1,28 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean { + + template + using Lazy = boost::di::extension::lazy; + + template + using LazyRef = boost::di::extension::lazy; + + template + using LazyCRef = boost::di::extension::lazy; + + template + using LazySPtr = boost::di::extension::lazy>; + + template + using LazyUPtr = boost::di::extension::lazy>; + +} // namespace lean diff --git a/src/injector/node_injector.cpp b/src/injector/node_injector.cpp new file mode 100644 index 0000000..66aca6c --- /dev/null +++ b/src/injector/node_injector.cpp @@ -0,0 +1,166 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BOOST_DI_CFG_DIAGNOSTICS_LEVEL 2 +#define BOOST_DI_CFG_CTOR_LIMIT_SIZE 32 + +#include "injector/node_injector.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "app/configuration.hpp" +#include "app/impl/application_impl.hpp" +#include "app/impl/chain_spec_impl.hpp" +#include "app/impl/state_manager_impl.hpp" +#include "app/impl/watchdog.hpp" +#include "blockchain/impl/block_storage_impl.hpp" +#include "blockchain/impl/genesis_block_header_impl.hpp" +#include "clock/impl/clock_impl.hpp" +#include "crypto/hasher/hasher_impl.hpp" +#include "injector/bind_by_lambda.hpp" +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "metrics/impl/exposer_impl.hpp" +#include "metrics/impl/prometheus/handler_impl.hpp" +#include "modules/module.hpp" +#include "se/impl/async_dispatcher_impl.hpp" +#include "se/subscription.hpp" +#include "storage/in_memory/in_memory_spaced_storage.hpp" +#include "storage/in_memory/in_memory_storage.hpp" +#include "storage/rocksdb/rocksdb.hpp" + +namespace { + namespace di = boost::di; + namespace fs = std::filesystem; + using namespace lean; // NOLINT + + template + auto useConfig(C c) { + return boost::di::bind>().to( + std::move(c))[boost::di::override]; + } + + using injector::bind_by_lambda; + + template + auto makeApplicationInjector(std::shared_ptr logsys, + std::shared_ptr config, + Ts &&...args) { + // clang-format off + return di::make_injector( + di::bind.to(config), + di::bind.to(logsys), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to>(), + di::bind.to([](const auto &injector) { + return metrics::Exposer::Configuration{ + injector + .template create() + .openmetricsHttpEndpoint() + }; + }), + di::bind.to(), + //di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + + // user-defined overrides... + std::forward(args)...); + // clang-format on + } + + template + auto makeNodeInjector(std::shared_ptr logsys, + std::shared_ptr config, + Ts &&...args) { + return di::make_injector( + makeApplicationInjector(std::move(logsys), std::move(config)), + + // user-defined overrides... + std::forward(args)...); + } +} // namespace + +namespace lean::injector { + class NodeInjectorImpl { + public: + using Injector = + decltype(makeNodeInjector(std::shared_ptr(), + std::shared_ptr())); + + explicit NodeInjectorImpl(Injector injector) + : injector_{std::move(injector)} {} + + Injector injector_; + }; + + NodeInjector::NodeInjector(std::shared_ptr logsys, + std::shared_ptr config) + : pimpl_{std::make_unique( + makeNodeInjector(std::move(logsys), std::move(config)))} {} + + std::shared_ptr NodeInjector::injectApplication() { + return pimpl_->injector_ + .template create>(); + } + + std::unique_ptr NodeInjector::register_loader( + std::shared_ptr module) { + auto logsys = pimpl_->injector_ + .template create>(); + auto logger = logsys->getLogger("Modules", "lean"); + + std::unique_ptr loader{}; + + if ("ExampleLoader" == module->get_loader_id()) { + loader = pimpl_->injector_ + .create>(); + } else if ("NetworkingLoader" == module->get_loader_id()) { + loader = pimpl_->injector_ + .create>(); + } else if ("SynchronizerLoader" == module->get_loader_id()) { + loader = + pimpl_->injector_ + .create>(); + } else { + SL_CRITICAL(logger, + "> No loader found for: {} [{}]", + module->get_loader_id(), + module->get_path()); + return {}; + } + + loader->start(module); + + if (auto info = loader->module_info()) { + SL_INFO(logger, "> Module: {} [{}]", *info, module->get_path()); + } else { + SL_ERROR(logger, + "> No module info for: {} [{}]", + module->get_loader_id(), + module->get_path()); + } + return std::unique_ptr(loader.release()); + } +} // namespace lean::injector diff --git a/src/injector/node_injector.hpp b/src/injector/node_injector.hpp new file mode 100644 index 0000000..e9dbb32 --- /dev/null +++ b/src/injector/node_injector.hpp @@ -0,0 +1,49 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "se/subscription.hpp" + +namespace lean::log { + class LoggingSystem; +} // namespace lean::log + +namespace lean::app { + class Configuration; + class Application; +} // namespace lean::app + +namespace lean::loaders { + class Loader; +} // namespace lean::loaders + +namespace lean::modules { + class Module; +} // namespace lean::modules + +namespace lean::injector { + + /** + * Dependency injector for a universal node. Provides all major components + * required by the JamNode application. + */ + class NodeInjector final { + public: + explicit NodeInjector(std::shared_ptr logging_system, + std::shared_ptr configuration); + + std::shared_ptr injectApplication(); + std::unique_ptr register_loader( + std::shared_ptr module); + + protected: + std::shared_ptr pimpl_; + }; + +} // namespace lean::injector diff --git a/src/lean_types/block.hpp b/src/lean_types/block.hpp new file mode 100644 index 0000000..2bbdc68 --- /dev/null +++ b/src/lean_types/block.hpp @@ -0,0 +1,23 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "lean_types/block_body.hpp" + +namespace lean { + + struct Block { + uint64_t slot; + uint64_t proposer_index; + qtils::ByteArr<32> parent_root; + qtils::ByteArr<32> state_root; + BlockBody body; + }; + +} // namespace lean diff --git a/src/lean_types/block_body.hpp b/src/lean_types/block_body.hpp new file mode 100644 index 0000000..abae44b --- /dev/null +++ b/src/lean_types/block_body.hpp @@ -0,0 +1,23 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "lean_types/constants.hpp" +#include "lean_types/vote.hpp" + +namespace lean { + + struct BlockBody : ssz::ssz_container { + /// @note votes will be replaced by aggregated attestations. + ssz::list votes; + + SSZ_CONT(votes); + }; + +} // namespace lean diff --git a/src/lean_types/block_data.hpp b/src/lean_types/block_data.hpp new file mode 100644 index 0000000..be3bcd3 --- /dev/null +++ b/src/lean_types/block_data.hpp @@ -0,0 +1,77 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "block_body.hpp" +#include "block_header.hpp" +#include "block_signature.hpp" +#include "types.hpp" + +// SSZ-friendly optional encoded as List[T; max=1]. +// Empty list -> None, single element -> Some(T). +template +struct ssz_maybe : public ssz::ssz_container { + ssz::list inner; // length 0 or 1 + + SSZ_CONT(inner); + + // Convenience helpers + bool has_value() const { + return inner.size() == 1; + } + explicit operator bool() const { + return has_value(); + } + + void reset() { + inner.clear(); + } + void emplace(const T &v) { + inner = {}; + inner.push_back(v); + } + void emplace() { + inner = {}; + inner.push_back({}); + } + + T &value() { + return inner[0]; + } + const T &value() const { + return inner[0]; + } + + T &operator*() { + return value(); + } + const T &operator*() const { + return value(); + } + + T *operator->() { + return &value(); + } + const T *operator->() const { + return &value(); + } +}; + + +namespace lean { + struct BlockData : ssz::ssz_variable_size_container { + BlockHash hash; + ssz_maybe header; + ssz_maybe body; + ssz_maybe signature; + + SSZ_CONT(hash, header, body, signature); + }; +} // namespace lean diff --git a/src/lean_types/block_header.hpp b/src/lean_types/block_header.hpp new file mode 100644 index 0000000..c2b05f9 --- /dev/null +++ b/src/lean_types/block_header.hpp @@ -0,0 +1,69 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include + +#include "serde/serialization.hpp" + +namespace lean { + + /** + * @struct BlockHeader + * This is a lighter version of the block, used for referencing and + * verification. + */ + class BlockHeader : public ssz::ssz_container { + public: + /// The block’s slot number + Slot slot; + /// Index of the validator that proposed the block + ProposerIndex proposer_index; + /// Hash of the parent block + HeaderHash parent_root; + /// Hash of the post-state after the block is processed + StateRoot state_root; + /// The block’s body, containing further operations and data + BodyRoot body_root; + + BlockHeader() = default; + + /// Block hash if calculated + mutable std::optional hash_opt{}; + + CUSTOM_EQUALITY( + BlockHeader, slot, proposer_index, parent_root, state_root, body_root); + + SSZ_CONT(slot, proposer_index, parent_root, state_root, body_root); + + const HeaderHash &hash() const { + BOOST_ASSERT_MSG(hash_opt.has_value(), + "Hash must be calculated and saved before that"); + return hash_opt.value(); + } + + void updateHash(const crypto::Hasher &hasher) const { + auto enc_res = encode(*this); + BOOST_ASSERT_MSG(enc_res.has_value(), + "Header should be encoded errorless"); + hash_opt.emplace(hasher.blake2b_256(enc_res.value())); + } + + BlockIndex index() const { + return {slot, hash()}; + } + }; + + inline void calculateBlockHash(const BlockHeader &header, + const crypto::Hasher &hasher) { + header.updateHash(hasher); + } + +} // namespace lean diff --git a/src/lean_types/block_signature.hpp b/src/lean_types/block_signature.hpp new file mode 100644 index 0000000..8b4d8b8 --- /dev/null +++ b/src/lean_types/block_signature.hpp @@ -0,0 +1,15 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "types.hpp" + +namespace lean { + + using BlockSignature = OpaqueHash; + +} diff --git a/src/lean_types/chekpoint.hpp b/src/lean_types/chekpoint.hpp new file mode 100644 index 0000000..e5df1cb --- /dev/null +++ b/src/lean_types/chekpoint.hpp @@ -0,0 +1,22 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "types.hpp" + +namespace lean { + + struct Checkpoint : ssz::ssz_container { + qtils::ByteArr<32> root; + Slot slot; + + SSZ_CONT(root, slot); + }; + +} // namespace lean diff --git a/src/lean_types/config.hpp b/src/lean_types/config.hpp new file mode 100644 index 0000000..ae764c6 --- /dev/null +++ b/src/lean_types/config.hpp @@ -0,0 +1,19 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +namespace lean { + + /// @note temporary property to support simplified round robin block + /// production in absence of randao & deposit mechanisms + struct Config { + uint64_t num_validators; + uint64_t genesis_time; + }; + + +} // namespace lean diff --git a/src/lean_types/constants.hpp b/src/lean_types/constants.hpp new file mode 100644 index 0000000..4a2f7bc --- /dev/null +++ b/src/lean_types/constants.hpp @@ -0,0 +1,39 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +using qtils::literals::operator""_bytes; + +namespace lean { + + static constexpr uint64_t SLOT_DURATION_MS = 4000; // 4 seconds + static constexpr uint64_t INTERVALS_PER_SLOT = 4; // 4 intervals by 1 second + + // State list lengths + + static constexpr uint64_t HISTORICAL_ROOTS_LIMIT = + 1 << 18; // 262'144 roots, 12.1 days + static constexpr uint64_t VALIDATOR_REGISTRY_LIMIT = 1 << 12; // 4'096 val + + // Networking + + /// Maximum number of blocks in a single request + static constexpr uint64_t MAX_REQUEST_BLOCKS = 1 << 10; // 1024 + + using DomainType = qtils::ByteArr<4>; + + /// 4-byte domain for gossip message-id isolation of *invalid* snappy messages + static constexpr DomainType MESSAGE_DOMAIN_INVALID_SNAPPY = {0, 0, 0, 0}; + /// 4-byte domain for gossip message-id isolation of *valid* snappy messages + static constexpr DomainType MESSAGE_DOMAIN_VALID_SNAPPY = {1, 0, 0, 0}; + +} // namespace lean diff --git a/src/lean_types/justification.hpp b/src/lean_types/justification.hpp new file mode 100644 index 0000000..fddeb9e --- /dev/null +++ b/src/lean_types/justification.hpp @@ -0,0 +1,17 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace lean { + + using Justification = qtils::Tagged; + +} diff --git a/src/lean_types/signed_block.hpp b/src/lean_types/signed_block.hpp new file mode 100644 index 0000000..c1057ec --- /dev/null +++ b/src/lean_types/signed_block.hpp @@ -0,0 +1,16 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +namespace lean { + + struct SignedBlock { + Block message; + qtils::ByteArr<32> signature; + }; + +} // namespace lean diff --git a/src/lean_types/signed_vote.hpp b/src/lean_types/signed_vote.hpp new file mode 100644 index 0000000..6501a47 --- /dev/null +++ b/src/lean_types/signed_vote.hpp @@ -0,0 +1,19 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +namespace lean { + + struct SignedVote { + Vote data; + /// @note The signature type is still to be determined so Bytes32 is used in + /// the interim. The actual signature size is expected to be a lot larger + /// (~3 KiB). + qtils::ByteArr<32> signature; + }; + +} // namespace lean diff --git a/src/lean_types/state.hpp b/src/lean_types/state.hpp new file mode 100644 index 0000000..bd0e2ae --- /dev/null +++ b/src/lean_types/state.hpp @@ -0,0 +1,29 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +namespace lean { + + struct State { + Config : config; + uint64_t : slot; + BlockHeader : latest_block_header; + + Checkpoint : latest_justified; + Checkpoint : latest_finalized; + + List[Bytes32, HISTORICAL_ROOTS_LIMIT] : historical_block_hashes; + List[bool, HISTORICAL_ROOTS_LIMIT] : justified_slots; + + // Diverged from 3SF-mini.py: + // Flattened `justifications: Dict[str, List[bool]]` for SSZ compatibility + List[Bytes32, HISTORICAL_ROOTS_LIMIT] : justifications_roots; + Bitlist[HISTORICAL_ROOTS_LIMIT * VALIDATOR_REGISTRY_LIMIT] + : justifications_validators; + }; + +} // namespace lean diff --git a/src/lean_types/types.hpp b/src/lean_types/types.hpp new file mode 100644 index 0000000..8a9bf27 --- /dev/null +++ b/src/lean_types/types.hpp @@ -0,0 +1,132 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +namespace lean { + // stub types. must be refactored in future + + struct Stub {}; + + // blockchain types + + using OpaqueHash = qtils::ByteArr<32>; + + using BlockHash = OpaqueHash; + using HeaderHash = OpaqueHash; + using StateRoot = OpaqueHash; + using BodyRoot = OpaqueHash; + + using Slot = uint64_t; + + using ProposerIndex = uint64_t; + + struct BlockIndex { + Slot slot; + BlockHash hash; + auto operator<=>(const BlockIndex &other) const = default; + }; + + using BlockInfo = BlockIndex; + + using BlockNumber = Slot; + + using BlockId = std::variant; + + // networking types + + using PeerId = qtils::Tagged; // STUB + + /// Direction, in which to retrieve ordered data + enum class Direction : uint8_t { + /// from child to parent + ASCENDING = 0, + /// from parent to canonical child + DESCENDING = 1 + }; + + /// Request for blocks to another peer + struct BlocksRequest { + /// start from this block + BlockIndex from{}; + /// sequence direction + Direction direction{}; + /// maximum number of blocks to return; an implementation defined maximum is + /// used when unspecified + std::optional max{}; + bool multiple_justifications = true; + }; + + struct BlockAnnounce { + BlockAnnounce(const BlockAnnounce &) = delete; + }; + +} // namespace lean + +template <> +struct fmt::formatter { + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + return it; + } + + template + auto format(const lean::Stub &, FormatContext &ctx) const + -> decltype(ctx.out()) { + return fmt::format_to(ctx.out(), "stub"); + } +}; + +template <> +struct fmt::formatter { + // Presentation format: 's' - short, 'l' - long. + char presentation = 's'; + + // Parses format specifications of the form ['s' | 'l']. + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { + // Parse the presentation format and store it in the formatter: + auto it = ctx.begin(), end = ctx.end(); + if (it != end && (*it == 's' or *it == 'l')) { + presentation = *it++; + } + + // Check if reached the end of the range: + if (it != end && *it != '}') { + throw format_error("invalid format"); + } + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the BlockInfo using the parsed format specification (presentation) + // stored in this formatter. + template + auto format(const lean::BlockInfo &block_info, FormatContext &ctx) const + -> decltype(ctx.out()) { + // ctx.out() is an output iterator to write to. + + if (presentation == 's') { + return fmt::format_to( + ctx.out(), "{:0x} @ {}", block_info.hash, block_info.slot); + } + + return fmt::format_to( + ctx.out(), "{:0xx} @ {}", block_info.hash, block_info.slot); + } +}; + + +template +struct fmt::formatter> : formatter {}; diff --git a/src/lean_types/vote.hpp b/src/lean_types/vote.hpp new file mode 100644 index 0000000..4d7dfa3 --- /dev/null +++ b/src/lean_types/vote.hpp @@ -0,0 +1,23 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +namespace lean { + + struct Vote : public ssz::ssz_container { + uint64_t validator_id; + uint64_t slot; + Checkpoint head; + Checkpoint target; + Checkpoint source; + + SSZ_CONT(validator_id, slot, head, target, source); + }; + +} // namespace lean diff --git a/src/loaders/README b/src/loaders/README new file mode 100644 index 0000000..31be259 --- /dev/null +++ b/src/loaders/README @@ -0,0 +1 @@ +# loaders are locating here \ No newline at end of file diff --git a/src/loaders/impl/example_loader.hpp b/src/loaders/impl/example_loader.hpp new file mode 100644 index 0000000..652b577 --- /dev/null +++ b/src/loaders/impl/example_loader.hpp @@ -0,0 +1,130 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "modules/example/example.hpp" +#include "se/subscription.hpp" + +namespace lean::loaders { + + class ExampleLoader final + : public std::enable_shared_from_this, + public Loader, + public modules::ExampleModuleLoader { + std::shared_ptr> on_init_complete_; + + std::shared_ptr> on_loading_finished_; + + std::shared_ptr>> + on_request_; + + std::shared_ptr>> + on_response_; + + std::shared_ptr>> + on_notification_; + + public: + ExampleLoader(qtils::SharedRef logsys, + qtils::SharedRef se_manager) + : Loader(std::move(logsys), std::move(se_manager)) {} + + ExampleLoader(const ExampleLoader &) = delete; + ExampleLoader &operator=(const ExampleLoader &) = delete; + + ~ExampleLoader() override = default; + + void start(std::shared_ptr module) override { + set_module(module); + auto module_accessor = + get_module() + ->getFunctionFromLibrary< + std::weak_ptr, + modules::ExampleModuleLoader &, + std::shared_ptr>("query_module_instance"); + + if (not module_accessor) { + return; + } + + auto module_internal = (*module_accessor)(*this, logsys_); + + on_init_complete_ = se::SubscriberCreator::template create< + EventTypes::ExampleModuleIsLoaded>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &) { + if (auto m = module_internal.lock()) { + m->on_loaded_success(); + } + }); + + on_loading_finished_ = + se::SubscriberCreator::template create< + EventTypes::LoadingIsFinished>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &) { + if (auto m = module_internal.lock()) { + m->on_loading_is_finished(); + } + }); + + on_request_ = se::SubscriberCreator>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &msg) { + if (auto m = module_internal.lock()) { + m->on_request(msg); + } + }); + + on_response_ = se::SubscriberCreator>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &msg) { + if (auto m = module_internal.lock()) { + m->on_response(msg); + } + }); + + on_notification_ = + se::SubscriberCreator>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal](auto &msg) { + if (auto m = module_internal.lock()) { + m->on_notify(msg); + } + }); + + se_manager_->notify(lean::EventTypes::ExampleModuleIsLoaded); + } + + void dispatch_request(std::shared_ptr s) override { + se_manager_->notify(lean::EventTypes::ExampleRequest, s); + } + + void dispatch_response(std::shared_ptr s) override { + se_manager_->notify(lean::EventTypes::ExampleResponse, s); + } + + void dispatch_notify(std::shared_ptr s) override { + se_manager_->notify(lean::EventTypes::ExampleNotification, s); + } + }; +} // namespace lean::loaders diff --git a/src/loaders/impl/networking_loader.hpp b/src/loaders/impl/networking_loader.hpp new file mode 100644 index 0000000..4336e78 --- /dev/null +++ b/src/loaders/impl/networking_loader.hpp @@ -0,0 +1,128 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "modules/networking/networking.hpp" +#include "modules/shared/networking_types.tmp.hpp" +#include "se/subscription.hpp" + +namespace lean::loaders { + + class NetworkingLoader final + : public std::enable_shared_from_this, + public Loader, + public modules::NetworkingLoader { + log::Logger logger_; + + std::shared_ptr> on_init_complete_; + + std::shared_ptr> on_loading_finished_; + + std::shared_ptr< + BaseSubscriber>> + on_block_request_; + + public: + NetworkingLoader(std::shared_ptr logsys, + std::shared_ptr se_manager) + : Loader(std::move(logsys), std::move(se_manager)), + logger_(logsys_->getLogger("Networking", "networking_module")) {} + + NetworkingLoader(const NetworkingLoader &) = delete; + NetworkingLoader &operator=(const NetworkingLoader &) = delete; + + ~NetworkingLoader() override = default; + + void start(std::shared_ptr module) override { + set_module(module); + auto module_accessor = + get_module() + ->getFunctionFromLibrary, + modules::NetworkingLoader &, + std::shared_ptr>( + "query_module_instance"); + + if (not module_accessor) { + return; + } + + auto module_internal = (*module_accessor)(*this, logsys_); + + on_init_complete_ = se::SubscriberCreator::template create< + EventTypes::NetworkingIsLoaded>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &) { + if (auto m = module_internal.lock()) { + SL_TRACE(logger_, "Handle NetworkingIsLoaded"); + m->on_loaded_success(); + } + }); + + on_loading_finished_ = + se::SubscriberCreator::template create< + EventTypes::LoadingIsFinished>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &) { + if (auto m = module_internal.lock()) { + SL_TRACE(logger_, "Handle LoadingIsFinished"); + m->on_loading_is_finished(); + } + }); + + on_block_request_ = se::SubscriberCreator< + qtils::Empty, + std::shared_ptr>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &, const auto &msg) { + if (auto m = module_internal.lock()) { + SL_TRACE( + logger_, "Handle BlockRequest; rid={}", msg->ctx.rid); + m->on_block_request(msg); + } + }); + + + se_manager_->notify(lean::EventTypes::NetworkingIsLoaded); + } + + void dispatch_peer_connected( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch PeerConnected; peer={}", msg->peer); + se_manager_->notify(lean::EventTypes::PeerConnected, msg); + } + + void dispatch_peer_disconnected( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch PeerDisconnected; peer={}", msg->peer); + se_manager_->notify(lean::EventTypes::PeerDisconnected, msg); + } + + void dispatch_block_announce( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch BlockAnnounceReceived"); + se_manager_->notify(lean::EventTypes::BlockAnnounceReceived, msg); + } + + void dispatch_block_response( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch BlockResponse; rid={}", msg->ctx.rid); + se_manager_->notify(lean::EventTypes::BlockResponse, std::move(msg)); + } + }; +} // namespace lean::loaders diff --git a/src/loaders/impl/synchronizer_loader.hpp b/src/loaders/impl/synchronizer_loader.hpp new file mode 100644 index 0000000..122f2c8 --- /dev/null +++ b/src/loaders/impl/synchronizer_loader.hpp @@ -0,0 +1,114 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include + +#include "loaders/loader.hpp" +#include "log/logger.hpp" +#include "modules/synchronizer/synchronizer.hpp" +#include "se/subscription.hpp" + +namespace lean::loaders { + + class SynchronizerLoader final + : public std::enable_shared_from_this, + public Loader, + public modules::SynchronizerLoader { + log::Logger logger_; + + using InitCompleteSubscriber = BaseSubscriber; + std::shared_ptr on_init_complete_; + + std::shared_ptr< + BaseSubscriber>> + on_block_announce_; + + std::shared_ptr< + BaseSubscriber>> + on_block_response_; + + public: + SynchronizerLoader(std::shared_ptr logsys, + std::shared_ptr se_manager) + : Loader(std::move(logsys), std::move(se_manager)), + logger_(logsys_->getLogger("Synchronizer", "synchronizer_module")) {} + + SynchronizerLoader(const SynchronizerLoader &) = delete; + SynchronizerLoader &operator=(const SynchronizerLoader &) = delete; + + ~SynchronizerLoader() override = default; + + void start(std::shared_ptr module) override { + set_module(module); + auto module_accessor = + get_module() + ->getFunctionFromLibrary< + std::weak_ptr, + modules::SynchronizerLoader &, + std::shared_ptr>("query_module_instance"); + + if (not module_accessor) { + return; + } + + auto module_internal = (*module_accessor)(*this, logsys_); + + on_init_complete_ = se::SubscriberCreator::template create< + EventTypes::SynchronizerIsLoaded>( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &) { + if (auto m = module_internal.lock()) { + SL_TRACE(logger_, "Handle SynchronizerIsLoaded"); + m->on_loaded_success(); + } + }); + + on_block_announce_ = se::SubscriberCreator< + qtils::Empty, + std::shared_ptr>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &, const auto &msg) { + if (auto m = module_internal.lock()) { + SL_TRACE(logger_, "Handle BlockAnnounceReceived"); + m->on_block_announce(msg); + } + }); + + on_block_response_ = se::SubscriberCreator< + qtils::Empty, + std::shared_ptr>:: + template create( + *se_manager_, + SubscriptionEngineHandlers::kTest, + [module_internal, this](auto &, const auto &msg) { + if (auto m = module_internal.lock()) { + SL_TRACE( + logger_, "Handle BlockResponse; rid={}", msg->ctx.rid); + m->on_block_response(msg); + } + }); + + se_manager_->notify(lean::EventTypes::SynchronizerIsLoaded); + } + + void dispatch_block_request( + std::shared_ptr msg) override { + SL_TRACE(logger_, "Dispatch BlockRequest; rid={}", msg->ctx.rid); + se_manager_->notify(lean::EventTypes::BlockRequest, msg); + } + }; + +} // namespace lean::loaders diff --git a/src/loaders/loader.hpp b/src/loaders/loader.hpp new file mode 100644 index 0000000..b9b73d9 --- /dev/null +++ b/src/loaders/loader.hpp @@ -0,0 +1,73 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include + +#include "modules/module.hpp" +#include "se/subscription_fwd.hpp" + +namespace lean::log { + class LoggingSystem; +} // namespace lean::log + +namespace lean::modules { + class Module; +} // namespace lean::modules + +namespace lean::loaders { + + class Loader { + public: + Loader(const Loader &) = delete; + Loader &operator=(const Loader &) = delete; + + Loader(std::shared_ptr logsys, + std::shared_ptr se_manager) + : logsys_(std::move(logsys)), se_manager_(std::move(se_manager)) {} + + virtual ~Loader() = default; + virtual void start(std::shared_ptr) = 0; + + std::optional module_info() { + if (module_) { + auto result = + module_->getFunctionFromLibrary("module_info"); + if (result) { + return (*result)(); + } + } + return std::nullopt; + } + + std::shared_ptr get_logsys() { + return logsys_; + } + + std::shared_ptr get_module() { + if (!module_) { + throw std::runtime_error("Module not set"); + } + return module_; + } + + void set_module(std::shared_ptr module) { + module_ = module; + } + + protected: + qtils::SharedRef logsys_; + qtils::SharedRef se_manager_; + + private: + std::shared_ptr module_; + }; +} // namespace lean::loaders diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt new file mode 100644 index 0000000..f9ce07a --- /dev/null +++ b/src/log/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright Quadrivium LLC +# All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# + +add_library(logger + logger.cpp +) +target_link_libraries(logger + fmt::fmt + soralog::soralog + soralog::configurator_yaml + qtils::qtils + Boost::algorithm + ) diff --git a/src/log/formatters/filepath.hpp b/src/log/formatters/filepath.hpp new file mode 100644 index 0000000..12bc599 --- /dev/null +++ b/src/log/formatters/filepath.hpp @@ -0,0 +1,19 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +template <> +struct fmt::formatter + : fmt::formatter { + auto format(const std::filesystem::path &path, format_context &ctx) { + return fmt::formatter::format(path.native(), ctx); + } +}; diff --git a/src/log/formatters/optional.hpp b/src/log/formatters/optional.hpp new file mode 100644 index 0000000..b4f5d82 --- /dev/null +++ b/src/log/formatters/optional.hpp @@ -0,0 +1,35 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include +#include + +template