logos-blockchain-ui/src/BlockchainBackend.cpp

323 lines
10 KiB
C++
Raw Normal View History

2026-02-11 20:39:32 +01:00
#include "BlockchainBackend.h"
#include "logos_api.h"
#include "logos_api_client.h"
2026-02-17 21:22:38 +01:00
#include <QByteArray>
#include <QClipboard>
2026-02-11 20:39:32 +01:00
#include <QDateTime>
#include <QDebug>
2026-02-24 17:05:46 +01:00
#include <QDir>
#include <QGuiApplication>
2026-02-24 17:05:46 +01:00
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QSignalBlocker>
2026-02-17 21:22:38 +01:00
#include <QTimer>
2026-02-11 20:39:32 +01:00
#include <QUrl>
#include <QVariant>
2026-02-11 20:39:32 +01:00
const QString BlockchainBackend::BLOCKCHAIN_MODULE_NAME =
QStringLiteral("liblogos_blockchain_module");
void BlockchainBackend::setError(const QString& message)
{
setLastErrorMessage(message);
setStatus(Error);
}
static QString toLocalPath(const QString& pathInput)
{
if (pathInput.trimmed().isEmpty())
return pathInput;
return QUrl::fromUserInput(pathInput).toLocalFile();
}
// invokeRemoteMethod() returns an invalid QVariant (not a LogosResult) when
// the call itself fails to get a reply (timeout, disconnected module, etc.),
// as opposed to the remote method running and reporting failure normally.
static LogosResult toLogosResult(const QVariant& reply)
{
if (!reply.isValid())
return LogosResult{false, QVariant(), QStringLiteral("Call failed.")};
return reply.value<LogosResult>();
}
static QString toErrorMessage(const LogosResult& result)
{
return QStringLiteral("Error: %1").arg(result.error.toString());
}
// QML callers distinguish success from failure by sniffing for an "Error"
// prefix (see BlockchainView.qml), so both branches must share this type.
// TODO: Currently functions are returning a string (for ok and ko cases), which is later re-parsed to know if the call
// succeeded or failed. We can simplify this by returning the result itself upward.
static QString toDisplayMessage(const LogosResult& result)
{
return result.success ? result.value.toString() : toErrorMessage(result);
}
2026-02-11 20:39:32 +01:00
BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
: BlockchainBackendSimpleSource(parent)
, m_logosAPI(logosAPI)
, m_accountsModel(new AccountsModel(this))
, m_logModel(new LogModel(this))
2026-02-11 20:39:32 +01:00
{
setStatus(NotStarted);
setUseGeneratedConfig(false);
setGeneratedUserConfigPath(
QDir::currentPath() + QStringLiteral("/user_config.yaml"));
// Restore saved config paths
QSettings s("Logos", "BlockchainUI");
const QString envConfigPath =
QString::fromUtf8(qgetenv("LB_CONFIG_PATH"));
const QString savedUserConfig =
s.value("userConfigPath").toString();
const QString savedDeploymentConfig =
s.value("deploymentConfigPath").toString();
if (!envConfigPath.isEmpty())
setUserConfig(toLocalPath(envConfigPath));
else if (!savedUserConfig.isEmpty())
setUserConfig(toLocalPath(savedUserConfig));
if (!savedDeploymentConfig.isEmpty())
setDeploymentConfig(toLocalPath(savedDeploymentConfig));
// Re-apply pre-.rep behavior: normalize file URLs, then persist (as master did in setters).
connect(this, &BlockchainBackendSimpleSource::userConfigChanged, this, [this]() {
const QString p = userConfig();
const QString n = toLocalPath(p);
if (n != p) {
QSignalBlocker b(this);
setUserConfig(n);
}
QSettings("Logos", "BlockchainUI")
.setValue("userConfigPath", userConfig());
});
connect(this, &BlockchainBackendSimpleSource::deploymentConfigChanged, this, [this]() {
const QString p = deploymentConfig();
const QString n = toLocalPath(p);
if (n != p) {
QSignalBlocker b(this);
setDeploymentConfig(n);
}
QSettings("Logos", "BlockchainUI")
.setValue("deploymentConfigPath", deploymentConfig());
});
2026-02-11 20:39:32 +01:00
if (!m_logosAPI) {
qWarning() << "BlockchainBackend: constructed without LogosAPI";
return;
2026-02-11 20:39:32 +01:00
}
m_blockchainClient = m_logosAPI->getClient(BLOCKCHAIN_MODULE_NAME);
if (!m_blockchainClient) {
setError(QStringLiteral("Module not initialized"));
qWarning() << "BlockchainBackend: failed to get blockchain module client";
2026-02-11 20:39:32 +01:00
return;
}
LogosObject* replica =
m_blockchainClient->requestObject(BLOCKCHAIN_MODULE_NAME);
if (replica) {
m_blockchainClient->onEvent(
replica, "newBlock",
[this](const QString&, const QVariantList& data) {
const QString timestamp =
QDateTime::currentDateTime().toString("HH:mm:ss");
QString line;
if (!data.isEmpty())
line = QString("[%1] New block: %2")
.arg(timestamp, data.first().toString());
else
line = QString("[%1] New block (no data)").arg(timestamp);
m_logModel->append(line);
});
} else {
setError(QStringLiteral("Failed to subscribe to events"));
2026-02-11 20:39:32 +01:00
}
qDebug() << "BlockchainBackend: initialized";
2026-02-11 20:39:32 +01:00
}
BlockchainBackend::~BlockchainBackend()
2026-02-11 20:39:32 +01:00
{
if (status() == Running || status() == Starting)
stopBlockchain();
2026-02-11 20:39:32 +01:00
}
QString BlockchainBackend::claimLeaderRewards()
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "leader_claim")));
}
2026-02-11 20:39:32 +01:00
void BlockchainBackend::startBlockchain()
{
if (!m_blockchainClient) {
setError(QStringLiteral("Module not initialized"));
2026-02-11 20:39:32 +01:00
return;
}
setStatus(Starting);
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "start", userConfig(), deploymentConfig()));
2026-02-11 20:39:32 +01:00
if (result.success) {
2026-02-11 20:39:32 +01:00
setStatus(Running);
2026-02-26 17:21:25 +01:00
QTimer::singleShot(500, this, [this]() { refreshAccounts(); });
2026-02-11 20:39:32 +01:00
} else {
setError(result.error.toString());
2026-02-11 20:39:32 +01:00
}
}
void BlockchainBackend::stopBlockchain()
{
if (status() != Running && status() != Starting)
2026-02-11 20:39:32 +01:00
return;
if (!m_blockchainClient) {
setError(QStringLiteral("Module not initialized"));
2026-02-11 20:39:32 +01:00
return;
}
setStatus(Stopping);
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "stop"));
2026-02-11 20:39:32 +01:00
if (result.success) {
2026-02-11 20:39:32 +01:00
setStatus(Stopped);
} else {
setError(result.error.toString());
}
}
void BlockchainBackend::refreshAccounts()
{
if (!m_blockchainClient) return;
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses"));
QStringList list;
if (result.success && result.value.canConvert<QStringList>())
list = result.value.toStringList();
m_accountsModel->setAddresses(list);
QTimer::singleShot(0, this,
[this, list]() { fetchBalancesForAccounts(list); });
}
void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
{
if (!m_blockchainClient) return;
for (const QString& address : list) {
if (address.isEmpty()) continue;
getBalance(address);
2026-02-11 20:39:32 +01:00
}
}
QString BlockchainBackend::getBalance(QString addressHex)
2026-02-11 20:39:32 +01:00
{
QString result;
if (!m_blockchainClient) {
result = QStringLiteral("Error: Module not initialized.");
2026-02-11 20:39:32 +01:00
} else {
result = toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex)));
2026-02-11 20:39:32 +01:00
}
m_accountsModel->setBalanceForAddress(addressHex, result);
return result;
2026-02-11 20:39:32 +01:00
}
2026-02-24 17:05:46 +01:00
QString BlockchainBackend::transferFunds(
QString fromKeyHex, QString toKeyHex, QString amountStr)
2026-02-24 17:05:46 +01:00
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
QStringList senders{fromKeyHex};
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_transfer_funds",
fromKeyHex, senders, toKeyHex, amountStr, QString())));
2026-02-24 17:05:46 +01:00
}
int BlockchainBackend::generateConfig(
QString outputPath, QStringList initialPeers, int netPort, int blendPort,
QString httpAddr, QString externalAddress, bool noPublicIpCheck,
int deploymentMode, QString deploymentConfigPath, QString statePath)
2026-02-24 17:05:46 +01:00
{
if (!m_blockchainClient)
2026-02-24 17:05:46 +01:00
return -1;
2026-02-24 17:05:46 +01:00
QVariantMap normalized;
QString out = outputPath.trimmed();
if (out.isEmpty())
2026-02-24 17:05:46 +01:00
out = generatedUserConfigPath();
else
2026-02-24 17:05:46 +01:00
out = toLocalPath(out);
normalized.insert("output", out);
2026-02-24 17:05:46 +01:00
if (!initialPeers.isEmpty()) {
QVariantList peersList;
for (const QString& p : initialPeers) {
if (!p.trimmed().isEmpty())
peersList.append(p.trimmed());
}
if (!peersList.isEmpty())
normalized.insert("initial_peers", peersList);
2026-02-24 17:05:46 +01:00
}
if (netPort > 0)
normalized.insert("net_port", netPort);
2026-02-24 17:05:46 +01:00
if (blendPort > 0)
normalized.insert("blend_port", blendPort);
2026-02-24 17:05:46 +01:00
if (!httpAddr.trimmed().isEmpty())
normalized.insert("http_addr", httpAddr.trimmed());
2026-02-24 17:05:46 +01:00
if (!externalAddress.trimmed().isEmpty())
normalized.insert("external_address", externalAddress.trimmed());
2026-02-24 17:05:46 +01:00
if (noPublicIpCheck)
normalized.insert("no_public_ip_check", true);
2026-02-24 17:05:46 +01:00
if (deploymentMode == 0) {
QVariantMap deployment;
deployment.insert("well_known_deployment", "devnet");
normalized.insert("deployment", deployment);
} else if (deploymentMode == 1
&& !deploymentConfigPath.trimmed().isEmpty()) {
2026-02-24 17:05:46 +01:00
QVariantMap deployment;
deployment.insert("config_path",
toLocalPath(deploymentConfigPath.trimmed()));
normalized.insert("deployment", deployment);
2026-02-24 17:05:46 +01:00
}
if (!statePath.trimmed().isEmpty())
normalized.insert("state_path", toLocalPath(statePath.trimmed()));
2026-02-24 17:05:46 +01:00
const QJsonDocument doc = QJsonDocument::fromVariant(normalized);
const QString jsonToSend =
QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
2026-02-24 17:05:46 +01:00
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "generate_user_config", jsonToSend));
return result.success ? 0 : -1;
2026-02-24 17:05:46 +01:00
}
void BlockchainBackend::clearLogs()
2026-02-24 17:05:46 +01:00
{
m_logModel->clear();
}
void BlockchainBackend::copyToClipboard(QString text)
{
if (QGuiApplication::clipboard())
QGuiApplication::clipboard()->setText(text);
2026-02-24 17:05:46 +01:00
}