diff --git a/src/i_logos_blockchain_module.h b/src/i_logos_blockchain_module.h index aff5abf..ec56789 100644 --- a/src/i_logos_blockchain_module.h +++ b/src/i_logos_blockchain_module.h @@ -11,11 +11,15 @@ public: // Logos Core virtual void initLogos(LogosAPI* logosAPIInstance) = 0; - // Node + // ---- 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& addressHex) = 0; virtual QString wallet_transfer_funds( const QString& changePublicKey, @@ -25,6 +29,8 @@ public: const QString& optionalTipHex ) = 0; virtual QStringList wallet_get_known_addresses() = 0; + + // Blend virtual QString blend_join_as_core_node( const QString& providerIdHex, const QString& zkIdHex, @@ -32,10 +38,12 @@ public: const QStringList& locators ) = 0; - // Explorer + // Storage virtual QString get_block(const QString& headerIdHex) = 0; virtual QString get_blocks(quint64 fromSlot, quint64 toSlot) = 0; virtual QString get_transaction(const QString& txHashHex) = 0; + + // Cryptarchia virtual QString get_cryptarchia_info() = 0; }; diff --git a/src/logos_blockchain_module.cpp b/src/logos_blockchain_module.cpp index b25cba7..7ec94b2 100644 --- a/src/logos_blockchain_module.cpp +++ b/src/logos_blockchain_module.cpp @@ -26,383 +26,7 @@ namespace { return {}; return bytes; } -} // namespace -void LogosBlockchainModule::onNewBlockCallback(const char* block) { - if (s_instance) { - qInfo() << "Received new block: " << block; - QVariantList data; - data.append(QString::fromUtf8(block)); - s_instance->emitEvent("newBlock", data); - // SAFETY: - // We are getting an owned pointer here which is freed after this callback is called, so there is not need to - // free the resrouce here as we are copying the data! - } -} - -LogosBlockchainModule::LogosBlockchainModule() { - node = nullptr; -} - -LogosBlockchainModule::~LogosBlockchainModule() { - s_instance = nullptr; - if (node) { - stop(); - } -} - -QString LogosBlockchainModule::name() const { - return "liblogos_blockchain_module"; -} - -QString LogosBlockchainModule::version() const { - return "1.0.0"; -} - -void LogosBlockchainModule::initLogos(LogosAPI* logosAPIInstance) { - logosAPI = logosAPIInstance; - if (logosAPI) { - client = logosAPI->getClient("liblogos_blockchain_module"); - if (!client) { - qWarning() << "LogosBlockchainModule: Failed to get liblogos_blockchain_module client"; - } - } -} - -int LogosBlockchainModule::start(const QString& config_path, const QString& deployment) { - if (node) { - qWarning() << "Could not execute the operation: The node is already running."; - return 1; - } - - // Set LOGOS_BLOCKCHAIN_CIRCUITS env variable (use QDir for correct path separator on all platforms) - const QString circuits_path = - QDir(logosAPI->property("modulePath").toString()).filePath(QStringLiteral("circuits")); - qputenv("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_path.toUtf8()); - qInfo() << "LOGOS_BLOCKCHAIN_CIRCUITS set to:" << circuits_path; - QString 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 (env && *env) { - effective_config_path = QString::fromUtf8(env); - qInfo() << "Using config from LB_CONFIG_PATH:" << effective_config_path; - } else { - qCritical() << "Config path was not specified and LB_CONFIG_PATH is not set."; - return 2; - } - } - - qInfo() << "Starting the node with the configuration file:" << effective_config_path; - qInfo() << "Using deployment:" << (deployment.isEmpty() ? "" : deployment); - - const QByteArray config_path_buffer = effective_config_path.toUtf8(); - const char* config_path_ptr = effective_config_path.isEmpty() ? nullptr : config_path_buffer.constData(); - - const QByteArray deployment_buffer = deployment.toUtf8(); - const char* deployment_ptr = deployment.isEmpty() ? nullptr : deployment_buffer.constData(); - - auto [value, error] = start_lb_node(config_path_ptr, deployment_ptr); - qInfo() << "Start node returned with value and error."; - if (!is_ok(&error)) { - qCritical() << "Failed to start the node. Error:" << error; - return 3; - } - - node = value; - qInfo() << "The node was started successfully."; - - // Subscribe to block events - if (!node) { - qWarning() << "Could not subcribe to block events: The node is not running."; - return 1; - } - - s_instance = this; - subscribe_to_new_blocks(node, onNewBlockCallback); - - return 0; -} - -int LogosBlockchainModule::stop() { - if (!node) { - qWarning() << "Could not execute the operation: The node is not running."; - return 1; - } - - s_instance = nullptr; // Clear before stopping to prevent callbacks during shutdown - - const OperationStatus status = stop_node(node); - if (is_ok(&status)) { - qInfo() << "The node was stopped successfully."; - } else { - qCritical() << "Could not stop the node. Error:" << status; - } - - node = nullptr; - return 0; -} - -QString LogosBlockchainModule::wallet_get_balance(const QString& addressHex) { - qDebug() << "wallet_get_balance: addressHex=" << addressHex; - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - const QByteArray bytes = parseAddressHex(addressHex); - if (bytes.isEmpty() || bytes.size() != kAddressBytes) { - return QStringLiteral("Error: Address must be 64 hex characters (32 bytes)."); - } - - auto [value, error] = get_balance(node, reinterpret_cast(bytes.constData()), nullptr); - if (!is_ok(&error)) { - return QStringLiteral("Error: Failed to get balance: ") + QString::number(error); - } - - return QString::number(value); -} - -QString LogosBlockchainModule::wallet_transfer_funds( - const QString& changePublicKey, - const QStringList& senderAddresses, - const QString& recipientAddress, - const QString& amount, - const QString& optionalTipHex -) { - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - bool ok = false; - const quint64 amountVal = amount.trimmed().toULongLong(&ok); - if (!ok) { - return QStringLiteral("Error: Invalid amount (positive integer required)."); - } - - const QByteArray changeBytes = parseAddressHex(changePublicKey); - if (changeBytes.isEmpty() || changeBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid changePublicKey (64 hex characters required)."); - } - const QByteArray recipientBytes = parseAddressHex(recipientAddress); - if (recipientBytes.isEmpty() || recipientBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid recipientAddress (64 hex characters required)."); - } - if (senderAddresses.isEmpty()) { - return QStringLiteral("Error: At least one sender address required."); - } - QVector fundingBytes; - for (const QString& hex : senderAddresses) { - QByteArray b = parseAddressHex(hex); - if (b.isEmpty() || b.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid sender address (64 hex characters required)."); - } - fundingBytes.append(b); - } - QVector fundingPtrs; - for (const QByteArray& b : fundingBytes) - fundingPtrs.append(reinterpret_cast(b.constData())); - - QByteArray tipBytes; // NOLINT: Needs to be outside of scope for lifetime - const HeaderId* optional_tip = nullptr; - if (!optionalTipHex.isEmpty()) { - tipBytes = parseAddressHex(optionalTipHex); - if (tipBytes.isEmpty() || tipBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid optional tip (64 hex characters or empty)."); - } - optional_tip = reinterpret_cast(tipBytes.constData()); - } - - TransferFundsArguments args{}; - args.optional_tip = optional_tip; - args.change_public_key = reinterpret_cast(changeBytes.constData()); - args.funding_public_keys = fundingPtrs.constData(); - args.funding_public_keys_len = static_cast(fundingPtrs.size()); - args.recipient_public_key = reinterpret_cast(recipientBytes.constData()); - args.amount = amountVal; - - auto [value, error] = transfer_funds(node, &args); - if (!is_ok(&error)) { - return QStringLiteral("Error: Failed to transfer funds: ") + QString::number(error); - } - // value is Hash (32 bytes); convert to hex string - const QByteArray hashBytes(reinterpret_cast(&value), kAddressBytes); - return QString::fromUtf8(hashBytes.toHex()); -} - -QString LogosBlockchainModule::wallet_transfer_funds( - const QString& changePublicKey, - const QString& senderAddress, - const QString& recipientAddress, - const QString& amount, - const QString& optionalTipHex -) { - return wallet_transfer_funds(changePublicKey, QStringList{senderAddress}, recipientAddress, amount, optionalTipHex); -} - -QStringList LogosBlockchainModule::wallet_get_known_addresses() { - QStringList out; - if (!node) { - qWarning() << "Could not execute the operation: The node is not running."; - return out; - } - auto [value, error] = get_known_addresses(node); - if (!is_ok(&error)) { - qCritical() << "Failed to get known addresses. Error:" << error; - return out; - } - // Each address is kAddressBytes (32) byte - for (size_t i = 0; i < value.len; ++i) { - const uint8_t* ptr = value.addresses[i]; // NOLINT: Move definition to if-statement - if (ptr) { - QByteArray addr(reinterpret_cast(ptr), kAddressBytes); - out.append(QString::fromUtf8(addr.toHex())); - } - } - free_known_addresses(value); - qDebug() << "blockchain lib: known addresses, count=" << out.size() - << "sample:" << (out.isEmpty() ? QLatin1String("(none)") : out.constFirst()); - return out; -} - -QString LogosBlockchainModule::blend_join_as_core_node( - const QString& providerIdHex, - const QString& zkIdHex, - const QString& lockedNoteIdHex, - const QStringList& locators -) { - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - const QByteArray providerIdBytes = parseAddressHex(providerIdHex); - if (providerIdBytes.isEmpty() || providerIdBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid providerId (64 hex characters required)."); - } - - const QByteArray zkIdBytes = parseAddressHex(zkIdHex); - if (zkIdBytes.isEmpty() || zkIdBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid zkId (64 hex characters required)."); - } - - const QByteArray lockedNoteIdBytes = parseAddressHex(lockedNoteIdHex); - if (lockedNoteIdBytes.isEmpty() || lockedNoteIdBytes.size() != kAddressBytes) { - return QStringLiteral("Error: Invalid lockedNoteId (64 hex characters required)."); - } - - // QString is UTF-16, but the FFI requires UTF-8. - // locatorsData owns the converted buffers, while locatorsPtrs holds raw pointers into them for the FFI call. - // Using reserve() prevents reallocation, keeping the constData() pointers stable. - std::vector locatorsData; - std::vector locatorsPtrs; - locatorsData.reserve(locators.size()); - locatorsPtrs.reserve(locators.size()); - for (const QString& locator : locators) { - locatorsData.push_back(locator.toUtf8()); - locatorsPtrs.push_back(locatorsData.back().constData()); - } - - auto [value, error] = ::blend_join_as_core_node( - node, - reinterpret_cast(providerIdBytes.constData()), - reinterpret_cast(zkIdBytes.constData()), - reinterpret_cast(lockedNoteIdBytes.constData()), - locatorsPtrs.data(), - locatorsPtrs.size() - ); - if (!is_ok(&error)) { - qCritical() << "Failed to join as core node. Error:" << error; - return QStringLiteral("Error: Failed to join as core node: ") + QString::number(error); - } - - const QByteArray declarationIdBytes(reinterpret_cast(&value), sizeof(value)); - qInfo() << "Successfully joined as core node. DeclarationId:" << declarationIdBytes.toHex(); - return QString::fromUtf8(declarationIdBytes.toHex()); -} - -QString LogosBlockchainModule::get_block(const QString& headerIdHex) { - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - const QByteArray bytes = parseAddressHex(headerIdHex); - if (bytes.isEmpty() || bytes.size() != kAddressBytes) { - return QStringLiteral("Error: Header ID must be 64 hex characters (32 bytes)."); - } - - auto [value, error] = ::get_block(node, reinterpret_cast(bytes.constData())); - if (!is_ok(&error)) { - qWarning() << "Failed to get block. Error:" << error; - return QStringLiteral("Error: Failed to get block: ") + QString::number(error); - } - - const QString result = QString::fromUtf8(value); - free_cstring(value); - return result; -} - -QString LogosBlockchainModule::get_transaction(const QString& txHashHex) { - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - const QByteArray bytes = parseAddressHex(txHashHex); - if (bytes.isEmpty() || bytes.size() != kAddressBytes) { - return QStringLiteral("Error: Transaction hash must be 64 hex characters (32 bytes)."); - } - - auto [value, error] = ::get_transaction(node, reinterpret_cast(bytes.constData())); - if (!is_ok(&error)) { - qWarning() << "Failed to get transaction. Error:" << error; - return QStringLiteral("Error: Failed to get transaction: ") + QString::number(error); - } - - const QString result = QString::fromUtf8(value); - free_cstring(value); - return result; -} - -QString LogosBlockchainModule::get_blocks(const quint64 fromSlot, const quint64 toSlot) { - if (!node) { - return QStringLiteral("Error: The node is not running."); - } - - auto [value, error] = ::get_blocks(node, fromSlot, toSlot); - if (!is_ok(&error)) { - qWarning() << "Failed to get blocks. Error:" << error; - return QStringLiteral("Error: Failed to get blocks: ") + QString::number(error); - } - - const QString result = QString::fromUtf8(value); - free_cstring(value); - return result; -} - -QString LogosBlockchainModule::get_cryptarchia_info() { - if (!node) { - return QStringLiteral("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); - } - - QJsonObject obj; - obj[QStringLiteral("lib")] = - QString::fromUtf8(QByteArray(reinterpret_cast(value->lib), kAddressBytes).toHex()); - obj[QStringLiteral("tip")] = - QString::fromUtf8(QByteArray(reinterpret_cast(value->tip), kAddressBytes).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"); - - free_cryptarchia_info(value); - return QJsonDocument(obj).toJson(QJsonDocument::Compact); -} - -namespace { // Wrapper that owns data and provides GenerateConfigArgs struct OwnedGenerateConfigArgs { std::vector initial_peers_data; @@ -527,6 +151,53 @@ namespace { }; } // namespace +void LogosBlockchainModule::onNewBlockCallback(const char* block) { + if (s_instance) { + qInfo() << "Received new block: " << block; + QVariantList data; + data.append(QString::fromUtf8(block)); + s_instance->emitEvent("newBlock", data); + // SAFETY: + // We are getting an owned pointer here which is freed after this callback is called, so there is not need to + // free the resrouce here as we are copying the data! + } +} + +LogosBlockchainModule::LogosBlockchainModule() { + node = nullptr; +} + +LogosBlockchainModule::~LogosBlockchainModule() { + s_instance = nullptr; + if (node) { + stop(); + } +} + +// Logos Core + +QString LogosBlockchainModule::name() const { + return "liblogos_blockchain_module"; +} + +QString LogosBlockchainModule::version() const { + return "1.0.0"; +} + +void LogosBlockchainModule::initLogos(LogosAPI* logosAPIInstance) { + logosAPI = logosAPIInstance; + 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); @@ -544,6 +215,347 @@ int LogosBlockchainModule::generate_user_config_from_str(const QString& args) { return generate_user_config(parsed_args); } +int LogosBlockchainModule::start(const QString& config_path, const QString& deployment) { + if (node) { + qWarning() << "Could not execute the operation: The node is already running."; + return 1; + } + + // Set LOGOS_BLOCKCHAIN_CIRCUITS env variable (use QDir for correct path separator on all platforms) + const QString circuits_path = + QDir(logosAPI->property("modulePath").toString()).filePath(QStringLiteral("circuits")); + qputenv("LOGOS_BLOCKCHAIN_CIRCUITS", circuits_path.toUtf8()); + qInfo() << "LOGOS_BLOCKCHAIN_CIRCUITS set to:" << circuits_path; + QString 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 (env && *env) { + effective_config_path = QString::fromUtf8(env); + qInfo() << "Using config from LB_CONFIG_PATH:" << effective_config_path; + } else { + qCritical() << "Config path was not specified and LB_CONFIG_PATH is not set."; + return 2; + } + } + + qInfo() << "Starting the node with the configuration file:" << effective_config_path; + qInfo() << "Using deployment:" << (deployment.isEmpty() ? "" : deployment); + + const QByteArray config_path_buffer = effective_config_path.toUtf8(); + const char* config_path_ptr = effective_config_path.isEmpty() ? nullptr : config_path_buffer.constData(); + + const QByteArray deployment_buffer = deployment.toUtf8(); + const char* deployment_ptr = deployment.isEmpty() ? nullptr : deployment_buffer.constData(); + + auto [value, error] = start_lb_node(config_path_ptr, deployment_ptr); + qInfo() << "Start node returned with value and error."; + if (!is_ok(&error)) { + qCritical() << "Failed to start the node. Error:" << error; + return 3; + } + + node = value; + qInfo() << "The node was started successfully."; + + // Subscribe to block events + if (!node) { + qWarning() << "Could not subcribe to block events: The node is not running."; + return 1; + } + + s_instance = this; + subscribe_to_new_blocks(node, onNewBlockCallback); + + return 0; +} + +int LogosBlockchainModule::stop() { + if (!node) { + qWarning() << "Could not execute the operation: The node is not running."; + return 1; + } + + s_instance = nullptr; // Clear before stopping to prevent callbacks during shutdown + + const OperationStatus status = stop_node(node); + if (is_ok(&status)) { + qInfo() << "The node was stopped successfully."; + } else { + qCritical() << "Could not stop the node. Error:" << status; + } + + node = nullptr; + return 0; +} + +// Wallet + +QString LogosBlockchainModule::wallet_get_balance(const QString& addressHex) { + qDebug() << "wallet_get_balance: addressHex=" << addressHex; + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + const QByteArray bytes = parseAddressHex(addressHex); + if (bytes.isEmpty() || bytes.size() != kAddressBytes) { + return QStringLiteral("Error: Address must be 64 hex characters (32 bytes)."); + } + + auto [value, error] = get_balance(node, reinterpret_cast(bytes.constData()), nullptr); + if (!is_ok(&error)) { + return QStringLiteral("Error: Failed to get balance: ") + QString::number(error); + } + + return QString::number(value); +} + +QString LogosBlockchainModule::wallet_transfer_funds( + const QString& changePublicKey, + const QStringList& senderAddresses, + const QString& recipientAddress, + const QString& amount, + const QString& optionalTipHex +) { + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + bool ok = false; + const quint64 amountVal = amount.trimmed().toULongLong(&ok); + if (!ok) { + return QStringLiteral("Error: Invalid amount (positive integer required)."); + } + + const QByteArray changeBytes = parseAddressHex(changePublicKey); + if (changeBytes.isEmpty() || changeBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid changePublicKey (64 hex characters required)."); + } + const QByteArray recipientBytes = parseAddressHex(recipientAddress); + if (recipientBytes.isEmpty() || recipientBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid recipientAddress (64 hex characters required)."); + } + if (senderAddresses.isEmpty()) { + return QStringLiteral("Error: At least one sender address required."); + } + QVector fundingBytes; + for (const QString& hex : senderAddresses) { + QByteArray b = parseAddressHex(hex); + if (b.isEmpty() || b.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid sender address (64 hex characters required)."); + } + fundingBytes.append(b); + } + QVector fundingPtrs; + for (const QByteArray& b : fundingBytes) + fundingPtrs.append(reinterpret_cast(b.constData())); + + QByteArray tipBytes; // NOLINT: Needs to be outside of scope for lifetime + const HeaderId* optional_tip = nullptr; + if (!optionalTipHex.isEmpty()) { + tipBytes = parseAddressHex(optionalTipHex); + if (tipBytes.isEmpty() || tipBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid optional tip (64 hex characters or empty)."); + } + optional_tip = reinterpret_cast(tipBytes.constData()); + } + + TransferFundsArguments args{}; + args.optional_tip = optional_tip; + args.change_public_key = reinterpret_cast(changeBytes.constData()); + args.funding_public_keys = fundingPtrs.constData(); + args.funding_public_keys_len = static_cast(fundingPtrs.size()); + args.recipient_public_key = reinterpret_cast(recipientBytes.constData()); + args.amount = amountVal; + + auto [value, error] = transfer_funds(node, &args); + if (!is_ok(&error)) { + return QStringLiteral("Error: Failed to transfer funds: ") + QString::number(error); + } + // value is Hash (32 bytes); convert to hex string + const QByteArray hashBytes(reinterpret_cast(&value), kAddressBytes); + return QString::fromUtf8(hashBytes.toHex()); +} + +QString LogosBlockchainModule::wallet_transfer_funds( + const QString& changePublicKey, + const QString& senderAddress, + const QString& recipientAddress, + const QString& amount, + const QString& optionalTipHex +) { + return wallet_transfer_funds(changePublicKey, QStringList{senderAddress}, recipientAddress, amount, optionalTipHex); +} + +QStringList LogosBlockchainModule::wallet_get_known_addresses() { + QStringList out; + if (!node) { + qWarning() << "Could not execute the operation: The node is not running."; + return out; + } + auto [value, error] = get_known_addresses(node); + if (!is_ok(&error)) { + qCritical() << "Failed to get known addresses. Error:" << error; + return out; + } + // Each address is kAddressBytes (32) byte + for (size_t i = 0; i < value.len; ++i) { + const uint8_t* ptr = value.addresses[i]; // NOLINT: Move definition to if-statement + if (ptr) { + QByteArray addr(reinterpret_cast(ptr), kAddressBytes); + out.append(QString::fromUtf8(addr.toHex())); + } + } + free_known_addresses(value); + qDebug() << "blockchain lib: known addresses, count=" << out.size() + << "sample:" << (out.isEmpty() ? QLatin1String("(none)") : out.constFirst()); + return out; +} + +// Blend + +QString LogosBlockchainModule::blend_join_as_core_node( + const QString& providerIdHex, + const QString& zkIdHex, + const QString& lockedNoteIdHex, + const QStringList& locators +) { + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + const QByteArray providerIdBytes = parseAddressHex(providerIdHex); + if (providerIdBytes.isEmpty() || providerIdBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid providerId (64 hex characters required)."); + } + + const QByteArray zkIdBytes = parseAddressHex(zkIdHex); + if (zkIdBytes.isEmpty() || zkIdBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid zkId (64 hex characters required)."); + } + + const QByteArray lockedNoteIdBytes = parseAddressHex(lockedNoteIdHex); + if (lockedNoteIdBytes.isEmpty() || lockedNoteIdBytes.size() != kAddressBytes) { + return QStringLiteral("Error: Invalid lockedNoteId (64 hex characters required)."); + } + + // QString is UTF-16, but the FFI requires UTF-8. + // locatorsData owns the converted buffers, while locatorsPtrs holds raw pointers into them for the FFI call. + // Using reserve() prevents reallocation, keeping the constData() pointers stable. + std::vector locatorsData; + std::vector locatorsPtrs; + locatorsData.reserve(locators.size()); + locatorsPtrs.reserve(locators.size()); + for (const QString& locator : locators) { + locatorsData.push_back(locator.toUtf8()); + locatorsPtrs.push_back(locatorsData.back().constData()); + } + + auto [value, error] = ::blend_join_as_core_node( + node, + reinterpret_cast(providerIdBytes.constData()), + reinterpret_cast(zkIdBytes.constData()), + reinterpret_cast(lockedNoteIdBytes.constData()), + locatorsPtrs.data(), + locatorsPtrs.size() + ); + if (!is_ok(&error)) { + qCritical() << "Failed to join as core node. Error:" << error; + return QStringLiteral("Error: Failed to join as core node: ") + QString::number(error); + } + + const QByteArray declarationIdBytes(reinterpret_cast(&value), sizeof(value)); + qInfo() << "Successfully joined as core node. DeclarationId:" << declarationIdBytes.toHex(); + return QString::fromUtf8(declarationIdBytes.toHex()); +} + +// Storage + +QString LogosBlockchainModule::get_block(const QString& headerIdHex) { + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + const QByteArray bytes = parseAddressHex(headerIdHex); + if (bytes.isEmpty() || bytes.size() != kAddressBytes) { + return QStringLiteral("Error: Header ID must be 64 hex characters (32 bytes)."); + } + + auto [value, error] = ::get_block(node, reinterpret_cast(bytes.constData())); + if (!is_ok(&error)) { + qWarning() << "Failed to get block. Error:" << error; + return QStringLiteral("Error: Failed to get block: ") + QString::number(error); + } + + const QString result = QString::fromUtf8(value); + free_cstring(value); + return result; +} + +QString LogosBlockchainModule::get_blocks(const quint64 fromSlot, const quint64 toSlot) { + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + auto [value, error] = ::get_blocks(node, fromSlot, toSlot); + if (!is_ok(&error)) { + qWarning() << "Failed to get blocks. Error:" << error; + return QStringLiteral("Error: Failed to get blocks: ") + QString::number(error); + } + + const QString result = QString::fromUtf8(value); + free_cstring(value); + return result; +} + +QString LogosBlockchainModule::get_transaction(const QString& txHashHex) { + if (!node) { + return QStringLiteral("Error: The node is not running."); + } + + const QByteArray bytes = parseAddressHex(txHashHex); + if (bytes.isEmpty() || bytes.size() != kAddressBytes) { + return QStringLiteral("Error: Transaction hash must be 64 hex characters (32 bytes)."); + } + + auto [value, error] = ::get_transaction(node, reinterpret_cast(bytes.constData())); + if (!is_ok(&error)) { + qWarning() << "Failed to get transaction. Error:" << error; + return QStringLiteral("Error: Failed to get transaction: ") + QString::number(error); + } + + const QString result = QString::fromUtf8(value); + free_cstring(value); + return result; +} + +// Cryptarchia + +QString LogosBlockchainModule::get_cryptarchia_info() { + if (!node) { + return QStringLiteral("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); + } + + QJsonObject obj; + obj[QStringLiteral("lib")] = + QString::fromUtf8(QByteArray(reinterpret_cast(value->lib), kAddressBytes).toHex()); + obj[QStringLiteral("tip")] = + QString::fromUtf8(QByteArray(reinterpret_cast(value->tip), kAddressBytes).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"); + + free_cryptarchia_info(value); + return QJsonDocument(obj).toJson(QJsonDocument::Compact); +} + void LogosBlockchainModule::emitEvent(const QString& eventName, const QVariantList& data) { if (!logosAPI) { qWarning() << "LogosBlockchainModule: LogosAPI not available, cannot emit" << eventName; diff --git a/src/logos_blockchain_module.h b/src/logos_blockchain_module.h index 2510807..71f7038 100644 --- a/src/logos_blockchain_module.h +++ b/src/logos_blockchain_module.h @@ -25,11 +25,15 @@ public: [[nodiscard]] QString version() const override; Q_INVOKABLE void initLogos(LogosAPI*) override; - // Logos Blockchain + // ---- 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; + + // Wallet Q_INVOKABLE QString wallet_get_balance(const QString& addressHex) override; Q_INVOKABLE QString wallet_transfer_funds( const QString& changePublicKey, @@ -46,6 +50,8 @@ public: const QString& optionalTipHex ); Q_INVOKABLE QStringList wallet_get_known_addresses() override; + + // Blend Q_INVOKABLE QString blend_join_as_core_node( const QString& providerIdHex, const QString& zkIdHex, @@ -53,10 +59,12 @@ public: const QStringList& locators ) override; - // Explorer + // Storage Q_INVOKABLE QString get_block(const QString& headerIdHex) override; Q_INVOKABLE QString get_blocks(quint64 fromSlot, quint64 toSlot) override; Q_INVOKABLE QString get_transaction(const QString& txHashHex) override; + + // Cryptarchia Q_INVOKABLE QString get_cryptarchia_info() override; signals: