update api

update api

simplify

simplify
This commit is contained in:
Logos Workspace 2026-05-08 14:47:22 -04:00
parent 7d2ae4a69d
commit e8cd77139d
6 changed files with 507 additions and 628 deletions

View File

@ -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

View File

@ -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" }

View File

@ -1,53 +0,0 @@
#ifndef I_LOGOS_BLOCKCHAIN_MODULE_API_H
#define I_LOGOS_BLOCKCHAIN_MODULE_API_H
#include <QString>
#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

View File

@ -1,14 +1,17 @@
#include "logos_blockchain_module.h"
#include "logos_api_client.h"
#include <QByteArray>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QMetaType>
#include <QUrl>
#include <QVariant>
#include <QVariantList>
#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;
@ -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<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();
@ -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<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;
@ -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<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;
@ -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<std::string>();
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<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);
}

View File

@ -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 "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<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);
};

View File

@ -4,28 +4,57 @@
#include <logos_test.h>
#include "logos_blockchain_module.h"
#include <QDir>
#include <QFile>
#include <QTemporaryDir>
#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 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<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");
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<int>(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<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");
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<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;
@ -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<std::string> 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<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");
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;
}