fix(tx): track async operations to completion (send/shield/auto-shield)
z_sendmany returns an opid immediately; the tx is built/signed/broadcast asynchronously afterward. The send path showed "Transaction sent successfully!" and cleared the form on opid receipt, so a later async failure contradicted it. Shield/merge stored the opid only in a dialog-local static (never polled), and auto-shield ran a blocking z_shieldcoinbase on the UI thread and discarded its opid — async failures of all three were silently lost. - Add App::trackOperation(opid) so shield/merge/auto-shield register with the shared opid poller (failures surface, balances refresh on completion). - Defer the full-node send's success/failure to the poller via per-opid callbacks (parseOperationStatusPoll now exposes failureByOpid); the "Sending..." spinner covers the finalizing window, and the form is kept until terminal status. - Dispatch auto-shield through the worker thread and use the configured fee. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
33
src/app.cpp
33
src/app.cpp
@@ -739,9 +739,25 @@ void App::update()
|
||||
auto parsed = services::NetworkRefreshService::parseOperationStatusPoll(result, opids);
|
||||
return [this, parsed = std::move(parsed)]() mutable {
|
||||
opid_poll_in_progress_ = false;
|
||||
for (const auto& msg : parsed.failureMessages) {
|
||||
ui::Notifications::instance().error(msg);
|
||||
|
||||
// Successes: hand the real txid to any waiting send UI callback.
|
||||
std::unordered_set<std::string> successfulOpids;
|
||||
for (const auto& [opid, txid] : parsed.successTxidsByOpid) {
|
||||
successfulOpids.insert(opid);
|
||||
markPendingSendTransactionSucceeded(opid, txid);
|
||||
send_txids_.insert(txid);
|
||||
invokeSendResultCallback(opid, true, txid);
|
||||
}
|
||||
|
||||
// Failures: route to the originating send UI when there is one (it shows
|
||||
// its own error toast); otherwise surface a generic notification (this is
|
||||
// how shield/merge/auto-shield failures become visible).
|
||||
for (const auto& [opid, msg] : parsed.failureByOpid) {
|
||||
if (!invokeSendResultCallback(opid, false, msg)) {
|
||||
ui::Notifications::instance().error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> terminalOpids = std::move(parsed.doneOpids);
|
||||
terminalOpids.insert(terminalOpids.end(),
|
||||
parsed.staleOpids.begin(), parsed.staleOpids.end());
|
||||
@@ -750,13 +766,14 @@ void App::update()
|
||||
std::remove(pending_opids_.begin(), pending_opids_.end(), id),
|
||||
pending_opids_.end());
|
||||
}
|
||||
|
||||
// Stale opids (no longer reported by the daemon): let any waiting send UI
|
||||
// know the outcome couldn't be confirmed rather than spinning forever.
|
||||
for (const auto& opid : parsed.staleOpids) {
|
||||
invokeSendResultCallback(opid, false, TR("send_status_unconfirmed"));
|
||||
}
|
||||
|
||||
if (parsed.anySuccess) {
|
||||
std::unordered_set<std::string> successfulOpids;
|
||||
for (const auto& [opid, txid] : parsed.successTxidsByOpid) {
|
||||
successfulOpids.insert(opid);
|
||||
markPendingSendTransactionSucceeded(opid, txid);
|
||||
send_txids_.insert(txid);
|
||||
}
|
||||
std::vector<std::string> successOpids;
|
||||
std::vector<std::string> failedOrStaleOpids;
|
||||
for (const auto& opid : terminalOpids) {
|
||||
|
||||
Reference in New Issue
Block a user