mirror of
https://github.com/logos-blockchain/logos-blockchain-module.git
synced 2026-05-24 01:59:50 +00:00
Merge 6b6a640998a7ba2c045e6ba8dd241c0e13fdafdf into 08bd849d5a4d331d7c206cdd291105c95147dc12
This commit is contained in:
commit
fc4c691cda
45
.github/workflows/ci.yml
vendored
Normal file
45
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: logos-co
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build module
|
||||
run: nix build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: logos-co
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Run unit tests
|
||||
run: nix build '.#checks.x86_64-linux.unit-tests' -L
|
||||
211
CMakeLists.txt
211
CMakeLists.txt
@ -1,203 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(logos-blockchain-module LANGUAGES CXX)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(LogosBlockchainModulePlugin LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# ---- Options ----
|
||||
set(LOGOS_CORE_ROOT "" CACHE PATH "Path to logos-core root directory.")
|
||||
set(LOGOS_BLOCKCHAIN_ROOT "" CACHE PATH "Path to logos-blockchain source root.")
|
||||
set(LOGOS_BLOCKCHAIN_LIB "" CACHE PATH "Path to prebuilt logos-blockchain lib.")
|
||||
set(LOGOS_BLOCKCHAIN_INCLUDE "" CACHE PATH "Path to prebuilt logos-blockchain include.")
|
||||
|
||||
set(HAS_LOGOS_CORE_ROOT FALSE)
|
||||
set(HAS_LOGOS_BLOCKCHAIN_ROOT FALSE)
|
||||
set(HAS_LOGOS_BLOCKCHAIN_LIB FALSE)
|
||||
set(HAS_LOGOS_BLOCKCHAIN_INCLUDE FALSE)
|
||||
|
||||
if (DEFINED LOGOS_CORE_ROOT AND NOT "${LOGOS_CORE_ROOT}" STREQUAL "")
|
||||
set(HAS_LOGOS_CORE_ROOT TRUE)
|
||||
endif()
|
||||
|
||||
if (DEFINED LOGOS_BLOCKCHAIN_ROOT AND NOT "${LOGOS_BLOCKCHAIN_ROOT}" STREQUAL "")
|
||||
set(HAS_LOGOS_BLOCKCHAIN_ROOT TRUE)
|
||||
endif()
|
||||
|
||||
if(DEFINED LOGOS_BLOCKCHAIN_LIB AND NOT "${LOGOS_BLOCKCHAIN_LIB}" STREQUAL "")
|
||||
set(HAS_LOGOS_BLOCKCHAIN_LIB TRUE)
|
||||
endif()
|
||||
|
||||
if(DEFINED LOGOS_BLOCKCHAIN_INCLUDE AND NOT "${LOGOS_BLOCKCHAIN_INCLUDE}" STREQUAL "")
|
||||
set(HAS_LOGOS_BLOCKCHAIN_INCLUDE TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT HAS_LOGOS_CORE_ROOT)
|
||||
message(FATAL_ERROR "LOGOS_CORE_ROOT must be set to the logos-core root directory.")
|
||||
endif()
|
||||
|
||||
if(HAS_LOGOS_BLOCKCHAIN_LIB AND HAS_LOGOS_BLOCKCHAIN_INCLUDE AND NOT HAS_LOGOS_BLOCKCHAIN_ROOT)
|
||||
message(STATUS "Using prebuilt logos-blockchain.")
|
||||
set(LOGOS_BLOCKCHAIN_PREBUILT TRUE)
|
||||
elseif(NOT HAS_LOGOS_BLOCKCHAIN_LIB AND NOT HAS_LOGOS_BLOCKCHAIN_INCLUDE AND HAS_LOGOS_BLOCKCHAIN_ROOT)
|
||||
message(STATUS "Building logos-blockchain from source.")
|
||||
set(LOGOS_BLOCKCHAIN_PREBUILT FALSE)
|
||||
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
||||
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
||||
else()
|
||||
message(FATAL_ERROR "Either both LOGOS_BLOCKCHAIN_LIB and LOGOS_BLOCKCHAIN_INCLUDE must be set for prebuilt logos-blockchain, or only LOGOS_BLOCKCHAIN_ROOT must be set for building from source.")
|
||||
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
|
||||
endif()
|
||||
|
||||
# ---- Qt ----
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
# ---- Directories ----
|
||||
set(WORKSPACE_ROOT "${CMAKE_BINARY_DIR}/workspace")
|
||||
file(MAKE_DIRECTORY "${WORKSPACE_ROOT}")
|
||||
|
||||
# ---- Logos Core SDK ----
|
||||
set(SDK_LIB "${LOGOS_CORE_ROOT}/lib/liblogos_sdk.a")
|
||||
set(SDK_INC "${LOGOS_CORE_ROOT}/include")
|
||||
|
||||
# ---- OS Specifics ----
|
||||
if(APPLE)
|
||||
set(DYLIB_EXT ".dylib")
|
||||
elseif(WIN32)
|
||||
set(DYLIB_EXT ".dll")
|
||||
set(IMPLIB_EXT ".lib")
|
||||
else()
|
||||
set(DYLIB_EXT ".so")
|
||||
endif()
|
||||
|
||||
# NOTE (Windows):
|
||||
# Rust cdylib typically produces:
|
||||
# - logos_blockchain.dll (runtime)
|
||||
# - logos_blockchain.lib (import lib)
|
||||
# The Windows build hasn't been yet, so adjust accordingly if the DLL is named without the 'lib' prefix.
|
||||
|
||||
# ---- Logos Blockchain (build OR consume) ----
|
||||
if(LOGOS_BLOCKCHAIN_PREBUILT)
|
||||
set(LOGOS_BLOCKCHAIN_DYLIB "${LOGOS_BLOCKCHAIN_LIB}/liblogos_blockchain${DYLIB_EXT}")
|
||||
|
||||
if(WIN32)
|
||||
set(LOGOS_BLOCKCHAIN_IMPLIB "${LOGOS_BLOCKCHAIN_LIB}/logos_blockchain${IMPLIB_EXT}")
|
||||
endif()
|
||||
|
||||
add_custom_target(logos_blockchain_libs)
|
||||
|
||||
else()
|
||||
find_program(CARGO_EXECUTABLE cargo REQUIRED)
|
||||
|
||||
set(CARGO_TARGET_DIR "${WORKSPACE_ROOT}/logos-blockchain/target")
|
||||
set(INTERNAL_STAGE "${WORKSPACE_ROOT}/stage")
|
||||
set(INTERNAL_STAGE_LIB "${INTERNAL_STAGE}/lib")
|
||||
set(INTERNAL_STAGE_INCLUDE "${INTERNAL_STAGE}/include")
|
||||
file(MAKE_DIRECTORY "${CARGO_TARGET_DIR}" "${INTERNAL_STAGE_LIB}" "${INTERNAL_STAGE_INCLUDE}")
|
||||
|
||||
set(LOGOS_BLOCKCHAIN_LIB "${INTERNAL_STAGE_LIB}")
|
||||
set(LOGOS_BLOCKCHAIN_INCLUDE "${INTERNAL_STAGE_INCLUDE}")
|
||||
|
||||
set(LOGOS_BLOCKCHAIN_DYLIB "${INTERNAL_STAGE_LIB}/liblogos_blockchain${DYLIB_EXT}")
|
||||
set(LOGOS_BLOCKCHAIN_HEADER "${INTERNAL_STAGE_INCLUDE}/logos_blockchain.h")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${LOGOS_BLOCKCHAIN_DYLIB}"
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
CARGO_TARGET_DIR=${CARGO_TARGET_DIR}
|
||||
${CARGO_EXECUTABLE} build --release
|
||||
--package logos-blockchain-c
|
||||
--manifest-path "${LOGOS_BLOCKCHAIN_ROOT}/Cargo.toml"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CARGO_TARGET_DIR}/release/liblogos_blockchain${DYLIB_EXT}"
|
||||
"${LOGOS_BLOCKCHAIN_DYLIB}"
|
||||
DEPENDS "${LOGOS_BLOCKCHAIN_ROOT}/Cargo.toml"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${LOGOS_BLOCKCHAIN_HEADER}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${LOGOS_BLOCKCHAIN_ROOT}/c-bindings/logos_blockchain.h"
|
||||
"${LOGOS_BLOCKCHAIN_HEADER}"
|
||||
DEPENDS "${LOGOS_BLOCKCHAIN_DYLIB}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(logos_blockchain_libs DEPENDS "${LOGOS_BLOCKCHAIN_DYLIB}" "${LOGOS_BLOCKCHAIN_HEADER}")
|
||||
endif()
|
||||
|
||||
# ---- Imported targets ----
|
||||
add_library(logos_blockchain_interface SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(logos_blockchain_interface PROPERTIES
|
||||
IMPORTED_LOCATION "${LOGOS_BLOCKCHAIN_DYLIB}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LOGOS_BLOCKCHAIN_INCLUDE}"
|
||||
# Universal module — generated_code/ is picked up automatically by LogosModule.cmake
|
||||
logos_module(
|
||||
NAME liblogos_blockchain_module
|
||||
SOURCES
|
||||
src/logos_blockchain_module.h
|
||||
src/logos_blockchain_module.cpp
|
||||
EXTERNAL_LIBS
|
||||
logos_blockchain
|
||||
)
|
||||
|
||||
if(NOT LOGOS_BLOCKCHAIN_PREBUILT)
|
||||
add_dependencies(logos_blockchain_interface logos_blockchain_libs)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(logos_blockchain_interface PROPERTIES IMPORTED_IMPLIB "${LOGOS_BLOCKCHAIN_IMPLIB}")
|
||||
endif()
|
||||
|
||||
add_library(logos_core STATIC IMPORTED)
|
||||
set_target_properties(logos_core PROPERTIES
|
||||
IMPORTED_LOCATION "${SDK_LIB}"
|
||||
)
|
||||
|
||||
add_library(logos_cpp_sdk INTERFACE)
|
||||
target_include_directories(logos_cpp_sdk INTERFACE "${SDK_INC}" "${SDK_INC}/cpp")
|
||||
|
||||
# ---- Plugin ----
|
||||
set(PLUGIN_TARGET logos_blockchain_module)
|
||||
|
||||
qt_add_plugin(${PLUGIN_TARGET} CLASS_NAME LogosBlockchainModule)
|
||||
|
||||
target_sources(${PLUGIN_TARGET} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/logos_blockchain_module.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET ${PLUGIN_TARGET} PROPERTY PUBLIC_HEADER
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/i_logos_blockchain_module.h
|
||||
)
|
||||
|
||||
target_include_directories(${PLUGIN_TARGET} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_link_libraries(${PLUGIN_TARGET} PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::RemoteObjects
|
||||
logos_blockchain_interface
|
||||
logos_cpp_sdk
|
||||
logos_core
|
||||
)
|
||||
|
||||
target_compile_definitions(${PLUGIN_TARGET} PRIVATE
|
||||
LOGOS_BLOCKCHAIN_MODULE_METADATA_FILE="${CMAKE_CURRENT_SOURCE_DIR}/metadata.json"
|
||||
)
|
||||
|
||||
add_dependencies(${PLUGIN_TARGET} logos_blockchain_libs)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(${PLUGIN_TARGET} PROPERTIES
|
||||
BUILD_RPATH "@loader_path"
|
||||
INSTALL_RPATH "@loader_path"
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(${PLUGIN_TARGET} PROPERTIES
|
||||
BUILD_RPATH "$ORIGIN"
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
)
|
||||
endif()
|
||||
|
||||
# ---- Install ----
|
||||
install(TARGETS ${PLUGIN_TARGET}
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
PUBLIC_HEADER DESTINATION include
|
||||
)
|
||||
install(DIRECTORY "${LOGOS_BLOCKCHAIN_INCLUDE}/" DESTINATION include)
|
||||
install(FILES "${LOGOS_BLOCKCHAIN_DYLIB}" DESTINATION lib)
|
||||
|
||||
38
README.md
38
README.md
@ -1,38 +1,10 @@
|
||||
# Logos Blockchain Module
|
||||
|
||||
### Setup
|
||||
A Logos core module that wraps the [logos-blockchain](https://github.com/logos-blockchain/logos-blockchain) C bindings and ships the zk circuit binaries needed at runtime.
|
||||
|
||||
#### IDE
|
||||
### Build and inspect
|
||||
|
||||
If you're using an IDE with CMake integration make sure it points to the same cmake directory as the `justfile`, which defaults to `build`.
|
||||
```bash
|
||||
nix build '.#lgx'
|
||||
```
|
||||
|
||||
This will reduce friction when working on the project.
|
||||
|
||||
#### Nix
|
||||
|
||||
* Use `nix flake update` to bring all nix context and packages
|
||||
* Use `nix build` to build the package
|
||||
* Use `nix run` to launch the module-viewer and check your module loads properly
|
||||
* Use `nix develop` to setup your IDE
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Nix + IDE Integration
|
||||
If your IDE reports that a file doesn't belong to the project or that files cannot be found, the CMake cache
|
||||
is likely missing the Nix-provided paths. This happens when the IDE runs CMake on its own, outside the Nix
|
||||
environment, leaving the required paths empty.
|
||||
|
||||
To fix it:
|
||||
|
||||
1. **Regenerate the cache from within the Nix shell**
|
||||
|
||||
This provides the required Nix paths and writes them into `build/CMakeCache.txt`:
|
||||
```bash
|
||||
nix develop -c just configure
|
||||
```
|
||||
|
||||
2. **Reload the CMake project without resetting the cache**
|
||||
|
||||
If on RustRover: Open the CMake tool window (**View → Tool Windows → CMake**) and click the **Reload** button (↺) in the toolbar.
|
||||
|
||||
> Resetting the cache would wipe the paths you just wrote, so make sure to reload only.
|
||||
|
||||
2018
flake.lock
generated
2018
flake.lock
generated
File diff suppressed because it is too large
Load Diff
183
flake.nix
183
flake.nix
@ -2,162 +2,45 @@
|
||||
description = "Logos Blockchain Module - Qt6 Plugin";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.follows = "logos-liblogos/nixpkgs";
|
||||
|
||||
logos-liblogos.url = "github:logos-co/logos-liblogos";
|
||||
logos-core.url = "github:logos-co/logos-cpp-sdk";
|
||||
|
||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
||||
logos-blockchain.url = "github:logos-blockchain/logos-blockchain?rev=88941ff33f2e028591b9d0ed2549a328d54f0cfa"; # pre-0.1.3 + potential note fixes
|
||||
|
||||
logos-module-viewer.url = "github:logos-co/logos-module-viewer";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
logos-core,
|
||||
logos-blockchain,
|
||||
logos-module-viewer,
|
||||
...
|
||||
}:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
outputs = inputs@{ logos-module-builder, ... }:
|
||||
logos-module-builder.lib.mkLogosModule {
|
||||
src = ./.;
|
||||
configFile = ./metadata.json;
|
||||
flakeInputs = inputs;
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
externalLibInputs = {
|
||||
logos_blockchain = inputs.logos-blockchain;
|
||||
};
|
||||
|
||||
forAll = lib.genAttrs systems;
|
||||
tests = {
|
||||
dir = ./tests;
|
||||
mockCLibs = [ "logos_blockchain" ];
|
||||
};
|
||||
|
||||
mkPkgs = system: import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
packages = forAll (
|
||||
system:
|
||||
let
|
||||
pkgs = mkPkgs system;
|
||||
llvmPkgs = pkgs.llvmPackages;
|
||||
preConfigure = { externalLibs }: ''
|
||||
if [ -d "${externalLibs.logos_blockchain}/circuits" ]; then
|
||||
echo "Staging zk circuits from logos-blockchain..."
|
||||
cp -r "${externalLibs.logos_blockchain}/circuits" ./circuits
|
||||
chmod -R u+w ./circuits
|
||||
else
|
||||
echo "WARNING: no circuits/ found in logos-blockchain derivation"
|
||||
fi
|
||||
'';
|
||||
|
||||
logosCore = logos-core.packages.${system}.default;
|
||||
logosBlockchainC = logos-blockchain.packages.${system}.logos-blockchain-c;
|
||||
|
||||
logosBlockchainModule = pkgs.stdenv.mkDerivation {
|
||||
pname = "logos-blockchain-module";
|
||||
version = "dev";
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
pkgs.ninja
|
||||
pkgs.pkg-config
|
||||
pkgs.qt6.wrapQtAppsHook
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qtremoteobjects
|
||||
pkgs.qt6.qttools
|
||||
llvmPkgs.clang
|
||||
llvmPkgs.libclang
|
||||
logosBlockchainC
|
||||
]
|
||||
++ lib.optionals pkgs.stdenv.isDarwin [
|
||||
pkgs.libiconv
|
||||
pkgs.cacert
|
||||
];
|
||||
|
||||
LIBCLANG_PATH = "${llvmPkgs.libclang.lib}/lib";
|
||||
CLANG_PATH = "${llvmPkgs.clang}/bin/clang";
|
||||
SSL_CERT_FILE = lib.optionalString pkgs.stdenv.isDarwin "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
|
||||
cmakeFlags = [
|
||||
"-DLOGOS_CORE_ROOT=${logosCore}"
|
||||
"-DLOGOS_BLOCKCHAIN_LIB=${logosBlockchainC}/lib"
|
||||
"-DLOGOS_BLOCKCHAIN_INCLUDE=${logosBlockchainC}/include"
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir $out/share
|
||||
cp -r ${logosBlockchainC}/circuits $out/share
|
||||
'';
|
||||
|
||||
# Logos Core Edge-case
|
||||
# The current version of Logos Core expects circuits' binaries under `lib/circuits/`.
|
||||
# Until we address this in Logos Core, we use this hook to include to ensure the circuits' binaries
|
||||
# are included in the binary bundle and avoid the circuits being mangled by Nix (which did that when
|
||||
# copying them in a previous phase).
|
||||
postFixup = ''
|
||||
cp -r ${logosBlockchainC}/circuits $out/lib/circuits
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
lib = logosBlockchainModule;
|
||||
default = logosBlockchainModule;
|
||||
}
|
||||
);
|
||||
|
||||
apps = forAll (
|
||||
system:
|
||||
let
|
||||
pkgs = mkPkgs system;
|
||||
logosBlockchainModuleLib = self.packages.${system}.lib;
|
||||
logosModuleViewer = logos-module-viewer.packages.${system}.default;
|
||||
extension = if pkgs.stdenv.isDarwin then "dylib"
|
||||
else if pkgs.stdenv.hostPlatform.isWindows then "dll"
|
||||
else "so";
|
||||
inspectModule = {
|
||||
type = "app";
|
||||
program =
|
||||
"${pkgs.writeShellScriptBin "inspect-module" ''
|
||||
exec ${logosModuleViewer}/bin/logos-module-viewer \
|
||||
--module ${logosBlockchainModuleLib}/lib/liblogos_blockchain_module.${extension}
|
||||
''}/bin/inspect-module";
|
||||
};
|
||||
in
|
||||
{
|
||||
inspect-module = inspectModule;
|
||||
default = inspectModule;
|
||||
}
|
||||
);
|
||||
|
||||
devShells = forAll (
|
||||
system:
|
||||
let
|
||||
pkgs = mkPkgs system;
|
||||
pkg = self.packages.${system}.default;
|
||||
logosCore = logos-core.packages.${system}.default;
|
||||
logosBlockchainC = logos-blockchain.packages.${system}.logos-blockchain-c;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
inputsFrom = [ pkg ];
|
||||
|
||||
inherit (pkg)
|
||||
LIBCLANG_PATH
|
||||
CLANG_PATH;
|
||||
|
||||
LOGOS_CORE_ROOT = "${logosCore}";
|
||||
LOGOS_BLOCKCHAIN_LIB = "${logosBlockchainC}/lib";
|
||||
LOGOS_BLOCKCHAIN_INCLUDE = "${logosBlockchainC}/include";
|
||||
|
||||
shellHook = ''
|
||||
BLUE='\e[1;34m'
|
||||
GREEN='\e[1;32m'
|
||||
RESET='\e[0m'
|
||||
|
||||
echo -e "\n''${BLUE}=== Logos Blockchain Module Development Environment ===''${RESET}"
|
||||
echo -e "''${GREEN}LOGOS_CORE_ROOT:''${RESET} $LOGOS_CORE_ROOT"
|
||||
echo -e "''${GREEN}LOGOS_BLOCKCHAIN_LIB:''${RESET} $LOGOS_BLOCKCHAIN_LIB"
|
||||
echo -e "''${GREEN}LOGOS_BLOCKCHAIN_INCLUDE:''${RESET} $LOGOS_BLOCKCHAIN_INCLUDE"
|
||||
echo -e "''${BLUE}---------------------------------------------------------''${RESET}"
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
# Logos Core Edge-case
|
||||
# The current version of Logos Core expects circuits' binaries under `lib/circuits/`.
|
||||
# Until we address this in Logos Core, we use this hook to include to ensure the circuits' binaries
|
||||
# are included in the binary bundle and avoid the circuits being mangled by Nix (which did that when
|
||||
# copying them in a previous phase).
|
||||
postInstall = ''
|
||||
if [ -d "$LOGOS_MODULE_SOURCE_DIR/circuits" ]; then
|
||||
cp -r "$LOGOS_MODULE_SOURCE_DIR/circuits" "$out/lib/circuits"
|
||||
chmod -R u+w "$out/lib/circuits"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
10
justfile
10
justfile
@ -1,13 +1,13 @@
|
||||
default: build
|
||||
|
||||
# Inside `nix develop` / `ws develop logos-blockchain-module` the module-builder
|
||||
# provides LOGOS_CPP_SDK_ROOT and LOGOS_MODULE_BUILDER_ROOT — CMake picks them
|
||||
# up automatically via the logos_module() macro, no explicit -D flags needed.
|
||||
configure:
|
||||
cmake -S . -B build -G Ninja \
|
||||
${LOGOS_CORE_ROOT:+-DLOGOS_CORE_ROOT="$LOGOS_CORE_ROOT"} \
|
||||
${LOGOS_BLOCKCHAIN_LIB:+-DLOGOS_BLOCKCHAIN_LIB="$LOGOS_BLOCKCHAIN_LIB"} \
|
||||
${LOGOS_BLOCKCHAIN_INCLUDE:+-DLOGOS_BLOCKCHAIN_INCLUDE="$LOGOS_BLOCKCHAIN_INCLUDE"}
|
||||
cmake -S . -B build -G Ninja
|
||||
|
||||
build: configure
|
||||
cmake --build build --parallel --target logos_blockchain_module
|
||||
cmake --build build --parallel --target liblogos_blockchain_module_module_plugin
|
||||
|
||||
clean:
|
||||
rm -rf build result
|
||||
|
||||
@ -4,17 +4,34 @@
|
||||
"description": "Logos blockchain node for logos-core",
|
||||
"author": "Logos Blockchain Team",
|
||||
"type": "core",
|
||||
"interface": "universal",
|
||||
"codegen": {
|
||||
"impl_header": "logos_blockchain_module.h"
|
||||
},
|
||||
"category": "blockchain",
|
||||
"main": "liblogos_blockchain_module",
|
||||
"main": "liblogos_blockchain_module_plugin",
|
||||
"dependencies": [],
|
||||
"capabilities": [],
|
||||
"include": [
|
||||
"liblogos_blockchain.dylib",
|
||||
"liblogos_blockchain.so",
|
||||
"liblogos_blockchain.dll",
|
||||
"liblogos_blockchain_module.dylib",
|
||||
"liblogos_blockchain_module.so",
|
||||
"liblogos_blockchain_module.dll",
|
||||
"liblogos_blockchain_module_plugin.dylib",
|
||||
"liblogos_blockchain_module_plugin.so",
|
||||
"liblogos_blockchain_module_plugin.dll",
|
||||
"circuits"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"nix": {
|
||||
"packages": {
|
||||
"build": [],
|
||||
"runtime": ["nlohmann_json", "boost"]
|
||||
},
|
||||
"external_libraries": [
|
||||
{ "name": "logos_blockchain" }
|
||||
],
|
||||
"cmake": {
|
||||
"extra_include_dirs": ["lib"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
#ifndef I_LOGOS_BLOCKCHAIN_MODULE_API_H
|
||||
#define I_LOGOS_BLOCKCHAIN_MODULE_API_H
|
||||
|
||||
#include <QString>
|
||||
#include <core/interface.h>
|
||||
|
||||
class ILogosBlockchainModule {
|
||||
public:
|
||||
virtual ~ILogosBlockchainModule() = default;
|
||||
|
||||
// Logos Core
|
||||
virtual void initLogos(LogosAPI* logos_api_instance) = 0;
|
||||
|
||||
// ---- Node ----
|
||||
|
||||
// Lifecycle
|
||||
virtual int generate_user_config(const QVariantMap& args) = 0;
|
||||
virtual int generate_user_config_from_str(const QString& args) = 0;
|
||||
virtual int start(const QString& config_path, const QString& deployment) = 0;
|
||||
virtual int stop() = 0;
|
||||
|
||||
// Wallet
|
||||
virtual QString wallet_get_balance(const QString& address_hex) = 0;
|
||||
virtual QString wallet_transfer_funds(
|
||||
const QString& change_public_key,
|
||||
const QStringList& sender_addresses,
|
||||
const QString& recipient_address,
|
||||
const QString& amount,
|
||||
const QString& optional_tip_hex
|
||||
) = 0;
|
||||
virtual QStringList wallet_get_known_addresses() = 0;
|
||||
|
||||
// Blend
|
||||
virtual QString blend_join_as_core_node(
|
||||
const QString& provider_id_hex,
|
||||
const QString& zk_id_hex,
|
||||
const QString& locked_note_id_hex,
|
||||
const QStringList& locators
|
||||
) = 0;
|
||||
|
||||
// Storage
|
||||
virtual QString get_block(const QString& header_id_hex) = 0;
|
||||
virtual QString get_blocks(quint64 from_slot, quint64 to_slot) = 0;
|
||||
virtual QString get_transaction(const QString& tx_hash_hex) = 0;
|
||||
|
||||
// Cryptarchia
|
||||
virtual QString get_cryptarchia_info() = 0;
|
||||
};
|
||||
|
||||
#define ILogosBlockchainModule_iid "org.logos.ilogosblockchainmodule"
|
||||
Q_DECLARE_INTERFACE(ILogosBlockchainModule, ILogosBlockchainModule_iid)
|
||||
|
||||
#endif
|
||||
@ -1,62 +1,86 @@
|
||||
#include "logos_blockchain_module.h"
|
||||
#include "logos_api_client.h"
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVariant>
|
||||
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Define static member
|
||||
LogosBlockchainModule* LogosBlockchainModule::s_instance = nullptr;
|
||||
|
||||
namespace {
|
||||
// Rust `File::open` / `deserialize_config_at_path` only accept real filesystem paths. QML often
|
||||
// passes `file:///...` URLs; strip to a local path when applicable.
|
||||
std::string localPathFromFileUrl(const std::string& s) {
|
||||
if (s.size() >= 7 && s.substr(0, 7) == "file://") return s.substr(7);
|
||||
if (s.size() >= 5 && s.substr(0, 5) == "file:") return s.substr(5);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Use the C API type Hash (from logos_blockchain.h) to define address/hash byte size.
|
||||
constexpr int ADDRESS_BYTES = sizeof(Hash);
|
||||
constexpr int ADDRESS_HEX_LEN = ADDRESS_BYTES * 2;
|
||||
|
||||
// Parses ADDRESS_HEX_LEN hex chars (optional 0x prefix) to ADDRESS_BYTES. Returns empty QByteArray on error.
|
||||
QByteArray parse_address_hex(const QString& address_hex) {
|
||||
QString hex = address_hex.trimmed();
|
||||
if (hex.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
|
||||
hex = hex.mid(2);
|
||||
if (hex.length() != ADDRESS_HEX_LEN)
|
||||
std::vector<uint8_t> parse_address_hex(const std::string& address_hex) {
|
||||
std::string hex = address_hex;
|
||||
boost::algorithm::trim(hex);
|
||||
if (hex.size() >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X'))
|
||||
hex = hex.substr(2);
|
||||
if (static_cast<int>(hex.size()) != ADDRESS_HEX_LEN)
|
||||
return {};
|
||||
QByteArray bytes = QByteArray::fromHex(hex.toUtf8());
|
||||
if (bytes.size() != ADDRESS_BYTES)
|
||||
try {
|
||||
std::string decoded;
|
||||
boost::algorithm::unhex(hex.begin(), hex.end(), std::back_inserter(decoded));
|
||||
return {decoded.begin(), decoded.end()};
|
||||
} catch (const boost::algorithm::non_hex_input&) {
|
||||
return {};
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
std::string bytes_to_hex(const uint8_t* data, size_t len) {
|
||||
std::string out;
|
||||
out.reserve(len * 2);
|
||||
boost::algorithm::hex_lower(data, data + len, std::back_inserter(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Wrapper that owns data and provides GenerateConfigArgs
|
||||
struct OwnedGenerateConfigArgs {
|
||||
std::vector<QByteArray> initial_peers_data;
|
||||
std::vector<std::string> initial_peers_data;
|
||||
std::vector<const char*> initial_peers_ptrs;
|
||||
uint32_t initial_peers_count_val;
|
||||
QByteArray output_data;
|
||||
std::string output_data;
|
||||
uint16_t net_port_val;
|
||||
uint16_t blend_port_val;
|
||||
QByteArray http_addr_data;
|
||||
QByteArray external_address_data;
|
||||
std::string http_addr_data;
|
||||
std::string external_address_data;
|
||||
bool no_public_ip_check_val;
|
||||
QByteArray custom_deployment_config_path_data;
|
||||
std::string custom_deployment_config_path_data;
|
||||
Deployment deployment_val{};
|
||||
QByteArray state_path_data;
|
||||
std::string state_path_data;
|
||||
|
||||
// The FFI struct with pointers into owned data
|
||||
GenerateConfigArgs ffi_args{};
|
||||
|
||||
// Constructor that populates both owned data and FFI struct
|
||||
explicit OwnedGenerateConfigArgs(const QVariantMap& args) {
|
||||
// initial_peers (QStringList -> const char**)
|
||||
if (args.contains("initial_peers")) {
|
||||
QStringList peers = args["initial_peers"].toStringList();
|
||||
initial_peers_count_val = static_cast<uint32_t>(peers.size());
|
||||
|
||||
for (const QString& peer : peers) {
|
||||
initial_peers_data.push_back(peer.toUtf8());
|
||||
// Constructor that populates both owned data and FFI struct from JSON
|
||||
explicit OwnedGenerateConfigArgs(const json& args) {
|
||||
// initial_peers (JSON array -> const char**)
|
||||
if (args.contains("initial_peers") && args["initial_peers"].is_array()) {
|
||||
for (const auto& peer : args["initial_peers"]) {
|
||||
initial_peers_data.push_back(peer.get<std::string>());
|
||||
}
|
||||
for (const QByteArray& data : initial_peers_data) {
|
||||
initial_peers_ptrs.push_back(data.constData());
|
||||
initial_peers_count_val = static_cast<uint32_t>(initial_peers_data.size());
|
||||
|
||||
for (const std::string& data : initial_peers_data) {
|
||||
initial_peers_ptrs.push_back(data.c_str());
|
||||
}
|
||||
|
||||
ffi_args.initial_peers = initial_peers_ptrs.data();
|
||||
@ -66,49 +90,49 @@ namespace {
|
||||
ffi_args.initial_peers_count = nullptr;
|
||||
}
|
||||
|
||||
// output (QString -> const char*)
|
||||
if (args.contains("output")) {
|
||||
output_data = args["output"].toString().toUtf8();
|
||||
ffi_args.output = output_data.constData();
|
||||
// output (string -> const char*)
|
||||
if (args.contains("output") && args["output"].is_string()) {
|
||||
output_data = args["output"].get<std::string>();
|
||||
ffi_args.output = output_data.c_str();
|
||||
} else {
|
||||
ffi_args.output = nullptr;
|
||||
}
|
||||
|
||||
// net_port (int -> const uint16_t*)
|
||||
if (args.contains("net_port")) {
|
||||
net_port_val = static_cast<uint16_t>(args["net_port"].toInt());
|
||||
if (args.contains("net_port") && args["net_port"].is_number_integer()) {
|
||||
net_port_val = static_cast<uint16_t>(args["net_port"].get<int>());
|
||||
ffi_args.net_port = &net_port_val;
|
||||
} else {
|
||||
ffi_args.net_port = nullptr;
|
||||
}
|
||||
|
||||
// blend_port (int -> const uint16_t*)
|
||||
if (args.contains("blend_port")) {
|
||||
blend_port_val = static_cast<uint16_t>(args["blend_port"].toInt());
|
||||
if (args.contains("blend_port") && args["blend_port"].is_number_integer()) {
|
||||
blend_port_val = static_cast<uint16_t>(args["blend_port"].get<int>());
|
||||
ffi_args.blend_port = &blend_port_val;
|
||||
} else {
|
||||
ffi_args.blend_port = nullptr;
|
||||
}
|
||||
|
||||
// http_addr (QString -> const char*)
|
||||
if (args.contains("http_addr")) {
|
||||
http_addr_data = args["http_addr"].toString().toUtf8();
|
||||
ffi_args.http_addr = http_addr_data.constData();
|
||||
// http_addr (string -> const char*)
|
||||
if (args.contains("http_addr") && args["http_addr"].is_string()) {
|
||||
http_addr_data = args["http_addr"].get<std::string>();
|
||||
ffi_args.http_addr = http_addr_data.c_str();
|
||||
} else {
|
||||
ffi_args.http_addr = nullptr;
|
||||
}
|
||||
|
||||
// external_address (QString -> const char*)
|
||||
if (args.contains("external_address")) {
|
||||
external_address_data = args["external_address"].toString().toUtf8();
|
||||
ffi_args.external_address = external_address_data.constData();
|
||||
// external_address (string -> const char*)
|
||||
if (args.contains("external_address") && args["external_address"].is_string()) {
|
||||
external_address_data = args["external_address"].get<std::string>();
|
||||
ffi_args.external_address = external_address_data.c_str();
|
||||
} else {
|
||||
ffi_args.external_address = nullptr;
|
||||
}
|
||||
|
||||
// no_public_ip_check (bool -> const bool*)
|
||||
if (args.contains("no_public_ip_check")) {
|
||||
no_public_ip_check_val = args["no_public_ip_check"].toBool();
|
||||
if (args.contains("no_public_ip_check") && args["no_public_ip_check"].is_boolean()) {
|
||||
no_public_ip_check_val = args["no_public_ip_check"].get<bool>();
|
||||
ffi_args.no_public_ip_check = &no_public_ip_check_val;
|
||||
} else {
|
||||
ffi_args.no_public_ip_check = nullptr;
|
||||
@ -117,22 +141,21 @@ namespace {
|
||||
// deployment (const struct Deployment*)
|
||||
// Expected format: { "deployment": { "well_known_deployment": "devnet" } }
|
||||
// OR: { "deployment": { "config_path": "/path/to/config" } }
|
||||
if (args.contains("deployment")) {
|
||||
const QVariantMap deployment = args["deployment"].toMap(); // NOLINT: Move definition to if-statement
|
||||
if (args.contains("deployment") && args["deployment"].is_object()) {
|
||||
const auto& deployment = args["deployment"];
|
||||
|
||||
if (deployment.contains("well_known_deployment")) {
|
||||
if (deployment.contains("well_known_deployment") && deployment["well_known_deployment"].is_string()) {
|
||||
deployment_val.deployment_type = DeploymentType::WellKnown;
|
||||
const QString wellknown =
|
||||
deployment["well_known_deployment"].toString(); // NOLINT: Move definition to if-statement
|
||||
const std::string wellknown = deployment["well_known_deployment"].get<std::string>();
|
||||
if (wellknown == "devnet") {
|
||||
deployment_val.well_known_deployment = WellKnownDeployment::Devnet;
|
||||
}
|
||||
deployment_val.custom_deployment_config_path = nullptr;
|
||||
} else if (deployment.contains("config_path")) {
|
||||
} else if (deployment.contains("config_path") && deployment["config_path"].is_string()) {
|
||||
deployment_val.deployment_type = DeploymentType::Custom;
|
||||
deployment_val.well_known_deployment = static_cast<WellKnownDeployment>(0);
|
||||
custom_deployment_config_path_data = deployment["config_path"].toString().toUtf8();
|
||||
deployment_val.custom_deployment_config_path = custom_deployment_config_path_data.constData();
|
||||
custom_deployment_config_path_data = deployment["config_path"].get<std::string>();
|
||||
deployment_val.custom_deployment_config_path = custom_deployment_config_path_data.c_str();
|
||||
}
|
||||
|
||||
ffi_args.deployment = &deployment_val;
|
||||
@ -140,10 +163,10 @@ namespace {
|
||||
ffi_args.deployment = nullptr;
|
||||
}
|
||||
|
||||
// state_path (QString -> const char*)
|
||||
if (args.contains("state_path")) {
|
||||
state_path_data = args["state_path"].toString().toUtf8();
|
||||
ffi_args.state_path = state_path_data.constData();
|
||||
// state_path (string -> const char*)
|
||||
if (args.contains("state_path") && args["state_path"].is_string()) {
|
||||
state_path_data = args["state_path"].get<std::string>();
|
||||
ffi_args.state_path = state_path_data.c_str();
|
||||
} else {
|
||||
ffi_args.state_path = nullptr;
|
||||
}
|
||||
@ -154,35 +177,38 @@ namespace {
|
||||
namespace environment {
|
||||
constexpr auto LOGOS_BLOCKCHAIN_CIRCUITS = "LOGOS_BLOCKCHAIN_CIRCUITS";
|
||||
|
||||
// Checks the directory exists and ensures it contains at least one file to avoid an empty directory false positive
|
||||
bool is_circuits_path_valid(const QString& path) {
|
||||
const QDir directory(path);
|
||||
return directory.exists() && !directory.entryList(QDir::Files | QDir::NoDotAndDotDot).isEmpty();
|
||||
bool is_circuits_path_valid(const std::string& path) {
|
||||
std::error_code ec;
|
||||
if (!fs::is_directory(path, ec)) return false;
|
||||
for (const auto& entry : fs::directory_iterator(path, ec)) {
|
||||
if (entry.is_regular_file()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setup_circuits_path(const LogosAPI& logos_api) {
|
||||
const QString module_path = logos_api.property("modulePath").toString();
|
||||
const QDir module_directory(module_path);
|
||||
void setup_circuits_path(const std::string& module_path) {
|
||||
fs::path circuits_path = fs::path(module_path) / "circuits";
|
||||
std::string circuits_str = circuits_path.string();
|
||||
|
||||
const QString circuits_path = module_directory.filePath(QStringLiteral("circuits"));
|
||||
if (!is_circuits_path_valid(circuits_path)) {
|
||||
qFatal() << "The LOGOS_BLOCKCHAIN_CIRCUITS environment variable is not set or does not contain any files.";
|
||||
if (!is_circuits_path_valid(circuits_str)) {
|
||||
fprintf(stderr, "FATAL: The LOGOS_BLOCKCHAIN_CIRCUITS environment variable is not set or does not contain any files.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
qputenv("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_path.toUtf8());
|
||||
qInfo() << "LOGOS_BLOCKCHAIN_CIRCUITS set to:" << circuits_path;
|
||||
setenv("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_str.c_str(), 1);
|
||||
fprintf(stderr, "LOGOS_BLOCKCHAIN_CIRCUITS set to: %s\n", circuits_str.c_str());
|
||||
}
|
||||
} // namespace environment
|
||||
|
||||
void LogosBlockchainModule::on_new_block_callback(const char* block) {
|
||||
if (s_instance) {
|
||||
qInfo() << "Received new block: " << block;
|
||||
QVariantList data;
|
||||
data.append(QString::fromUtf8(block));
|
||||
s_instance->emit_event("newBlock", data);
|
||||
fprintf(stderr, "Received new block: %s\n", block);
|
||||
json j;
|
||||
j["block"] = std::string(block);
|
||||
if (s_instance->emitEvent)
|
||||
s_instance->emitEvent("newBlock", j.dump());
|
||||
// SAFETY:
|
||||
// We are getting an owned pointer here which is freed after this callback is called, so there is not need to
|
||||
// We are getting an owned pointer here which is freed after this callback is called, so there is no need to
|
||||
// free the resource here as we are copying the data!
|
||||
}
|
||||
}
|
||||
@ -198,100 +224,81 @@ LogosBlockchainModule::~LogosBlockchainModule() {
|
||||
}
|
||||
}
|
||||
|
||||
// Logos Core
|
||||
|
||||
QString LogosBlockchainModule::name() const {
|
||||
return "liblogos_blockchain_module";
|
||||
}
|
||||
|
||||
QString LogosBlockchainModule::version() const {
|
||||
return "1.0.0";
|
||||
}
|
||||
|
||||
void LogosBlockchainModule::initLogos(LogosAPI* logos_api_instance) {
|
||||
logosAPI = logos_api_instance;
|
||||
if (logosAPI) {
|
||||
client = logosAPI->getClient("liblogos_blockchain_module");
|
||||
if (!client) {
|
||||
qWarning() << "LogosBlockchainModule: Failed to get liblogos_blockchain_module client";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Node ----
|
||||
|
||||
// Lifecycle
|
||||
|
||||
int LogosBlockchainModule::generate_user_config(const QVariantMap& args) {
|
||||
const OwnedGenerateConfigArgs owned_args(args);
|
||||
int LogosBlockchainModule::generate_user_config(const std::string& json_args) {
|
||||
json parsed_args;
|
||||
try {
|
||||
parsed_args = json::parse(json_args);
|
||||
} catch (const json::parse_error& e) {
|
||||
fprintf(stderr, "Failed to parse JSON args: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const OwnedGenerateConfigArgs owned_args(parsed_args);
|
||||
|
||||
const OperationStatus status = ::generate_user_config(owned_args.ffi_args);
|
||||
if (!is_ok(&status)) {
|
||||
qCritical() << "Failed to generate user config. Error:" << status;
|
||||
fprintf(stderr, "Failed to generate user config. Error: %d\n", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LogosBlockchainModule::generate_user_config_from_str(const QString& args) {
|
||||
const QVariantMap parsed_args = QJsonDocument::fromJson(args.toUtf8()).object().toVariantMap();
|
||||
return generate_user_config(parsed_args);
|
||||
}
|
||||
|
||||
int LogosBlockchainModule::start(const QString& config_path, const QString& deployment) {
|
||||
int LogosBlockchainModule::start(const std::string& config_path, const std::string& deployment) {
|
||||
if (node) {
|
||||
qWarning() << "Could not execute the operation: The node is already running.";
|
||||
fprintf(stderr, "Could not execute the operation: The node is already running.\n");
|
||||
return 1;
|
||||
}
|
||||
if (!logosAPI) {
|
||||
qCritical() << "LogosAPI instance is null, cannot start node.";
|
||||
return 2;
|
||||
|
||||
const char* module_path_env = std::getenv("LOGOS_MODULE_PATH");
|
||||
if (module_path_env && *module_path_env) {
|
||||
environment::setup_circuits_path(module_path_env);
|
||||
} else {
|
||||
fprintf(stderr, "Warning: LOGOS_MODULE_PATH not set, skipping circuits path setup.\n");
|
||||
}
|
||||
|
||||
environment::setup_circuits_path(*logosAPI);
|
||||
QString effective_config_path = config_path;
|
||||
std::string effective_config_path = config_path;
|
||||
|
||||
if (effective_config_path.isEmpty()) {
|
||||
const char* env = std::getenv("LB_CONFIG_PATH"); // NOLINT: Move definition to if-statement
|
||||
if (effective_config_path.empty()) {
|
||||
const char* env = std::getenv("LB_CONFIG_PATH");
|
||||
if (env && *env) {
|
||||
effective_config_path = QString::fromUtf8(env);
|
||||
qInfo() << "Using config from LB_CONFIG_PATH:" << effective_config_path;
|
||||
effective_config_path = env;
|
||||
fprintf(stderr, "Using config from LB_CONFIG_PATH: %s\n", effective_config_path.c_str());
|
||||
} else {
|
||||
qCritical() << "Config path was not specified and LB_CONFIG_PATH is not set.";
|
||||
fprintf(stderr, "Config path was not specified and LB_CONFIG_PATH is not set.\n");
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
qInfo() << "Starting the node with the configuration file:" << effective_config_path;
|
||||
qInfo() << "Using deployment:" << (deployment.isEmpty() ? "<default>" : deployment);
|
||||
effective_config_path = localPathFromFileUrl(effective_config_path);
|
||||
const std::string deployment_path = localPathFromFileUrl(deployment);
|
||||
|
||||
const QByteArray config_path_buffer = effective_config_path.toUtf8();
|
||||
const char* config_path_ptr = effective_config_path.isEmpty() ? nullptr : config_path_buffer.constData();
|
||||
|
||||
const QByteArray deployment_buffer = deployment.toUtf8();
|
||||
const char* deployment_ptr = deployment.isEmpty() ? nullptr : deployment_buffer.constData();
|
||||
const char* config_path_ptr = effective_config_path.empty() ? nullptr : effective_config_path.c_str();
|
||||
const char* deployment_ptr = deployment_path.empty() ? nullptr : deployment_path.c_str();
|
||||
|
||||
auto [value, error] = start_lb_node(config_path_ptr, deployment_ptr);
|
||||
qInfo() << "Start node returned with value and error.";
|
||||
fprintf(stderr, "Start node returned with value and error.\n");
|
||||
if (!is_ok(&error)) {
|
||||
qCritical() << "Failed to start the node. Error:" << error;
|
||||
fprintf(stderr, "Failed to start the node. Error: %d\n", error);
|
||||
return 4;
|
||||
}
|
||||
|
||||
node = value;
|
||||
qInfo() << "The node was started successfully.";
|
||||
fprintf(stderr, "The node was started successfully.\n");
|
||||
|
||||
// Subscribe to block events
|
||||
if (!node) {
|
||||
qWarning() << "Could not subscribe to block events: The node is not running.";
|
||||
fprintf(stderr, "Could not subscribe to block events: The node is not running.\n");
|
||||
return 4;
|
||||
}
|
||||
|
||||
s_instance = this;
|
||||
const OperationStatus subscribe_status = subscribe_to_new_blocks(node, on_new_block_callback);
|
||||
if (!is_ok(&subscribe_status)) {
|
||||
qCritical() << "Failed to subscribe to new blocks. Error:" << subscribe_status;
|
||||
fprintf(stderr, "Failed to subscribe to new blocks. Error: %d\n", subscribe_status);
|
||||
return 5;
|
||||
}
|
||||
|
||||
@ -300,17 +307,17 @@ int LogosBlockchainModule::start(const QString& config_path, const QString& depl
|
||||
|
||||
int LogosBlockchainModule::stop() {
|
||||
if (!node) {
|
||||
qWarning() << "Could not execute the operation: The node is not running.";
|
||||
fprintf(stderr, "Could not execute the operation: The node is not running.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
s_instance = nullptr; // Clear before stopping to prevent callbacks during shutdown
|
||||
s_instance = nullptr;
|
||||
|
||||
const OperationStatus status = stop_node(node);
|
||||
if (is_ok(&status)) {
|
||||
qInfo() << "The node was stopped successfully.";
|
||||
fprintf(stderr, "The node was stopped successfully.\n");
|
||||
} else {
|
||||
qCritical() << "Could not stop the node. Error:" << status;
|
||||
fprintf(stderr, "Could not stop the node. Error: %d\n", status);
|
||||
}
|
||||
|
||||
node = nullptr;
|
||||
@ -319,296 +326,262 @@ int LogosBlockchainModule::stop() {
|
||||
|
||||
// Wallet
|
||||
|
||||
QString LogosBlockchainModule::wallet_get_balance(const QString& address_hex) {
|
||||
qDebug() << "wallet_get_balance: address_hex=" << address_hex;
|
||||
std::string LogosBlockchainModule::wallet_get_balance(const std::string& address_hex) {
|
||||
fprintf(stderr, "wallet_get_balance: address_hex=%s\n", address_hex.c_str());
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
const QByteArray bytes = parse_address_hex(address_hex);
|
||||
if (bytes.isEmpty() || bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Address must be 64 hex characters (32 bytes).");
|
||||
const std::vector<uint8_t> bytes = parse_address_hex(address_hex);
|
||||
if (bytes.empty() || static_cast<int>(bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Address must be 64 hex characters (32 bytes).";
|
||||
}
|
||||
|
||||
auto [value, error] = get_balance(node, reinterpret_cast<const uint8_t*>(bytes.constData()), nullptr);
|
||||
auto [value, error] = get_balance(node, bytes.data(), nullptr);
|
||||
if (!is_ok(&error)) {
|
||||
return QStringLiteral("Error: Failed to get balance: ") + QString::number(error);
|
||||
return "Error: Failed to get balance: " + std::to_string(error);
|
||||
}
|
||||
|
||||
return QString::number(value);
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
QString LogosBlockchainModule::wallet_transfer_funds(
|
||||
const QString& change_public_key,
|
||||
const QStringList& sender_addresses,
|
||||
const QString& recipient_address,
|
||||
const QString& amount,
|
||||
const QString& optional_tip_hex
|
||||
std::string LogosBlockchainModule::wallet_transfer_funds(
|
||||
const std::string& change_public_key,
|
||||
const std::vector<std::string>& sender_addresses,
|
||||
const std::string& recipient_address,
|
||||
const std::string& amount,
|
||||
const std::string& optional_tip_hex
|
||||
) {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const quint64 amount_val = amount.trimmed().toULongLong(&ok);
|
||||
if (!ok) {
|
||||
return QStringLiteral("Error: Invalid amount (positive integer required).");
|
||||
std::string amount_trimmed = amount;
|
||||
boost::algorithm::trim(amount_trimmed);
|
||||
uint64_t amount_val = 0;
|
||||
auto [ptr, ec] = std::from_chars(amount_trimmed.data(), amount_trimmed.data() + amount_trimmed.size(), amount_val);
|
||||
if (ec != std::errc{} || ptr != amount_trimmed.data() + amount_trimmed.size() || amount_trimmed.empty()) {
|
||||
return "Error: Invalid amount (positive integer required).";
|
||||
}
|
||||
|
||||
const QByteArray change_bytes = parse_address_hex(change_public_key);
|
||||
if (change_bytes.isEmpty() || change_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid change_public_key (64 hex characters required).");
|
||||
const std::vector<uint8_t> change_bytes = parse_address_hex(change_public_key);
|
||||
if (change_bytes.empty() || static_cast<int>(change_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid change_public_key (64 hex characters required).";
|
||||
}
|
||||
const QByteArray recipient_bytes = parse_address_hex(recipient_address);
|
||||
if (recipient_bytes.isEmpty() || recipient_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid recipient_address (64 hex characters required).");
|
||||
const std::vector<uint8_t> recipient_bytes = parse_address_hex(recipient_address);
|
||||
if (recipient_bytes.empty() || static_cast<int>(recipient_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid recipient_address (64 hex characters required).";
|
||||
}
|
||||
if (sender_addresses.isEmpty()) {
|
||||
return QStringLiteral("Error: At least one sender address required.");
|
||||
if (sender_addresses.empty()) {
|
||||
return "Error: At least one sender address required.";
|
||||
}
|
||||
QVector<QByteArray> funding_bytes;
|
||||
for (const QString& hex : sender_addresses) {
|
||||
QByteArray b = parse_address_hex(hex);
|
||||
if (b.isEmpty() || b.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid sender address (64 hex characters required).");
|
||||
std::vector<std::vector<uint8_t>> funding_bytes;
|
||||
for (const std::string& hex : sender_addresses) {
|
||||
std::vector<uint8_t> b = parse_address_hex(hex);
|
||||
if (b.empty() || static_cast<int>(b.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid sender address (64 hex characters required).";
|
||||
}
|
||||
funding_bytes.append(b);
|
||||
funding_bytes.push_back(std::move(b));
|
||||
}
|
||||
QVector<const uint8_t*> funding_ptrs;
|
||||
for (const QByteArray& b : funding_bytes)
|
||||
funding_ptrs.append(reinterpret_cast<const uint8_t*>(b.constData()));
|
||||
std::vector<const uint8_t*> funding_ptrs;
|
||||
for (const auto& b : funding_bytes)
|
||||
funding_ptrs.push_back(b.data());
|
||||
|
||||
QByteArray tip_bytes; // NOLINT: Needs to be outside of scope for lifetime
|
||||
std::vector<uint8_t> tip_bytes;
|
||||
const HeaderId* optional_tip = nullptr;
|
||||
if (!optional_tip_hex.isEmpty()) {
|
||||
if (!optional_tip_hex.empty()) {
|
||||
tip_bytes = parse_address_hex(optional_tip_hex);
|
||||
if (tip_bytes.isEmpty() || tip_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid optional tip (64 hex characters or empty).");
|
||||
if (tip_bytes.empty() || static_cast<int>(tip_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid optional tip (64 hex characters or empty).";
|
||||
}
|
||||
optional_tip = reinterpret_cast<const HeaderId*>(tip_bytes.constData());
|
||||
optional_tip = reinterpret_cast<const HeaderId*>(tip_bytes.data());
|
||||
}
|
||||
|
||||
TransferFundsArguments args{};
|
||||
args.optional_tip = optional_tip;
|
||||
args.change_public_key = reinterpret_cast<const uint8_t*>(change_bytes.constData());
|
||||
args.funding_public_keys = funding_ptrs.constData();
|
||||
args.funding_public_keys_len = static_cast<size_t>(funding_ptrs.size());
|
||||
args.recipient_public_key = reinterpret_cast<const uint8_t*>(recipient_bytes.constData());
|
||||
args.change_public_key = change_bytes.data();
|
||||
args.funding_public_keys = funding_ptrs.data();
|
||||
args.funding_public_keys_len = funding_ptrs.size();
|
||||
args.recipient_public_key = recipient_bytes.data();
|
||||
args.amount = amount_val;
|
||||
|
||||
auto [value, error] = transfer_funds(node, &args);
|
||||
if (!is_ok(&error)) {
|
||||
return QStringLiteral("Error: Failed to transfer funds: ") + QString::number(error);
|
||||
return "Error: Failed to transfer funds: " + std::to_string(error);
|
||||
}
|
||||
// value is Hash (32 bytes); convert to hex string
|
||||
const QByteArray hash_bytes(reinterpret_cast<const char*>(&value), ADDRESS_BYTES);
|
||||
return QString::fromUtf8(hash_bytes.toHex());
|
||||
return bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES);
|
||||
}
|
||||
|
||||
QString LogosBlockchainModule::wallet_transfer_funds(
|
||||
const QString& change_public_key,
|
||||
const QString& sender_address,
|
||||
const QString& recipient_address,
|
||||
const QString& amount,
|
||||
const QString& optional_tip_hex
|
||||
) {
|
||||
return wallet_transfer_funds(
|
||||
change_public_key, QStringList{sender_address}, recipient_address, amount, optional_tip_hex
|
||||
);
|
||||
}
|
||||
|
||||
QStringList LogosBlockchainModule::wallet_get_known_addresses() {
|
||||
QStringList out;
|
||||
std::vector<std::string> LogosBlockchainModule::wallet_get_known_addresses() {
|
||||
std::vector<std::string> out;
|
||||
if (!node) {
|
||||
qWarning() << "Could not execute the operation: The node is not running.";
|
||||
fprintf(stderr, "Could not execute the operation: The node is not running.\n");
|
||||
return out;
|
||||
}
|
||||
auto [value, error] = get_known_addresses(node);
|
||||
if (!is_ok(&error)) {
|
||||
qCritical() << "Failed to get known addresses. Error:" << error;
|
||||
fprintf(stderr, "Failed to get known addresses. Error: %d\n", error);
|
||||
return out;
|
||||
}
|
||||
// Each address is ADDRESS_BYTES (32) bytes
|
||||
for (size_t i = 0; i < value.len; ++i) {
|
||||
const uint8_t* ptr = value.addresses[i]; // NOLINT: Move definition to if-statement
|
||||
const uint8_t* ptr = value.addresses[i];
|
||||
if (ptr) {
|
||||
QByteArray addr(reinterpret_cast<const char*>(ptr), ADDRESS_BYTES);
|
||||
out.append(QString::fromUtf8(addr.toHex()));
|
||||
out.push_back(bytes_to_hex(ptr, ADDRESS_BYTES));
|
||||
}
|
||||
}
|
||||
const OperationStatus free_status = free_known_addresses(value);
|
||||
if (!is_ok(&free_status)) {
|
||||
qWarning() << "Failed to free known addresses. Error:" << free_status;
|
||||
fprintf(stderr, "Failed to free known addresses. Error: %d\n", free_status);
|
||||
}
|
||||
qDebug() << "blockchain lib: known addresses, count=" << out.size()
|
||||
<< "sample:" << (out.isEmpty() ? QLatin1String("(none)") : out.constFirst());
|
||||
fprintf(stderr, "blockchain lib: known addresses, count=%zu sample:%s\n",
|
||||
out.size(), out.empty() ? "(none)" : out.front().c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
// Blend
|
||||
|
||||
QString LogosBlockchainModule::blend_join_as_core_node(
|
||||
const QString& provider_id_hex,
|
||||
const QString& zk_id_hex,
|
||||
const QString& locked_note_id_hex,
|
||||
const QStringList& locators
|
||||
std::string LogosBlockchainModule::blend_join_as_core_node(
|
||||
const std::string& provider_id_hex,
|
||||
const std::string& zk_id_hex,
|
||||
const std::string& locked_note_id_hex,
|
||||
const std::vector<std::string>& locators
|
||||
) {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
const QByteArray provider_id_bytes = parse_address_hex(provider_id_hex);
|
||||
if (provider_id_bytes.isEmpty() || provider_id_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid provider_id_hex (64 hex characters required).");
|
||||
const std::vector<uint8_t> provider_id_bytes = parse_address_hex(provider_id_hex);
|
||||
if (provider_id_bytes.empty() || static_cast<int>(provider_id_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid provider_id_hex (64 hex characters required).";
|
||||
}
|
||||
|
||||
const QByteArray zk_id_bytes = parse_address_hex(zk_id_hex);
|
||||
if (zk_id_bytes.isEmpty() || zk_id_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid zk_id_hex (64 hex characters required).");
|
||||
const std::vector<uint8_t> zk_id_bytes = parse_address_hex(zk_id_hex);
|
||||
if (zk_id_bytes.empty() || static_cast<int>(zk_id_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid zk_id_hex (64 hex characters required).";
|
||||
}
|
||||
|
||||
const QByteArray locked_note_id_bytes = parse_address_hex(locked_note_id_hex);
|
||||
if (locked_note_id_bytes.isEmpty() || locked_note_id_bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Invalid locked_note_id_hex (64 hex characters required).");
|
||||
const std::vector<uint8_t> locked_note_id_bytes = parse_address_hex(locked_note_id_hex);
|
||||
if (locked_note_id_bytes.empty() || static_cast<int>(locked_note_id_bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Invalid locked_note_id_hex (64 hex characters required).";
|
||||
}
|
||||
|
||||
// QString is UTF-16, but the FFI requires UTF-8.
|
||||
// locators_data owns the converted buffers, while locators_ptrs holds raw pointers into them for the FFI call.
|
||||
// Using reserve() prevents reallocation, keeping the constData() pointers stable.
|
||||
std::vector<QByteArray> locators_data;
|
||||
// locators_ptrs holds raw pointers into the std::strings (valid as long as locators lives).
|
||||
std::vector<const char*> locators_ptrs;
|
||||
locators_data.reserve(locators.size());
|
||||
locators_ptrs.reserve(locators.size());
|
||||
for (const QString& locator : locators) {
|
||||
locators_data.push_back(locator.toUtf8());
|
||||
locators_ptrs.push_back(locators_data.back().constData());
|
||||
for (const std::string& locator : locators) {
|
||||
locators_ptrs.push_back(locator.c_str());
|
||||
}
|
||||
|
||||
auto [value, error] = ::blend_join_as_core_node(
|
||||
node,
|
||||
reinterpret_cast<const uint8_t*>(provider_id_bytes.constData()),
|
||||
reinterpret_cast<const uint8_t*>(zk_id_bytes.constData()),
|
||||
reinterpret_cast<const uint8_t*>(locked_note_id_bytes.constData()),
|
||||
provider_id_bytes.data(),
|
||||
zk_id_bytes.data(),
|
||||
locked_note_id_bytes.data(),
|
||||
locators_ptrs.data(),
|
||||
locators_ptrs.size()
|
||||
);
|
||||
if (!is_ok(&error)) {
|
||||
return QStringLiteral("Error: Failed to join as core node: ") + QString::number(error);
|
||||
return "Error: Failed to join as core node: " + std::to_string(error);
|
||||
}
|
||||
|
||||
const QByteArray declaration_id_bytes(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
const QString declaration_id = QString::fromUtf8(declaration_id_bytes.toHex());
|
||||
qInfo() << "Successfully joined as core node. DeclarationId:" << declaration_id;
|
||||
std::string declaration_id = bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
|
||||
fprintf(stderr, "Successfully joined as core node. DeclarationId: %s\n", declaration_id.c_str());
|
||||
return declaration_id;
|
||||
}
|
||||
|
||||
// Storage
|
||||
// Explorer
|
||||
|
||||
QString LogosBlockchainModule::get_block(const QString& header_id_hex) {
|
||||
std::string LogosBlockchainModule::get_block(const std::string& header_id_hex) {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
const QByteArray bytes = parse_address_hex(header_id_hex);
|
||||
if (bytes.isEmpty() || bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Header ID must be 64 hex characters (32 bytes).");
|
||||
const std::vector<uint8_t> bytes = parse_address_hex(header_id_hex);
|
||||
if (bytes.empty() || static_cast<int>(bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Header ID must be 64 hex characters (32 bytes).";
|
||||
}
|
||||
|
||||
auto [value, error] = ::get_block(node, reinterpret_cast<const HeaderId*>(bytes.constData()));
|
||||
auto [value, error] = ::get_block(node, reinterpret_cast<const HeaderId*>(bytes.data()));
|
||||
if (!is_ok(&error)) {
|
||||
qWarning() << "Failed to get block. Error:" << error;
|
||||
return QStringLiteral("Error: Failed to get block: ") + QString::number(error);
|
||||
fprintf(stderr, "Failed to get block. Error: %d\n", error);
|
||||
return "Error: Failed to get block: " + std::to_string(error);
|
||||
}
|
||||
|
||||
const QString result = QString::fromUtf8(value);
|
||||
std::string result(value);
|
||||
const OperationStatus free_status = free_cstring(value);
|
||||
if (!is_ok(&free_status)) {
|
||||
qWarning() << "Failed to free block string. Error:" << free_status;
|
||||
fprintf(stderr, "Failed to free block string. Error: %d\n", free_status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString LogosBlockchainModule::get_blocks(const quint64 from_slot, const quint64 to_slot) {
|
||||
std::string LogosBlockchainModule::get_blocks(const uint64_t from_slot, const uint64_t to_slot) {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
auto [value, error] = ::get_blocks(node, from_slot, to_slot);
|
||||
if (!is_ok(&error)) {
|
||||
qWarning() << "Failed to get blocks. Error:" << error;
|
||||
return QStringLiteral("Error: Failed to get blocks: ") + QString::number(error);
|
||||
fprintf(stderr, "Failed to get blocks. Error: %d\n", error);
|
||||
return "Error: Failed to get blocks: " + std::to_string(error);
|
||||
}
|
||||
|
||||
const QString result = QString::fromUtf8(value);
|
||||
std::string result(value);
|
||||
const OperationStatus free_status = free_cstring(value);
|
||||
if (!is_ok(&free_status)) {
|
||||
qWarning() << "Failed to free blocks string. Error:" << free_status;
|
||||
fprintf(stderr, "Failed to free blocks string. Error: %d\n", free_status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString LogosBlockchainModule::get_transaction(const QString& tx_hash_hex) {
|
||||
std::string LogosBlockchainModule::get_transaction(const std::string& tx_hash_hex) {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
const QByteArray bytes = parse_address_hex(tx_hash_hex);
|
||||
if (bytes.isEmpty() || bytes.size() != ADDRESS_BYTES) {
|
||||
return QStringLiteral("Error: Transaction hash must be 64 hex characters (32 bytes).");
|
||||
const std::vector<uint8_t> bytes = parse_address_hex(tx_hash_hex);
|
||||
if (bytes.empty() || static_cast<int>(bytes.size()) != ADDRESS_BYTES) {
|
||||
return "Error: Transaction hash must be 64 hex characters (32 bytes).";
|
||||
}
|
||||
|
||||
auto [value, error] = ::get_transaction(node, reinterpret_cast<const TxHash*>(bytes.constData()));
|
||||
auto [value, error] = ::get_transaction(node, reinterpret_cast<const TxHash*>(bytes.data()));
|
||||
if (!is_ok(&error)) {
|
||||
qWarning() << "Failed to get transaction. Error:" << error;
|
||||
return QStringLiteral("Error: Failed to get transaction: ") + QString::number(error);
|
||||
fprintf(stderr, "Failed to get transaction. Error: %d\n", error);
|
||||
return "Error: Failed to get transaction: " + std::to_string(error);
|
||||
}
|
||||
|
||||
const QString result = QString::fromUtf8(value);
|
||||
std::string result(value);
|
||||
const OperationStatus free_status = free_cstring(value);
|
||||
if (!is_ok(&free_status)) {
|
||||
qWarning() << "Failed to free transaction string. Error:" << free_status;
|
||||
fprintf(stderr, "Failed to free transaction string. Error: %d\n", free_status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cryptarchia
|
||||
|
||||
QString LogosBlockchainModule::get_cryptarchia_info() {
|
||||
std::string LogosBlockchainModule::get_cryptarchia_info() {
|
||||
if (!node) {
|
||||
return QStringLiteral("Error: The node is not running.");
|
||||
return "Error: The node is not running.";
|
||||
}
|
||||
|
||||
auto [value, error] = ::get_cryptarchia_info(node);
|
||||
if (!is_ok(&error)) {
|
||||
qWarning() << "Failed to get cryptarchia info. Error:" << error;
|
||||
return QStringLiteral("Error: Failed to get cryptarchia info: ") + QString::number(error);
|
||||
fprintf(stderr, "Failed to get cryptarchia info. Error: %d\n", error);
|
||||
return "Error: Failed to get cryptarchia info: " + std::to_string(error);
|
||||
}
|
||||
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("lib")] =
|
||||
QString::fromUtf8(QByteArray(reinterpret_cast<const char*>(value->lib), ADDRESS_BYTES).toHex());
|
||||
obj[QStringLiteral("tip")] =
|
||||
QString::fromUtf8(QByteArray(reinterpret_cast<const char*>(value->tip), ADDRESS_BYTES).toHex());
|
||||
obj[QStringLiteral("slot")] = static_cast<qint64>(value->slot);
|
||||
obj[QStringLiteral("height")] = static_cast<qint64>(value->height);
|
||||
obj[QStringLiteral("mode")] =
|
||||
(value->mode == State::Online) ? QStringLiteral("Online") : QStringLiteral("Bootstrapping");
|
||||
json obj;
|
||||
obj["lib"] = bytes_to_hex(reinterpret_cast<const uint8_t*>(value->lib), ADDRESS_BYTES);
|
||||
obj["tip"] = bytes_to_hex(reinterpret_cast<const uint8_t*>(value->tip), ADDRESS_BYTES);
|
||||
obj["slot"] = static_cast<int64_t>(value->slot);
|
||||
obj["height"] = static_cast<int64_t>(value->height);
|
||||
obj["mode"] = (value->mode == State::Online) ? "Online" : "Bootstrapping";
|
||||
|
||||
const OperationStatus free_status = free_cryptarchia_info(value);
|
||||
if (!is_ok(&free_status)) {
|
||||
qWarning() << "Failed to free cryptarchia info. Error:" << free_status;
|
||||
fprintf(stderr, "Failed to free cryptarchia info. Error: %d\n", free_status);
|
||||
}
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
return obj.dump();
|
||||
}
|
||||
|
||||
void LogosBlockchainModule::emit_event(const QString& event_name, const QVariantList& data) {
|
||||
if (!logosAPI) {
|
||||
qWarning() << "LogosBlockchainModule: LogosAPI not available, cannot emit" << event_name;
|
||||
return;
|
||||
}
|
||||
if (!client) {
|
||||
qWarning() << "LogosBlockchainModule: Failed to get liblogos_blockchain_module client for event" << event_name;
|
||||
return;
|
||||
}
|
||||
client->onEventResponse(this, event_name, data);
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "i_logos_blockchain_module.h"
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -11,75 +13,56 @@ extern "C" {
|
||||
}
|
||||
#endif
|
||||
|
||||
class LogosBlockchainModule final : public QObject, public PluginInterface, public ILogosBlockchainModule {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID ILogosBlockchainModule_iid FILE LOGOS_BLOCKCHAIN_MODULE_METADATA_FILE)
|
||||
Q_INTERFACES(PluginInterface)
|
||||
|
||||
class LogosBlockchainModule {
|
||||
public:
|
||||
LogosBlockchainModule();
|
||||
~LogosBlockchainModule() override;
|
||||
~LogosBlockchainModule();
|
||||
|
||||
// Logos Core
|
||||
[[nodiscard]] QString name() const override;
|
||||
[[nodiscard]] QString version() const override;
|
||||
Q_INVOKABLE void initLogos(LogosAPI*) override;
|
||||
// Wired automatically by the generated glue layer.
|
||||
// Call this to emit named events to other modules / the host application.
|
||||
// Data is a JSON-encoded string (object or array).
|
||||
std::function<void(const std::string& eventName, const std::string& data)> emitEvent;
|
||||
|
||||
// ---- Node ----
|
||||
|
||||
// Lifecycle
|
||||
Q_INVOKABLE int generate_user_config(const QVariantMap& args) override;
|
||||
Q_INVOKABLE int generate_user_config_from_str(const QString& args) override;
|
||||
Q_INVOKABLE int start(const QString& config_path, const QString& deployment) override;
|
||||
Q_INVOKABLE int stop() override;
|
||||
int generate_user_config(const std::string& json_args);
|
||||
int start(const std::string& config_path, const std::string& deployment);
|
||||
int stop();
|
||||
|
||||
// Wallet
|
||||
Q_INVOKABLE QString wallet_get_balance(const QString& address_hex) override;
|
||||
Q_INVOKABLE QString wallet_transfer_funds(
|
||||
const QString& change_public_key,
|
||||
const QStringList& sender_addresses,
|
||||
const QString& recipient_address,
|
||||
const QString& amount,
|
||||
const QString& optional_tip_hex
|
||||
) override;
|
||||
Q_INVOKABLE QString wallet_transfer_funds(
|
||||
const QString& change_public_key,
|
||||
const QString& sender_address,
|
||||
const QString& recipient_address,
|
||||
const QString& amount,
|
||||
const QString& optional_tip_hex
|
||||
std::string wallet_get_balance(const std::string& address_hex);
|
||||
std::string wallet_transfer_funds(
|
||||
const std::string& change_public_key,
|
||||
const std::vector<std::string>& sender_addresses,
|
||||
const std::string& recipient_address,
|
||||
const std::string& amount,
|
||||
const std::string& optional_tip_hex
|
||||
);
|
||||
Q_INVOKABLE QStringList wallet_get_known_addresses() override;
|
||||
std::vector<std::string> wallet_get_known_addresses();
|
||||
|
||||
// Blend
|
||||
Q_INVOKABLE QString blend_join_as_core_node(
|
||||
const QString& provider_id_hex,
|
||||
const QString& zk_id_hex,
|
||||
const QString& locked_note_id_hex,
|
||||
const QStringList& locators
|
||||
) override;
|
||||
std::string blend_join_as_core_node(
|
||||
const std::string& provider_id_hex,
|
||||
const std::string& zk_id_hex,
|
||||
const std::string& locked_note_id_hex,
|
||||
const std::vector<std::string>& locators
|
||||
);
|
||||
|
||||
// Explorer
|
||||
Q_INVOKABLE QString get_block(const QString& header_id_hex) override;
|
||||
Q_INVOKABLE QString get_blocks(quint64 from_slot, quint64 to_slot) override;
|
||||
Q_INVOKABLE QString get_transaction(const QString& tx_hash_hex) override;
|
||||
std::string get_block(const std::string& header_id_hex);
|
||||
std::string get_blocks(uint64_t from_slot, uint64_t to_slot);
|
||||
std::string get_transaction(const std::string& tx_hash_hex);
|
||||
|
||||
// Cryptarchia
|
||||
Q_INVOKABLE QString get_cryptarchia_info() override;
|
||||
|
||||
signals:
|
||||
void eventResponse(const QString& event_name, const QVariantList& data);
|
||||
std::string get_cryptarchia_info();
|
||||
|
||||
private:
|
||||
LogosBlockchainNode* node = nullptr;
|
||||
LogosAPIClient* client = nullptr;
|
||||
|
||||
// Static instance for C callback (C API doesn't support user data)
|
||||
static LogosBlockchainModule* s_instance;
|
||||
|
||||
// C-compatible callback function
|
||||
static void on_new_block_callback(const char* block);
|
||||
|
||||
// Helper method for emitting events
|
||||
void emit_event(const QString& event_name, const QVariantList& data);
|
||||
};
|
||||
|
||||
50
tests/CMakeLists.txt
Normal file
50
tests/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(BlockchainModuleTests LANGUAGES CXX)
|
||||
|
||||
include(LogosTest)
|
||||
|
||||
# Unit tests (mocked logos_blockchain)
|
||||
|
||||
logos_test(
|
||||
NAME blockchain_module_tests
|
||||
MODULE_SOURCES
|
||||
../src/logos_blockchain_module.cpp
|
||||
TEST_SOURCES
|
||||
main.cpp
|
||||
test_blockchain.cpp
|
||||
MOCK_C_SOURCES
|
||||
mocks/mock_logos_blockchain.cpp
|
||||
EXTRA_INCLUDES
|
||||
stubs
|
||||
)
|
||||
|
||||
# Integration tests (real logos_blockchain library)
|
||||
|
||||
find_library(LIBLOGOS_BLOCKCHAIN_PATH
|
||||
NAMES liblogos_blockchain.so liblogos_blockchain.dylib
|
||||
PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../lib
|
||||
NO_DEFAULT_PATH)
|
||||
|
||||
if(LIBLOGOS_BLOCKCHAIN_PATH)
|
||||
message(STATUS "[BlockchainTests] logos_blockchain found: ${LIBLOGOS_BLOCKCHAIN_PATH} - building integration tests")
|
||||
|
||||
logos_test(
|
||||
NAME blockchain_module_integration_tests
|
||||
MODULE_SOURCES
|
||||
../src/logos_blockchain_module.cpp
|
||||
TEST_SOURCES
|
||||
main.cpp
|
||||
test_blockchain.cpp
|
||||
EXTRA_INCLUDES
|
||||
../lib
|
||||
EXTRA_LINK_LIBS
|
||||
${LIBLOGOS_BLOCKCHAIN_PATH}
|
||||
)
|
||||
|
||||
get_filename_component(LIBLOGOS_BLOCKCHAIN_DIR "${LIBLOGOS_BLOCKCHAIN_PATH}" DIRECTORY)
|
||||
set_target_properties(blockchain_module_integration_tests PROPERTIES
|
||||
BUILD_RPATH "${LIBLOGOS_BLOCKCHAIN_DIR}"
|
||||
)
|
||||
else()
|
||||
message(STATUS "[BlockchainTests] logos_blockchain not found in ../lib - skipping integration tests")
|
||||
endif()
|
||||
3
tests/main.cpp
Normal file
3
tests/main.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
#include <logos_test.h>
|
||||
|
||||
LOGOS_TEST_MAIN()
|
||||
159
tests/mocks/mock_logos_blockchain.cpp
Normal file
159
tests/mocks/mock_logos_blockchain.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
// Mock implementation of logos_blockchain C functions.
|
||||
// Replaces the real Rust library at link time during unit tests.
|
||||
// Return values are controlled via LogosCMockStore.
|
||||
|
||||
#include <logos_clib_mock.h>
|
||||
#include <logos_blockchain.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
static char s_fakeNode = 0;
|
||||
static CryptarchiaInfo s_fakeCryptarchiaInfo = {};
|
||||
|
||||
// Known-address mock storage (up to 4 addresses)
|
||||
static uint8_t s_mockAddr0[32];
|
||||
static uint8_t s_mockAddr1[32];
|
||||
static uint8_t s_mockAddr2[32];
|
||||
static uint8_t s_mockAddr3[32];
|
||||
static uint8_t* s_mockAddrs[] = { s_mockAddr0, s_mockAddr1, s_mockAddr2, s_mockAddr3 };
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool is_ok(const OperationStatus* status) {
|
||||
return status && *status == 0;
|
||||
}
|
||||
|
||||
OperationStatus generate_user_config(GenerateConfigArgs args) {
|
||||
LOGOS_CMOCK_RECORD("generate_user_config");
|
||||
return LOGOS_CMOCK_RETURN(int, "generate_user_config");
|
||||
}
|
||||
|
||||
NodeResult start_lb_node(const char* config_path, const char* deployment) {
|
||||
LOGOS_CMOCK_RECORD("start_lb_node");
|
||||
int ok = LOGOS_CMOCK_RETURN(int, "start_lb_node");
|
||||
NodeResult result;
|
||||
result.value = ok ? reinterpret_cast<LogosBlockchainNode*>(&s_fakeNode) : nullptr;
|
||||
result.error = ok ? 0 : 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
OperationStatus stop_node(LogosBlockchainNode* node) {
|
||||
LOGOS_CMOCK_RECORD("stop_node");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback) {
|
||||
LOGOS_CMOCK_RECORD("subscribe_to_new_blocks");
|
||||
return LOGOS_CMOCK_RETURN(int, "subscribe_to_new_blocks");
|
||||
}
|
||||
|
||||
BalanceResult get_balance(LogosBlockchainNode* node, const uint8_t* address, const void* reserved) {
|
||||
LOGOS_CMOCK_RECORD("get_balance");
|
||||
BalanceResult result;
|
||||
result.value = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "get_balance_value"));
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "get_balance_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
TransferHashResult transfer_funds(LogosBlockchainNode* node, const TransferFundsArguments* args) {
|
||||
LOGOS_CMOCK_RECORD("transfer_funds");
|
||||
TransferHashResult result;
|
||||
memset(result.value, 0xAB, sizeof(Hash));
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "transfer_funds_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
KnownAddressesResult get_known_addresses(LogosBlockchainNode* node) {
|
||||
LOGOS_CMOCK_RECORD("get_known_addresses");
|
||||
KnownAddressesResult result;
|
||||
int err = LOGOS_CMOCK_RETURN(int, "get_known_addresses_error");
|
||||
result.error = err;
|
||||
if (err == 0) {
|
||||
int count = LOGOS_CMOCK_RETURN(int, "get_known_addresses_count");
|
||||
if (count > 4) count = 4;
|
||||
memset(s_mockAddr0, 0x11, 32);
|
||||
memset(s_mockAddr1, 0x22, 32);
|
||||
memset(s_mockAddr2, 0x33, 32);
|
||||
memset(s_mockAddr3, 0x44, 32);
|
||||
result.value.addresses = s_mockAddrs;
|
||||
result.value.len = static_cast<size_t>(count);
|
||||
} else {
|
||||
result.value.addresses = nullptr;
|
||||
result.value.len = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OperationStatus free_known_addresses(KnownAddresses addrs) {
|
||||
LOGOS_CMOCK_RECORD("free_known_addresses");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BlendHashResult blend_join_as_core_node(
|
||||
LogosBlockchainNode* node,
|
||||
const uint8_t* provider_id,
|
||||
const uint8_t* zk_id,
|
||||
const uint8_t* locked_note_id,
|
||||
const char** locators,
|
||||
size_t locators_count)
|
||||
{
|
||||
LOGOS_CMOCK_RECORD("blend_join_as_core_node");
|
||||
BlendHashResult result;
|
||||
memset(result.value, 0xCD, sizeof(Hash));
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "blend_join_as_core_node_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
StringResult get_block(LogosBlockchainNode* node, const HeaderId* header_id) {
|
||||
LOGOS_CMOCK_RECORD("get_block");
|
||||
StringResult result;
|
||||
const char* json = LOGOS_CMOCK_RETURN_STRING("get_block");
|
||||
result.value = json ? strdup(json) : nullptr;
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "get_block_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
StringResult get_blocks(LogosBlockchainNode* node, uint64_t from_slot, uint64_t to_slot) {
|
||||
LOGOS_CMOCK_RECORD("get_blocks");
|
||||
StringResult result;
|
||||
const char* json = LOGOS_CMOCK_RETURN_STRING("get_blocks");
|
||||
result.value = json ? strdup(json) : nullptr;
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "get_blocks_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
StringResult get_transaction(LogosBlockchainNode* node, const TxHash* tx_hash) {
|
||||
LOGOS_CMOCK_RECORD("get_transaction");
|
||||
StringResult result;
|
||||
const char* json = LOGOS_CMOCK_RETURN_STRING("get_transaction");
|
||||
result.value = json ? strdup(json) : nullptr;
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "get_transaction_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
CryptarchiaInfoResult get_cryptarchia_info(LogosBlockchainNode* node) {
|
||||
LOGOS_CMOCK_RECORD("get_cryptarchia_info");
|
||||
CryptarchiaInfoResult result;
|
||||
memset(&s_fakeCryptarchiaInfo, 0, sizeof(s_fakeCryptarchiaInfo));
|
||||
s_fakeCryptarchiaInfo.slot = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "cryptarchia_slot"));
|
||||
s_fakeCryptarchiaInfo.height = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "cryptarchia_height"));
|
||||
s_fakeCryptarchiaInfo.mode = static_cast<State>(LOGOS_CMOCK_RETURN(int, "cryptarchia_mode"));
|
||||
memset(s_fakeCryptarchiaInfo.lib, 0xEE, 32);
|
||||
memset(s_fakeCryptarchiaInfo.tip, 0xFF, 32);
|
||||
result.value = &s_fakeCryptarchiaInfo;
|
||||
result.error = LOGOS_CMOCK_RETURN(int, "get_cryptarchia_info_error");
|
||||
return result;
|
||||
}
|
||||
|
||||
OperationStatus free_cryptarchia_info(CryptarchiaInfo* info) {
|
||||
LOGOS_CMOCK_RECORD("free_cryptarchia_info");
|
||||
return 0;
|
||||
}
|
||||
|
||||
OperationStatus free_cstring(char* s) {
|
||||
LOGOS_CMOCK_RECORD("free_cstring");
|
||||
free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
131
tests/stubs/logos_blockchain.h
Normal file
131
tests/stubs/logos_blockchain.h
Normal file
@ -0,0 +1,131 @@
|
||||
// Stub header for logos_blockchain — provides the same declarations as the real
|
||||
// Rust-generated header so that logos_blockchain_module sources compile in tests.
|
||||
|
||||
#ifndef LOGOS_BLOCKCHAIN_H
|
||||
#define LOGOS_BLOCKCHAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 32-byte hash/address types
|
||||
typedef uint8_t Hash[32];
|
||||
typedef uint8_t HeaderId[32];
|
||||
typedef uint8_t TxHash[32];
|
||||
|
||||
// Opaque node handle
|
||||
typedef struct LogosBlockchainNode LogosBlockchainNode;
|
||||
|
||||
// Operation status (0 = OK)
|
||||
typedef int OperationStatus;
|
||||
|
||||
// Deployment enums
|
||||
typedef enum { WellKnown, Custom } DeploymentType;
|
||||
typedef enum { Devnet } WellKnownDeployment;
|
||||
|
||||
// Consensus state enum
|
||||
typedef enum { Bootstrapping, Online } State;
|
||||
|
||||
// Deployment configuration
|
||||
typedef struct {
|
||||
DeploymentType deployment_type;
|
||||
WellKnownDeployment well_known_deployment;
|
||||
const char* custom_deployment_config_path;
|
||||
} Deployment;
|
||||
|
||||
// Arguments for generate_user_config
|
||||
typedef struct {
|
||||
const char** initial_peers;
|
||||
const uint32_t* initial_peers_count;
|
||||
const char* output;
|
||||
const uint16_t* net_port;
|
||||
const uint16_t* blend_port;
|
||||
const char* http_addr;
|
||||
const char* external_address;
|
||||
const bool* no_public_ip_check;
|
||||
const Deployment* deployment;
|
||||
const char* state_path;
|
||||
} GenerateConfigArgs;
|
||||
|
||||
// Arguments for transfer_funds
|
||||
typedef struct {
|
||||
const HeaderId* optional_tip;
|
||||
const uint8_t* change_public_key;
|
||||
const uint8_t* const* funding_public_keys;
|
||||
size_t funding_public_keys_len;
|
||||
const uint8_t* recipient_public_key;
|
||||
uint64_t amount;
|
||||
} TransferFundsArguments;
|
||||
|
||||
// Known addresses result container
|
||||
typedef struct {
|
||||
uint8_t** addresses;
|
||||
size_t len;
|
||||
} KnownAddresses;
|
||||
|
||||
// Cryptarchia consensus info
|
||||
typedef struct {
|
||||
uint8_t lib[32];
|
||||
uint8_t tip[32];
|
||||
uint64_t slot;
|
||||
uint64_t height;
|
||||
State mode;
|
||||
} CryptarchiaInfo;
|
||||
|
||||
// Result types (C++ structured bindings decompose these)
|
||||
typedef struct { LogosBlockchainNode* value; OperationStatus error; } NodeResult;
|
||||
typedef struct { uint64_t value; OperationStatus error; } BalanceResult;
|
||||
typedef struct { Hash value; OperationStatus error; } TransferHashResult;
|
||||
typedef struct { KnownAddresses value; OperationStatus error; } KnownAddressesResult;
|
||||
typedef struct { Hash value; OperationStatus error; } BlendHashResult;
|
||||
typedef struct { char* value; OperationStatus error; } StringResult;
|
||||
typedef struct { CryptarchiaInfo* value; OperationStatus error; } CryptarchiaInfoResult;
|
||||
|
||||
// Block event callback
|
||||
typedef void (*BlockCallback)(const char* block_json);
|
||||
|
||||
// Status check
|
||||
bool is_ok(const OperationStatus* status);
|
||||
|
||||
// Lifecycle
|
||||
OperationStatus generate_user_config(GenerateConfigArgs args);
|
||||
NodeResult start_lb_node(const char* config_path, const char* deployment);
|
||||
OperationStatus stop_node(LogosBlockchainNode* node);
|
||||
OperationStatus subscribe_to_new_blocks(LogosBlockchainNode* node, BlockCallback callback);
|
||||
|
||||
// Wallet
|
||||
BalanceResult get_balance(LogosBlockchainNode* node, const uint8_t* address, const void* reserved);
|
||||
TransferHashResult transfer_funds(LogosBlockchainNode* node, const TransferFundsArguments* args);
|
||||
KnownAddressesResult get_known_addresses(LogosBlockchainNode* node);
|
||||
OperationStatus free_known_addresses(KnownAddresses addrs);
|
||||
|
||||
// Blend
|
||||
BlendHashResult blend_join_as_core_node(
|
||||
LogosBlockchainNode* node,
|
||||
const uint8_t* provider_id,
|
||||
const uint8_t* zk_id,
|
||||
const uint8_t* locked_note_id,
|
||||
const char** locators,
|
||||
size_t locators_count);
|
||||
|
||||
// Explorer
|
||||
StringResult get_block(LogosBlockchainNode* node, const HeaderId* header_id);
|
||||
StringResult get_blocks(LogosBlockchainNode* node, uint64_t from_slot, uint64_t to_slot);
|
||||
StringResult get_transaction(LogosBlockchainNode* node, const TxHash* tx_hash);
|
||||
|
||||
// Cryptarchia
|
||||
CryptarchiaInfoResult get_cryptarchia_info(LogosBlockchainNode* node);
|
||||
OperationStatus free_cryptarchia_info(CryptarchiaInfo* info);
|
||||
|
||||
// Memory management
|
||||
OperationStatus free_cstring(char* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LOGOS_BLOCKCHAIN_H
|
||||
729
tests/test_blockchain.cpp
Normal file
729
tests/test_blockchain.cpp
Normal file
@ -0,0 +1,729 @@
|
||||
// Unit tests for LogosBlockchainModule.
|
||||
// All logos_blockchain C functions are mocked at link time via mock_logos_blockchain.cpp.
|
||||
|
||||
#include <logos_test.h>
|
||||
#include "logos_blockchain_module.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// 64-char hex string = 32 bytes (valid address/hash)
|
||||
static const std::string VALID_HEX(64, 'a');
|
||||
static const std::string VALID_HEX_WITH_PREFIX = "0x" + std::string(64, 'b');
|
||||
|
||||
static bool starts_with(const std::string& s, const std::string& prefix) {
|
||||
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
|
||||
}
|
||||
|
||||
static bool contains(const std::string& s, const std::string& sub) {
|
||||
return s.find(sub) != std::string::npos;
|
||||
}
|
||||
|
||||
// RAII wrapper for a temporary directory (removed on destruction).
|
||||
struct TempDir {
|
||||
fs::path path;
|
||||
TempDir() {
|
||||
char tmpl[] = "/tmp/logos-blockchain-test-XXXXXX";
|
||||
char* dir = mkdtemp(tmpl);
|
||||
if (dir) path = dir;
|
||||
}
|
||||
~TempDir() {
|
||||
if (!path.empty()) {
|
||||
std::error_code ec;
|
||||
fs::remove_all(path, ec);
|
||||
}
|
||||
}
|
||||
bool isValid() const { return !path.empty(); }
|
||||
std::string filePath(const std::string& name) const { return (path / name).string(); }
|
||||
};
|
||||
|
||||
// Helper: create a module with a running (mocked) node.
|
||||
// Sets up circuits directory, LOGOS_MODULE_PATH env, and calls start().
|
||||
static LogosBlockchainModule* createStartedModule(LogosTestContext& t, TempDir& tmpDir) {
|
||||
fs::create_directories(tmpDir.path / "circuits");
|
||||
{
|
||||
std::ofstream f((tmpDir.path / "circuits" / "dummy.bin").string());
|
||||
f << "x";
|
||||
}
|
||||
|
||||
setenv("LOGOS_MODULE_PATH", tmpDir.path.string().c_str(), 1);
|
||||
|
||||
auto* module = new LogosBlockchainModule();
|
||||
|
||||
t.mockCFunction("start_lb_node").returns(1);
|
||||
t.mockCFunction("subscribe_to_new_blocks").returns(0);
|
||||
|
||||
int rc = module->start(tmpDir.filePath("config.json"), "");
|
||||
if (rc != 0) {
|
||||
delete module;
|
||||
return nullptr;
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// generate_user_config
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(generate_user_config_returns_0_on_success) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
|
||||
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/test-config.json"})"), 0);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(generate_user_config_returns_1_on_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(1);
|
||||
|
||||
LOGOS_ASSERT_EQ(module.generate_user_config("{}"), 1);
|
||||
}
|
||||
|
||||
LOGOS_TEST(generate_user_config_from_json_string) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
|
||||
LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/out.json"})"), 0);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("generate_user_config"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(generate_user_config_with_all_fields) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
|
||||
t.mockCFunction("generate_user_config").returns(0);
|
||||
|
||||
std::string args = R"({
|
||||
"initial_peers": ["peer1", "peer2"],
|
||||
"output": "/tmp/out.json",
|
||||
"net_port": 9000,
|
||||
"blend_port": 9001,
|
||||
"http_addr": "0.0.0.0:8080",
|
||||
"external_address": "1.2.3.4",
|
||||
"no_public_ip_check": true,
|
||||
"deployment": { "well_known_deployment": "devnet" },
|
||||
"state_path": "/tmp/state"
|
||||
})";
|
||||
|
||||
LOGOS_ASSERT_EQ(module.generate_user_config(args), 0);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// No-node error paths — all methods should fail gracefully
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(stop_without_node_returns_1) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_EQ(module.stop(), 1);
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
std::string result = module.wallet_get_balance(VALID_HEX);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "not running"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
std::string result = module.wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "not running"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_known_addresses_without_node_returns_empty) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_TRUE(module.wallet_get_known_addresses().empty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(blend_join_as_core_node_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
std::string result = module.blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {"locator1"});
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "not running"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_block_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_TRUE(starts_with(module.get_block(VALID_HEX), "Error:"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_blocks_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_TRUE(starts_with(module.get_blocks(0, 10), "Error:"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_transaction_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_TRUE(starts_with(module.get_transaction(VALID_HEX), "Error:"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_cryptarchia_info_without_node_returns_error) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
LogosBlockchainModule module;
|
||||
LOGOS_ASSERT_TRUE(starts_with(module.get_cryptarchia_info(), "Error:"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Node lifecycle (start / stop)
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(start_succeeds_with_mocked_dependencies) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
LOGOS_ASSERT_TRUE(tmpDir.isValid());
|
||||
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("start_lb_node"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("subscribe_to_new_blocks"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(start_returns_1_when_already_running) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
LOGOS_ASSERT_EQ(module->start("/tmp/config.json", ""), 1);
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(stop_succeeds_with_running_node) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
LOGOS_ASSERT_EQ(module->stop(), 0);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("stop_node"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Input validation (requires running node)
|
||||
// ============================================================================
|
||||
|
||||
// wallet_get_balance validation
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_rejects_short_hex) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_get_balance("abcd");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "64 hex"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_rejects_long_hex) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_get_balance(std::string(66, 'a'));
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_rejects_invalid_chars) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string hex = std::string(62, 'a') + "zz";
|
||||
std::string result = module->wallet_get_balance(hex);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// wallet_transfer_funds validation
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_invalid_amount) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "not_a_number", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "Invalid amount"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_invalid_change_key) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds("bad", {VALID_HEX}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "change_public_key"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_invalid_recipient) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, "short", "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "recipient_address"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_empty_senders) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "sender"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_invalid_sender_address) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {"bad_addr"}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "sender"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_rejects_invalid_optional_tip) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "100", "bad_tip");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "tip"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// blend_join_as_core_node validation
|
||||
|
||||
LOGOS_TEST(blend_join_rejects_invalid_provider_id) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->blend_join_as_core_node("short", VALID_HEX, VALID_HEX, {});
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "provider_id"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(blend_join_rejects_invalid_zk_id) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->blend_join_as_core_node(VALID_HEX, "short", VALID_HEX, {});
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "zk_id"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(blend_join_rejects_invalid_locked_note_id) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, "short", {});
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "locked_note_id"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// get_block / get_transaction validation
|
||||
|
||||
LOGOS_TEST(get_block_rejects_invalid_hex) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->get_block("tooshort");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "64 hex"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_transaction_rejects_invalid_hex) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
std::string result = module->get_transaction("bad");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "64 hex"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 0x prefix handling
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_accepts_0x_prefix) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_balance_value").returns(42);
|
||||
t.mockCFunction("get_balance_error").returns(0);
|
||||
|
||||
std::string result = module->wallet_get_balance(VALID_HEX_WITH_PREFIX);
|
||||
LOGOS_ASSERT_EQ(result, std::string("42"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Success paths (requires running node + mocked C functions)
|
||||
// ============================================================================
|
||||
|
||||
// Wallet
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_returns_balance_string) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_balance_value").returns(1000);
|
||||
t.mockCFunction("get_balance_error").returns(0);
|
||||
|
||||
std::string result = module->wallet_get_balance(VALID_HEX);
|
||||
LOGOS_ASSERT_EQ(result, std::string("1000"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_balance"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_balance_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_balance_error").returns(1);
|
||||
|
||||
std::string result = module->wallet_get_balance(VALID_HEX);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_returns_tx_hash) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("transfer_funds_error").returns(0);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "500", "");
|
||||
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "ab"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("transfer_funds"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_with_optional_tip) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("transfer_funds_error").returns(0);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "100", VALID_HEX);
|
||||
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("transfer_funds_error").returns(1);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_single_sender_via_vector) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("transfer_funds_error").returns(0);
|
||||
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, {VALID_HEX}, VALID_HEX, "100", "");
|
||||
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("transfer_funds"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_transfer_funds_multiple_senders) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("transfer_funds_error").returns(0);
|
||||
|
||||
std::vector<std::string> senders = {VALID_HEX, std::string(64, 'b')};
|
||||
std::string result = module->wallet_transfer_funds(VALID_HEX, senders, VALID_HEX, "200", "");
|
||||
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_known_addresses_returns_addresses) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_known_addresses_error").returns(0);
|
||||
t.mockCFunction("get_known_addresses_count").returns(2);
|
||||
|
||||
std::vector<std::string> addrs = module->wallet_get_known_addresses();
|
||||
LOGOS_ASSERT_EQ(static_cast<int>(addrs.size()), 2);
|
||||
// Mock fills addr0 with 0x11 -> hex "1111...11", addr1 with 0x22 -> "2222...22"
|
||||
LOGOS_ASSERT_EQ(addrs[0], std::string(64, '1'));
|
||||
LOGOS_ASSERT_EQ(addrs[1], std::string(64, '2'));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_known_addresses"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("free_known_addresses"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(wallet_get_known_addresses_returns_empty_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_known_addresses_error").returns(1);
|
||||
|
||||
std::vector<std::string> addrs = module->wallet_get_known_addresses();
|
||||
LOGOS_ASSERT_TRUE(addrs.empty());
|
||||
delete module;
|
||||
}
|
||||
|
||||
// Blend
|
||||
|
||||
LOGOS_TEST(blend_join_as_core_node_returns_declaration_id) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("blend_join_as_core_node_error").returns(0);
|
||||
|
||||
std::vector<std::string> locators = {"locator1", "locator2"};
|
||||
std::string result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, locators);
|
||||
// Mock fills hash with 0xCD -> hex "cdcd...cd" (64 chars)
|
||||
LOGOS_ASSERT_EQ(static_cast<int>(result.length()), 64);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "cd"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("blend_join_as_core_node"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(blend_join_as_core_node_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("blend_join_as_core_node_error").returns(1);
|
||||
|
||||
std::string result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {});
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// Explorer
|
||||
|
||||
LOGOS_TEST(get_block_returns_json_on_success) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_block").returns(R"({"slot":42,"data":"test"})");
|
||||
t.mockCFunction("get_block_error").returns(0);
|
||||
|
||||
std::string result = module->get_block(VALID_HEX);
|
||||
LOGOS_ASSERT_TRUE(contains(result, "slot"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "42"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_block"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("free_cstring"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_block_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_block_error").returns(1);
|
||||
|
||||
std::string result = module->get_block(VALID_HEX);
|
||||
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_blocks_returns_json_on_success) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_blocks").returns(R"([{"slot":1},{"slot":2}])");
|
||||
t.mockCFunction("get_blocks_error").returns(0);
|
||||
|
||||
std::string result = module->get_blocks(1, 10);
|
||||
LOGOS_ASSERT_TRUE(contains(result, "slot"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_blocks"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_blocks_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_blocks_error").returns(1);
|
||||
|
||||
LOGOS_ASSERT_TRUE(starts_with(module->get_blocks(0, 10), "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_transaction_returns_json_on_success) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_transaction").returns(R"({"hash":"abc","status":"confirmed"})");
|
||||
t.mockCFunction("get_transaction_error").returns(0);
|
||||
|
||||
std::string result = module->get_transaction(VALID_HEX);
|
||||
LOGOS_ASSERT_TRUE(contains(result, "confirmed"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_transaction"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("free_cstring"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_transaction_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_transaction_error").returns(1);
|
||||
|
||||
LOGOS_ASSERT_TRUE(starts_with(module->get_transaction(VALID_HEX), "Error:"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
// Cryptarchia
|
||||
|
||||
LOGOS_TEST(get_cryptarchia_info_returns_json_on_success) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_cryptarchia_info_error").returns(0);
|
||||
t.mockCFunction("cryptarchia_slot").returns(100);
|
||||
t.mockCFunction("cryptarchia_height").returns(50);
|
||||
t.mockCFunction("cryptarchia_mode").returns(1); // Online
|
||||
|
||||
std::string result = module->get_cryptarchia_info();
|
||||
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "slot"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "100"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "height"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "50"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "Online"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "lib"));
|
||||
LOGOS_ASSERT_TRUE(contains(result, "tip"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("get_cryptarchia_info"));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("free_cryptarchia_info"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_cryptarchia_info_bootstrapping_mode) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_cryptarchia_info_error").returns(0);
|
||||
t.mockCFunction("cryptarchia_mode").returns(0); // Bootstrapping
|
||||
|
||||
std::string result = module->get_cryptarchia_info();
|
||||
LOGOS_ASSERT_TRUE(contains(result, "Bootstrapping"));
|
||||
delete module;
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_cryptarchia_info_returns_error_on_ffi_failure) {
|
||||
auto t = LogosTestContext("blockchain_module");
|
||||
TempDir tmpDir;
|
||||
auto* module = createStartedModule(t, tmpDir);
|
||||
LOGOS_ASSERT_TRUE(module != nullptr);
|
||||
|
||||
t.mockCFunction("get_cryptarchia_info_error").returns(1);
|
||||
|
||||
LOGOS_ASSERT_TRUE(starts_with(module->get_cryptarchia_info(), "Error:"));
|
||||
delete module;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user