fix: Tier-2 involved mediums — send confirm re-validation + console JSON errors
- Send: re-validate against LIVE state at the "Confirm & Send" click (connected,
not syncing, amount > 0, total <= available). The confirm dialog persists
across frames, so a balance drop / sync restart / fee bump after Review could
otherwise broadcast a now-invalid transaction; it now shows a clear message
instead of sending.
- Console: a malformed JSON argument ({...}/[...] that fails to parse) was
silently sent to the daemon as a plain string. Now the command fails with a
clear "Argument N is not valid JSON" line instead of being silently mangled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,13 @@ void FullNodeConsoleExecutor::submit(const std::string& cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto call = BuildConsoleRpcCall(cmd);
|
auto call = BuildConsoleRpcCall(cmd);
|
||||||
if (!call.valid) return;
|
if (!call.valid) {
|
||||||
|
if (!call.error.empty()) {
|
||||||
|
std::lock_guard<std::mutex> lk(results_mutex_);
|
||||||
|
results_.push_back({call.error, true});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const std::string method = call.method;
|
const std::string method = call.method;
|
||||||
const nlohmann::json params = call.params;
|
const nlohmann::json params = call.params;
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ ConsoleRpcCall BuildConsoleRpcCall(const std::string& command)
|
|||||||
call.params.push_back(parsed);
|
call.params.push_back(parsed);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Malformed JSON — fail the whole command instead of silently sending it as a string.
|
||||||
|
call.valid = false;
|
||||||
|
call.error = "Argument " + std::to_string(argIndex) + " is not valid JSON: " + arg;
|
||||||
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg == "true") {
|
if (arg == "true") {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ struct ConsoleRpcCall {
|
|||||||
bool valid = false;
|
bool valid = false;
|
||||||
std::string method;
|
std::string method;
|
||||||
nlohmann::json params = nlohmann::json::array();
|
nlohmann::json params = nlohmann::json::array();
|
||||||
|
std::string error; // set (with valid=false) when an argument fails to parse
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ConsoleResultLineRole {
|
enum class ConsoleResultLineRole {
|
||||||
|
|||||||
@@ -794,9 +794,20 @@ void RenderSendConfirmPopup(App* app) {
|
|||||||
Type().text(TypeStyle::Body2, TR("sending"));
|
Type().text(TypeStyle::Body2, TR("sending"));
|
||||||
} else {
|
} else {
|
||||||
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
|
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
|
||||||
|
// Re-validate against LIVE state — the confirm dialog persists across frames, so the
|
||||||
|
// balance could have dropped or sync (re)started (or the fee bumped total over available)
|
||||||
|
// since Review. Don't broadcast a now-invalid transaction.
|
||||||
|
const auto& liveState = app->getWalletState();
|
||||||
|
double liveAvail = GetAvailableBalance(app);
|
||||||
|
if (!app->isConnected() || liveState.sync.syncing || s_amount <= 0 ||
|
||||||
|
(s_amount + s_fee) > liveAvail) {
|
||||||
|
s_tx_status = "Cannot send now — check connection, sync, and available balance.";
|
||||||
|
s_status_success = false;
|
||||||
|
s_status_timestamp = ImGui::GetTime();
|
||||||
|
}
|
||||||
// A locked encrypted lite wallet can't spend; prompt to unlock instead of sending
|
// A locked encrypted lite wallet can't spend; prompt to unlock instead of sending
|
||||||
// (the backend would otherwise reject with "Wallet is locked").
|
// (the backend would otherwise reject with "Wallet is locked").
|
||||||
if (app->liteWallet() && app->getWalletState().isLocked()) {
|
else if (app->liteWallet() && app->getWalletState().isLocked()) {
|
||||||
app->requestLiteUnlock();
|
app->requestLiteUnlock();
|
||||||
} else {
|
} else {
|
||||||
s_sending = true;
|
s_sending = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user