From e8cd77139d3e4dc14648001f576e100a927ad8b9 Mon Sep 17 00:00:00 2001 From: Logos Workspace Date: Fri, 8 May 2026 14:47:22 -0400 Subject: [PATCH] update api update api simplify simplify --- CMakeLists.txt | 7 +- metadata.json | 8 +- src/i_logos_blockchain_module.h | 53 --- src/logos_blockchain_module.cpp | 564 +++++++++++++++----------------- src/logos_blockchain_module.h | 79 ++--- tests/test_blockchain.cpp | 424 ++++++++++++------------ 6 files changed, 507 insertions(+), 628 deletions(-) delete mode 100644 src/i_logos_blockchain_module.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f007a0..148e747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,16 @@ cmake_minimum_required(VERSION 3.14) project(LogosBlockchainModulePlugin LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT}) include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake) else() message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.") endif() -# logos_module() handles: Qt/AUTOMOC setup, SDK/module include paths, linking -# libs from EXTERNAL_LIBS into lib/, plugin output naming, RPATH, install rules. +# Universal module — generated_code/ is picked up automatically by LogosModule.cmake logos_module( NAME liblogos_blockchain_module SOURCES - src/i_logos_blockchain_module.h src/logos_blockchain_module.h src/logos_blockchain_module.cpp EXTERNAL_LIBS diff --git a/metadata.json b/metadata.json index 3b07bdf..77e19c4 100644 --- a/metadata.json +++ b/metadata.json @@ -4,6 +4,10 @@ "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_plugin", "dependencies": [], @@ -20,8 +24,8 @@ "nix": { "packages": { - "build": [], - "runtime": [] + "build": ["boost"], + "runtime": ["nlohmann_json"] }, "external_libraries": [ { "name": "logos_blockchain" } diff --git a/src/i_logos_blockchain_module.h b/src/i_logos_blockchain_module.h deleted file mode 100644 index e3ddffe..0000000 --- a/src/i_logos_blockchain_module.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef I_LOGOS_BLOCKCHAIN_MODULE_API_H -#define I_LOGOS_BLOCKCHAIN_MODULE_API_H - -#include -#include "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 diff --git a/src/logos_blockchain_module.cpp b/src/logos_blockchain_module.cpp index 027105a..873f944 100644 --- a/src/logos_blockchain_module.cpp +++ b/src/logos_blockchain_module.cpp @@ -1,14 +1,17 @@ #include "logos_blockchain_module.h" -#include "logos_api_client.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using json = nlohmann::json; // Define static member LogosBlockchainModule* LogosBlockchainModule::s_instance = nullptr; @@ -16,16 +19,9 @@ 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. - QString localPathFromFileUrl(const QString& s) { - if (s.isEmpty()) { - return s; - } - if (s.startsWith(QStringLiteral("file:"), Qt::CaseInsensitive)) { - const QUrl u(s); - if (u.isLocalFile()) { - return QDir::toNativeSeparators(u.toLocalFile()); - } - } + 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; } @@ -33,49 +29,58 @@ namespace { 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 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(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 initial_peers_data; + std::vector initial_peers_data; std::vector 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(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()); } - for (const QByteArray& data : initial_peers_data) { - initial_peers_ptrs.push_back(data.constData()); + initial_peers_count_val = static_cast(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(); @@ -85,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(); + 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(args["net_port"].toInt()); + if (args.contains("net_port") && args["net_port"].is_number_integer()) { + net_port_val = static_cast(args["net_port"].get()); 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(args["blend_port"].toInt()); + if (args.contains("blend_port") && args["blend_port"].is_number_integer()) { + blend_port_val = static_cast(args["blend_port"].get()); 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(); + 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(); + 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(); ffi_args.no_public_ip_check = &no_public_ip_check_val; } else { ffi_args.no_public_ip_check = nullptr; @@ -136,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(); 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(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(); + deployment_val.custom_deployment_config_path = custom_deployment_config_path_data.c_str(); } ffi_args.deployment = &deployment_val; @@ -159,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(); + ffi_args.state_path = state_path_data.c_str(); } else { ffi_args.state_path = nullptr; } @@ -173,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! } } @@ -217,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; } } effective_config_path = localPathFromFileUrl(effective_config_path); - const QString deployment_path = localPathFromFileUrl(deployment); + 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_path.toUtf8(); - const char* deployment_ptr = deployment_path.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; } @@ -319,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; @@ -338,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 bytes = parse_address_hex(address_hex); + if (bytes.empty() || static_cast(bytes.size()) != ADDRESS_BYTES) { + return "Error: Address must be 64 hex characters (32 bytes)."; } - auto [value, error] = get_balance(node, reinterpret_cast(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& 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 change_bytes = parse_address_hex(change_public_key); + if (change_bytes.empty() || static_cast(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 recipient_bytes = parse_address_hex(recipient_address); + if (recipient_bytes.empty() || static_cast(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 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> funding_bytes; + for (const std::string& hex : sender_addresses) { + std::vector b = parse_address_hex(hex); + if (b.empty() || static_cast(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 funding_ptrs; - for (const QByteArray& b : funding_bytes) - funding_ptrs.append(reinterpret_cast(b.constData())); + std::vector 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 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(tip_bytes.size()) != ADDRESS_BYTES) { + return "Error: Invalid optional tip (64 hex characters or empty)."; } - optional_tip = reinterpret_cast(tip_bytes.constData()); + optional_tip = reinterpret_cast(tip_bytes.data()); } TransferFundsArguments args{}; args.optional_tip = optional_tip; - args.change_public_key = reinterpret_cast(change_bytes.constData()); - args.funding_public_keys = funding_ptrs.constData(); - args.funding_public_keys_len = static_cast(funding_ptrs.size()); - args.recipient_public_key = reinterpret_cast(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(&value), ADDRESS_BYTES); - return QString::fromUtf8(hash_bytes.toHex()); + return bytes_to_hex(reinterpret_cast(&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 LogosBlockchainModule::wallet_get_known_addresses() { + std::vector 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(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& 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 provider_id_bytes = parse_address_hex(provider_id_hex); + if (provider_id_bytes.empty() || static_cast(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 zk_id_bytes = parse_address_hex(zk_id_hex); + if (zk_id_bytes.empty() || static_cast(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 locked_note_id_bytes = parse_address_hex(locked_note_id_hex); + if (locked_note_id_bytes.empty() || static_cast(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 locators_data; + // locators_ptrs holds raw pointers into the std::strings (valid as long as locators lives). std::vector 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(provider_id_bytes.constData()), - reinterpret_cast(zk_id_bytes.constData()), - reinterpret_cast(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(&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(&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 bytes = parse_address_hex(header_id_hex); + if (bytes.empty() || static_cast(bytes.size()) != ADDRESS_BYTES) { + return "Error: Header ID must be 64 hex characters (32 bytes)."; } - auto [value, error] = ::get_block(node, reinterpret_cast(bytes.constData())); + auto [value, error] = ::get_block(node, reinterpret_cast(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 bytes = parse_address_hex(tx_hash_hex); + if (bytes.empty() || static_cast(bytes.size()) != ADDRESS_BYTES) { + return "Error: Transaction hash must be 64 hex characters (32 bytes)."; } - auto [value, error] = ::get_transaction(node, reinterpret_cast(bytes.constData())); + auto [value, error] = ::get_transaction(node, reinterpret_cast(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(value->lib), ADDRESS_BYTES).toHex()); - obj[QStringLiteral("tip")] = - QString::fromUtf8(QByteArray(reinterpret_cast(value->tip), ADDRESS_BYTES).toHex()); - obj[QStringLiteral("slot")] = static_cast(value->slot); - obj[QStringLiteral("height")] = static_cast(value->height); - obj[QStringLiteral("mode")] = - (value->mode == State::Online) ? QStringLiteral("Online") : QStringLiteral("Bootstrapping"); + json obj; + obj["lib"] = bytes_to_hex(reinterpret_cast(value->lib), ADDRESS_BYTES); + obj["tip"] = bytes_to_hex(reinterpret_cast(value->tip), ADDRESS_BYTES); + obj["slot"] = static_cast(value->slot); + obj["height"] = static_cast(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); -} diff --git a/src/logos_blockchain_module.h b/src/logos_blockchain_module.h index b0ea8b9..acf66d2 100644 --- a/src/logos_blockchain_module.h +++ b/src/logos_blockchain_module.h @@ -1,8 +1,10 @@ #pragma once -#include "i_logos_blockchain_module.h" +#include +#include +#include +#include -#include #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 "metadata.json") - 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 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& 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 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& 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); }; diff --git a/tests/test_blockchain.cpp b/tests/test_blockchain.cpp index 3491450..9f22d0f 100644 --- a/tests/test_blockchain.cpp +++ b/tests/test_blockchain.cpp @@ -4,28 +4,57 @@ #include #include "logos_blockchain_module.h" -#include -#include -#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; // 64-char hex string = 32 bytes (valid address/hash) -static const QString VALID_HEX = QString(64, 'a'); -static const QString VALID_HEX_WITH_PREFIX = "0x" + QString(64, 'b'); +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, mock LogosAPI, and calls start(). -static LogosBlockchainModule* createStartedModule(LogosTestContext& t, QTemporaryDir& tmpDir) { - QDir dir(tmpDir.path()); - dir.mkpath("circuits"); - QFile f(dir.filePath("circuits/dummy.bin")); - f.open(QIODevice::WriteOnly); - f.write("x"); - f.close(); +// 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"; + } - t.api()->setProperty("modulePath", tmpDir.path()); + setenv("LOGOS_MODULE_PATH", tmpDir.path.string().c_str(), 1); auto* module = new LogosBlockchainModule(); - t.initLegacy(module); t.mockCFunction("start_lb_node").returns(1); t.mockCFunction("subscribe_to_new_blocks").returns(0); @@ -38,20 +67,6 @@ static LogosBlockchainModule* createStartedModule(LogosTestContext& t, QTemporar return module; } -// ============================================================================ -// Core metadata -// ============================================================================ - -LOGOS_TEST(name_returns_module_name) { - LogosBlockchainModule module; - LOGOS_ASSERT_EQ(module.name(), QString("liblogos_blockchain_module")); -} - -LOGOS_TEST(version_returns_module_version) { - LogosBlockchainModule module; - LOGOS_ASSERT_EQ(module.version(), QString("1.0.0")); -} - // ============================================================================ // generate_user_config // ============================================================================ @@ -62,9 +77,7 @@ LOGOS_TEST(generate_user_config_returns_0_on_success) { t.mockCFunction("generate_user_config").returns(0); - QVariantMap args; - args["output"] = "/tmp/test-config.json"; - LOGOS_ASSERT_EQ(module.generate_user_config(args), 0); + LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/test-config.json"})"), 0); LOGOS_ASSERT(t.cFunctionCalled("generate_user_config")); } @@ -74,17 +87,16 @@ LOGOS_TEST(generate_user_config_returns_1_on_failure) { t.mockCFunction("generate_user_config").returns(1); - QVariantMap args; - LOGOS_ASSERT_EQ(module.generate_user_config(args), 1); + LOGOS_ASSERT_EQ(module.generate_user_config("{}"), 1); } -LOGOS_TEST(generate_user_config_from_str_delegates_to_generate_user_config) { +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_from_str(R"({"output":"/tmp/out.json"})"), 0); + LOGOS_ASSERT_EQ(module.generate_user_config(R"({"output":"/tmp/out.json"})"), 0); LOGOS_ASSERT(t.cFunctionCalled("generate_user_config")); } @@ -94,19 +106,17 @@ LOGOS_TEST(generate_user_config_with_all_fields) { t.mockCFunction("generate_user_config").returns(0); - QVariantMap deployment; - deployment["well_known_deployment"] = "devnet"; - - QVariantMap args; - args["initial_peers"] = QStringList{"peer1", "peer2"}; - args["output"] = "/tmp/out.json"; - args["net_port"] = 9000; - args["blend_port"] = 9001; - args["http_addr"] = "0.0.0.0:8080"; - args["external_address"] = "1.2.3.4"; - args["no_public_ip_check"] = true; - args["deployment"] = deployment; - args["state_path"] = "/tmp/state"; + 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); } @@ -124,62 +134,55 @@ LOGOS_TEST(stop_without_node_returns_1) { LOGOS_TEST(wallet_get_balance_without_node_returns_error) { auto t = LogosTestContext("blockchain_module"); LogosBlockchainModule module; - QString result = module.wallet_get_balance(VALID_HEX); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("not running")); + 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; - QString result = module.wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("not running")); -} - -LOGOS_TEST(wallet_transfer_funds_single_sender_without_node_returns_error) { - auto t = LogosTestContext("blockchain_module"); - LogosBlockchainModule module; - QString result = module.wallet_transfer_funds(VALID_HEX, VALID_HEX, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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().isEmpty()); + 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; - QString result = module.blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {"locator1"}); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("not running")); + 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(module.get_block(VALID_HEX).startsWith("Error:")); + 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(module.get_blocks(0, 10).startsWith("Error:")); + 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(module.get_transaction(VALID_HEX).startsWith("Error:")); + 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(module.get_cryptarchia_info().startsWith("Error:")); + LOGOS_ASSERT_TRUE(starts_with(module.get_cryptarchia_info(), "Error:")); } // ============================================================================ @@ -188,7 +191,7 @@ LOGOS_TEST(get_cryptarchia_info_without_node_returns_error) { LOGOS_TEST(start_succeeds_with_mocked_dependencies) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; LOGOS_ASSERT_TRUE(tmpDir.isValid()); auto* module = createStartedModule(t, tmpDir); @@ -200,7 +203,7 @@ LOGOS_TEST(start_succeeds_with_mocked_dependencies) { LOGOS_TEST(start_returns_1_when_already_running) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); @@ -208,15 +211,9 @@ LOGOS_TEST(start_returns_1_when_already_running) { delete module; } -LOGOS_TEST(start_returns_2_without_logos_api) { - auto t = LogosTestContext("blockchain_module"); - LogosBlockchainModule module; - LOGOS_ASSERT_EQ(module.start("/tmp/config.json", ""), 2); -} - LOGOS_TEST(stop_succeeds_with_running_node) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); @@ -233,36 +230,36 @@ LOGOS_TEST(stop_succeeds_with_running_node) { LOGOS_TEST(wallet_get_balance_rejects_short_hex) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_get_balance("abcd"); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("64 hex")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_get_balance(QString(66, 'a')); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString hex = QString(62, 'a') + "zz"; - QString result = module->wallet_get_balance(hex); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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; } @@ -270,73 +267,73 @@ LOGOS_TEST(wallet_get_balance_rejects_invalid_chars) { LOGOS_TEST(wallet_transfer_funds_rejects_invalid_amount) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "not_a_number", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("Invalid amount")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds("bad", QStringList{VALID_HEX}, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("change_public_key")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, "short", "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("recipient_address")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{}, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("sender")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{"bad_addr"}, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("sender")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "100", "bad_tip"); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("tip")); + 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; } @@ -344,37 +341,37 @@ LOGOS_TEST(wallet_transfer_funds_rejects_invalid_optional_tip) { LOGOS_TEST(blend_join_rejects_invalid_provider_id) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->blend_join_as_core_node("short", VALID_HEX, VALID_HEX, {}); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("provider_id")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->blend_join_as_core_node(VALID_HEX, "short", VALID_HEX, {}); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("zk_id")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, "short", {}); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("locked_note_id")); + 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; } @@ -382,25 +379,25 @@ LOGOS_TEST(blend_join_rejects_invalid_locked_note_id) { LOGOS_TEST(get_block_rejects_invalid_hex) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->get_block("tooshort"); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("64 hex")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); - QString result = module->get_transaction("bad"); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("64 hex")); + std::string result = module->get_transaction("bad"); + LOGOS_ASSERT_TRUE(starts_with(result, "Error:")); + LOGOS_ASSERT_TRUE(contains(result, "64 hex")); delete module; } @@ -410,15 +407,15 @@ LOGOS_TEST(get_transaction_rejects_invalid_hex) { LOGOS_TEST(wallet_get_balance_accepts_0x_prefix) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + 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); - QString result = module->wallet_get_balance(VALID_HEX_WITH_PREFIX); - LOGOS_ASSERT_EQ(result, QString("42")); + std::string result = module->wallet_get_balance(VALID_HEX_WITH_PREFIX); + LOGOS_ASSERT_EQ(result, std::string("42")); delete module; } @@ -430,118 +427,117 @@ LOGOS_TEST(wallet_get_balance_accepts_0x_prefix) { LOGOS_TEST(wallet_get_balance_returns_balance_string) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + 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); - QString result = module->wallet_get_balance(VALID_HEX); - LOGOS_ASSERT_EQ(result, QString("1000")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_balance_error").returns(1); - QString result = module->wallet_get_balance(VALID_HEX); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("transfer_funds_error").returns(0); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "500", ""); - LOGOS_ASSERT_FALSE(result.startsWith("Error:")); - LOGOS_ASSERT_EQ(result.length(), 64); - LOGOS_ASSERT_TRUE(result.startsWith("ab")); + 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(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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("transfer_funds_error").returns(0); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "100", VALID_HEX); - LOGOS_ASSERT_FALSE(result.startsWith("Error:")); - LOGOS_ASSERT_EQ(result.length(), 64); + 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(result.length()), 64); delete module; } LOGOS_TEST(wallet_transfer_funds_returns_error_on_ffi_failure) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("transfer_funds_error").returns(1); - QString result = module->wallet_transfer_funds(VALID_HEX, QStringList{VALID_HEX}, VALID_HEX, "100", ""); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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_overload) { +LOGOS_TEST(wallet_transfer_funds_single_sender_via_vector) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("transfer_funds_error").returns(0); - QString result = module->wallet_transfer_funds(VALID_HEX, VALID_HEX, VALID_HEX, "100", ""); - LOGOS_ASSERT_FALSE(result.startsWith("Error:")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("transfer_funds_error").returns(0); - QStringList senders; - senders << VALID_HEX << VALID_HEX_WITH_PREFIX.mid(2); // two different addresses - QString result = module->wallet_transfer_funds(VALID_HEX, senders, VALID_HEX, "200", ""); - LOGOS_ASSERT_FALSE(result.startsWith("Error:")); + std::vector 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"); - QTemporaryDir tmpDir; + 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); - QStringList addrs = module->wallet_get_known_addresses(); - LOGOS_ASSERT_EQ(addrs.size(), 2); - // Mock fills addr0 with 0x11 → hex "1111...11", addr1 with 0x22 → "2222...22" - LOGOS_ASSERT_EQ(addrs[0], QString(64, '1')); - LOGOS_ASSERT_EQ(addrs[1], QString(64, '2')); + std::vector addrs = module->wallet_get_known_addresses(); + LOGOS_ASSERT_EQ(static_cast(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; @@ -549,14 +545,14 @@ LOGOS_TEST(wallet_get_known_addresses_returns_addresses) { LOGOS_TEST(wallet_get_known_addresses_returns_empty_on_ffi_failure) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_known_addresses_error").returns(1); - QStringList addrs = module->wallet_get_known_addresses(); - LOGOS_ASSERT_TRUE(addrs.isEmpty()); + std::vector addrs = module->wallet_get_known_addresses(); + LOGOS_ASSERT_TRUE(addrs.empty()); delete module; } @@ -564,31 +560,31 @@ LOGOS_TEST(wallet_get_known_addresses_returns_empty_on_ffi_failure) { LOGOS_TEST(blend_join_as_core_node_returns_declaration_id) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("blend_join_as_core_node_error").returns(0); - QStringList locators = {"locator1", "locator2"}; - QString 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(result.length(), 64); - LOGOS_ASSERT_TRUE(result.startsWith("cd")); + std::vector 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(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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("blend_join_as_core_node_error").returns(1); - QString result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {}); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + std::string result = module->blend_join_as_core_node(VALID_HEX, VALID_HEX, VALID_HEX, {}); + LOGOS_ASSERT_TRUE(starts_with(result, "Error:")); delete module; } @@ -596,16 +592,16 @@ LOGOS_TEST(blend_join_as_core_node_returns_error_on_ffi_failure) { LOGOS_TEST(get_block_returns_json_on_success) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + 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); - QString result = module->get_block(VALID_HEX); - LOGOS_ASSERT_TRUE(result.contains("slot")); - LOGOS_ASSERT_TRUE(result.contains("42")); + 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; @@ -613,55 +609,55 @@ LOGOS_TEST(get_block_returns_json_on_success) { LOGOS_TEST(get_block_returns_error_on_ffi_failure) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_block_error").returns(1); - QString result = module->get_block(VALID_HEX); - LOGOS_ASSERT_TRUE(result.startsWith("Error:")); + 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"); - QTemporaryDir tmpDir; + 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); - QString result = module->get_blocks(1, 10); - LOGOS_ASSERT_TRUE(result.contains("slot")); + 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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_blocks_error").returns(1); - LOGOS_ASSERT_TRUE(module->get_blocks(0, 10).startsWith("Error:")); + 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"); - QTemporaryDir tmpDir; + 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); - QString result = module->get_transaction(VALID_HEX); - LOGOS_ASSERT_TRUE(result.contains("confirmed")); + 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; @@ -669,13 +665,13 @@ LOGOS_TEST(get_transaction_returns_json_on_success) { LOGOS_TEST(get_transaction_returns_error_on_ffi_failure) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_transaction_error").returns(1); - LOGOS_ASSERT_TRUE(module->get_transaction(VALID_HEX).startsWith("Error:")); + LOGOS_ASSERT_TRUE(starts_with(module->get_transaction(VALID_HEX), "Error:")); delete module; } @@ -683,7 +679,7 @@ LOGOS_TEST(get_transaction_returns_error_on_ffi_failure) { LOGOS_TEST(get_cryptarchia_info_returns_json_on_success) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); @@ -692,15 +688,15 @@ LOGOS_TEST(get_cryptarchia_info_returns_json_on_success) { t.mockCFunction("cryptarchia_height").returns(50); t.mockCFunction("cryptarchia_mode").returns(1); // Online - QString result = module->get_cryptarchia_info(); - LOGOS_ASSERT_FALSE(result.startsWith("Error:")); - LOGOS_ASSERT_TRUE(result.contains("slot")); - LOGOS_ASSERT_TRUE(result.contains("100")); - LOGOS_ASSERT_TRUE(result.contains("height")); - LOGOS_ASSERT_TRUE(result.contains("50")); - LOGOS_ASSERT_TRUE(result.contains("Online")); - LOGOS_ASSERT_TRUE(result.contains("lib")); - LOGOS_ASSERT_TRUE(result.contains("tip")); + 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; @@ -708,26 +704,26 @@ LOGOS_TEST(get_cryptarchia_info_returns_json_on_success) { LOGOS_TEST(get_cryptarchia_info_bootstrapping_mode) { auto t = LogosTestContext("blockchain_module"); - QTemporaryDir tmpDir; + 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 - QString result = module->get_cryptarchia_info(); - LOGOS_ASSERT_TRUE(result.contains("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"); - QTemporaryDir tmpDir; + TempDir tmpDir; auto* module = createStartedModule(t, tmpDir); LOGOS_ASSERT_TRUE(module != nullptr); t.mockCFunction("get_cryptarchia_info_error").returns(1); - LOGOS_ASSERT_TRUE(module->get_cryptarchia_info().startsWith("Error:")); + LOGOS_ASSERT_TRUE(starts_with(module->get_cryptarchia_info(), "Error:")); delete module; }