diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index a7e04c0..199d095 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -35,13 +35,21 @@ static QString toLocalPath(const QString& 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. +namespace result { + +static LogosResult err(const QString& message) +{ + return LogosResult{false, QVariant(), message}; +} + +// Normalises a `QVariant` (e.g. from a `invokeRemoteMethod()`) call to a `LogosResult`. +// +// `invokeRemoteMethod()` might return an invalid `QVariant` when the call itself fails to get a reply (e.g.: timeout). +// This function normalises the reply for the `LogosResult` case. static LogosResult toLogosResult(const QVariant& reply) { if (!reply.isValid()) - return LogosResult{false, QVariant(), QStringLiteral("Call failed.")}; + return err(QStringLiteral("Call failed.")); return reply.value(); } @@ -50,15 +58,25 @@ 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. +// Returns a stringified version of a `LogosResult`. +// +// Used in some places that consume the success and error properties in the same manner. static QString toDisplayMessage(const LogosResult& result) { return result.success ? result.value.toString() : toErrorMessage(result); } +static QVariantMap toVariantMap(const LogosResult& result) +{ + return QVariantMap{ + {"success", result.success}, + {"value", result.value}, + {"error", result.error}, + }; +} + +} // namespace result + // Decode a base58 (Bitcoin alphabet) string to raw bytes. On an invalid // character *ok is set to false and an empty array is returned. static QByteArray decodeBase58(const QString& input, bool* ok) @@ -188,12 +206,12 @@ BlockchainBackend::~BlockchainBackend() stopBlockchain(); } -QString BlockchainBackend::claimLeaderRewards() +QVariantMap BlockchainBackend::claimLeaderRewards() { if (!m_blockchainClient) - return QStringLiteral("Error: Module not initialized."); + return result::toVariantMap(result::err(QStringLiteral("Module not initialized."))); - return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod( + return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "leader_claim"))); } @@ -206,14 +224,14 @@ void BlockchainBackend::startBlockchain() setStatus(Starting); - const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod( + const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "start", userConfig(), deploymentConfig())); - if (result.success) { + if (r.success) { setStatus(Running); QTimer::singleShot(500, this, [this]() { refreshAccounts(); }); } else { - setError(result.error.toString()); + setError(r.error.toString()); } } @@ -229,13 +247,13 @@ void BlockchainBackend::stopBlockchain() setStatus(Stopping); - const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod( + const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "stop")); - if (result.success) { + if (r.success) { setStatus(Stopped); } else { - setError(result.error.toString()); + setError(r.error.toString()); } } @@ -243,11 +261,11 @@ void BlockchainBackend::refreshAccounts() { if (!m_blockchainClient) return; - const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod( + const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses")); - if (!result.success) { - qWarning() << "refreshAccounts: failed:" << result.error.toString(); + if (!r.success) { + qWarning() << "refreshAccounts: failed:" << r.error.toString(); return; } @@ -256,7 +274,7 @@ void BlockchainBackend::refreshAccounts() // QVariantList under Qt6), and fall back to toStringList() for the rare // case where the value already arrives as a QStringList. QStringList list; - const QVariantList items = result.value.toList(); + const QVariantList items = r.value.toList(); if (!items.isEmpty()) { for (const QVariant& item : items) { const QString addr = item.toString(); @@ -264,7 +282,7 @@ void BlockchainBackend::refreshAccounts() list << addr; } } else { - list = result.value.toStringList(); + list = r.value.toStringList(); } qDebug() << "refreshAccounts: loaded" << list.size() << "addresses"; @@ -284,39 +302,36 @@ void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list) } } -QString BlockchainBackend::getBalance(QString addressHex) +QVariantMap BlockchainBackend::getBalance(QString addressHex) { - QString result; - if (!m_blockchainClient) { - result = QStringLiteral("Error: Module not initialized."); - } else { - result = toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod( - BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex))); - } + const LogosResult lr = m_blockchainClient + ? result::toLogosResult(m_blockchainClient->invokeRemoteMethod( + BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex)) + : result::err(QStringLiteral("Module not initialized.")); - m_accountsModel->setBalanceForAddress(addressHex, result); - return result; + m_accountsModel->setBalanceForAddress(addressHex, result::toDisplayMessage(lr)); + return result::toVariantMap(lr); } -QString BlockchainBackend::transferFunds( +QVariantMap BlockchainBackend::transferFunds( QString fromKeyHex, QString toKeyHex, QString amountStr) { if (!m_blockchainClient) - return QStringLiteral("Error: Module not initialized."); + return result::toVariantMap(result::err(QStringLiteral("Module not initialized."))); QStringList senders{fromKeyHex}; - return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod( + return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "wallet_transfer_funds", fromKeyHex, senders, toKeyHex, amountStr, QString()))); } -int BlockchainBackend::generateConfig( +QVariantMap BlockchainBackend::generateConfig( QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath) { if (!m_blockchainClient) - return -1; + return result::toVariantMap(result::err(QStringLiteral("Module not initialized."))); QVariantMap normalized; @@ -364,29 +379,27 @@ int BlockchainBackend::generateConfig( const QString jsonToSend = QString::fromUtf8(doc.toJson(QJsonDocument::Compact)); - const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod( - BLOCKCHAIN_MODULE_NAME, "generate_user_config", jsonToSend)); - - return result.success ? 0 : -1; + return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod( + BLOCKCHAIN_MODULE_NAME, "generate_user_config", jsonToSend))); } -QString BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex) +QVariantMap BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex) { if (!m_blockchainClient) - return QStringLiteral("Error: Module not initialized."); + return result::toVariantMap(result::err(QStringLiteral("Module not initialized."))); - return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod( + return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "wallet_get_notes", walletAddressHex, optionalTipHex))); } -QString BlockchainBackend::channelDepositWithNotes( +QVariantMap BlockchainBackend::channelDepositWithNotes( QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex) { if (!m_blockchainClient) - return QStringLiteral("Error: Module not initialized."); + return result::toVariantMap(result::err(QStringLiteral("Module not initialized."))); // The metadata arrives base58-encoded; the module expects metadata_hex, so // decode to bytes and hex-encode. Empty stays empty (metadata is optional). @@ -395,7 +408,7 @@ QString BlockchainBackend::channelDepositWithNotes( bool ok = false; const QByteArray bytes = decodeBase58(metadataBase58, &ok); if (!ok) - return QStringLiteral("Error: Invalid base58 metadata."); + return result::toVariantMap(result::err(QStringLiteral("Invalid base58 metadata."))); metadataHex = QString::fromLatin1(bytes.toHex()); } @@ -405,7 +418,7 @@ QString BlockchainBackend::channelDepositWithNotes( args << channelIdHex << inputNoteIdHexes << metadataHex << changePublicKeyHex << fundingPublicKeyHexes << maxTxFee << optionalTipHex; - return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod( + return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, QStringLiteral("channel_deposit_with_notes"), args))); } diff --git a/src/BlockchainBackend.h b/src/BlockchainBackend.h index 84b26aa..4c5a342 100644 --- a/src/BlockchainBackend.h +++ b/src/BlockchainBackend.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "rep_BlockchainBackend_source.h" @@ -41,15 +42,15 @@ public slots: void startBlockchain() override; void stopBlockchain() override; void refreshAccounts() override; - QString getBalance(QString addressHex) override; - QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override; - QString claimLeaderRewards() override; - int generateConfig(QString outputPath, QStringList initialPeers, int netPort, + QVariantMap getBalance(QString addressHex) override; + QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override; + QVariantMap claimLeaderRewards() override; + QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath) override; - QString getNotes(QString walletAddressHex, QString optionalTipHex) override; - QString channelDepositWithNotes(QString channelIdHex, + QVariantMap getNotes(QString walletAddressHex, QString optionalTipHex) override; + QVariantMap channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, diff --git a/src/BlockchainBackend.rep b/src/BlockchainBackend.rep index ed0e7b2..ca27996 100644 --- a/src/BlockchainBackend.rep +++ b/src/BlockchainBackend.rep @@ -12,12 +12,12 @@ class BlockchainBackend SLOT(void startBlockchain()) SLOT(void stopBlockchain()) SLOT(void refreshAccounts()) - SLOT(QString getBalance(QString addressHex)) - SLOT(QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr)) - SLOT(QString claimLeaderRewards()) - SLOT(int generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath)) - SLOT(QString getNotes(QString walletAddressHex, QString optionalTipHex)) - SLOT(QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex)) + SLOT(QVariantMap getBalance(QString addressHex)) + SLOT(QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr)) + SLOT(QVariantMap claimLeaderRewards()) + SLOT(QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath)) + SLOT(QVariantMap getNotes(QString walletAddressHex, QString optionalTipHex)) + SLOT(QVariantMap channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex)) SLOT(void clearLogs()) SLOT(void copyToClipboard(QString text)) } diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index e5c1109..21a2efa 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -59,6 +59,10 @@ Rectangle { QtObject { id: _d + function errorText(message) { + return qsTr("Error: %1").arg(message) + } + function getStatusString(s) { switch(s) { case BlockchainBackend.NotStarted: return qsTr("Not Started") @@ -66,7 +70,7 @@ Rectangle { case BlockchainBackend.Running: return qsTr("Running") case BlockchainBackend.Stopping: return qsTr("Stopping...") case BlockchainBackend.Stopped: return qsTr("Stopped") - case BlockchainBackend.Error: return qsTr("Error: %1").arg(root.backend.lastErrorMessage) + case BlockchainBackend.Error: return _d.errorText(root.backend.lastErrorMessage) default: return qsTr("Unknown") } } @@ -138,16 +142,14 @@ Rectangle { outputPath, initialPeers, netPort, blendPort, httpAddr, externalAddress, noPublicIpCheck, deploymentMode, deploymentConfigPath, statePath), - function(code) { - // logos.watch stringifies the returned int — coerce back. - var rc = parseInt(code, 10) - console.log("[BlockchainView] generateConfig success callback: code=", code, "type=", typeof code, "→ rc=", rc) - configChoiceView.generateResultSuccess = (rc === 0) + function(result) { + console.log("[BlockchainView] generateConfig success callback: result=", JSON.stringify(result)) + configChoiceView.generateResultSuccess = result.success configChoiceView.generateResultMessage = - rc === 0 + result.success ? qsTr("Config generated successfully.") - : qsTr("Generate failed (code: %1).").arg(rc) - if (rc === 0) { + : qsTr("Generate failed: %1").arg(result.error) + if (result.success) { root.backend.userConfig = (outputPath !== "") ? outputPath : root.backend.generatedUserConfigPath root.backend.deploymentConfig = @@ -230,17 +232,17 @@ Rectangle { logos.watch( root.backend.getBalance(addressHex), function(result) { - if ((result || "").indexOf("Error") === 0) { - walletView.lastBalanceErrorAddress = addressHex - walletView.lastBalanceError = result - } else { + if (result.success) { walletView.lastBalanceErrorAddress = "" walletView.lastBalanceError = "" + } else { + walletView.lastBalanceErrorAddress = addressHex + walletView.lastBalanceError = _d.errorText(result.error) } }, function(error) { walletView.lastBalanceErrorAddress = addressHex - walletView.lastBalanceError = "Error: " + error + walletView.lastBalanceError = _d.errorText(error) } ) } @@ -251,16 +253,28 @@ Rectangle { if (!root.backend) return logos.watch( root.backend.transferFunds(fromKeyHex, toKeyHex, amount), - function(result) { walletView.setTransferResult(result) }, - function(error) { walletView.setTransferResult("Error: " + error) } + function(result) { + if (result.success) { + walletView.setTransferResult(result.value) + } else { + walletView.setTransferResult(_d.errorText(result.error)) + } + }, + function(error) { walletView.setTransferResult(_d.errorText(error)) } ) } onClaimLeaderRewardsRequested: function() { if (!root.backend) return logos.watch( root.backend.claimLeaderRewards(), - function(result) { walletView.setLeaderClaimResult(result) }, - function(error) { walletView.setLeaderClaimResult("Error: " + error) } + function(result) { + if (result.success) { + walletView.setLeaderClaimResult(result.value) + } else { + walletView.setLeaderClaimResult(_d.errorText(result.error)) + } + }, + function(error) { walletView.setLeaderClaimResult(_d.errorText(error)) } ) } onRefreshAccountsRequested: if (root.backend) root.backend.refreshAccounts() @@ -294,8 +308,13 @@ Rectangle { if (!root.backend) return logos.watch( root.backend.getNotes(addressHex, optionalTipHex), - function(result) { channelDepositView.setNotesResult(result) }, - function(error) { channelDepositView.setNotesResult("Error: " + error) } + function(result) { + if (result.success) + channelDepositView.setNotes(result.value) + else + channelDepositView.setNotesError(_d.errorText(result.error)) + }, + function(error) { channelDepositView.setNotesError(_d.errorText(error)) } ) } onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { @@ -304,8 +323,13 @@ Rectangle { root.backend.channelDepositWithNotes( channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex), - function(result) { channelDepositView.setSubmitResult(result) }, - function(error) { channelDepositView.setSubmitResult("Error: " + error) } + function(result) { + if (result.success) + channelDepositView.setSubmitResult(true, result.value) + else + channelDepositView.setSubmitResult(false, _d.errorText(result.error)) + }, + function(error) { channelDepositView.setSubmitResult(false, _d.errorText(error)) } ) } onCopyToClipboard: (text) => { diff --git a/src/qml/views/ChannelDepositView.qml b/src/qml/views/ChannelDepositView.qml index 7cf2061..3e91281 100644 --- a/src/qml/views/ChannelDepositView.qml +++ b/src/qml/views/ChannelDepositView.qml @@ -31,14 +31,9 @@ ColumnLayout { noteSelector.errorText = "" } - function setNotesResult(jsonStr) { + function setNotes(jsonStr) { noteSelector.loading = false var s = jsonStr || "" - if (s.indexOf("Error") === 0) { - noteSelector.errorText = s - noteSelector.notes = [] - return - } try { var parsed = JSON.parse(s) d.notesTip = parsed.tip || "" @@ -50,11 +45,16 @@ ColumnLayout { } } - function setSubmitResult(resultStr) { + function setNotesError(message) { + noteSelector.loading = false + noteSelector.errorText = message + noteSelector.notes = [] + } + + function setSubmitResult(success, text) { d.resultPending = false - var s = resultStr || "" - d.resultSuccess = (s.indexOf("Error") !== 0) - d.resultText = s + d.resultSuccess = success + d.resultText = text } spacing: Theme.spacing.large