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:
@@ -181,7 +181,7 @@ void ShieldDialog::render(App* app)
|
||||
double fee = s_fee;
|
||||
int limit = s_utxo_limit;
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), from, to, fee, limit]() -> rpc::RPCWorker::MainCb {
|
||||
app->worker()->post([app, rpc = app->rpc(), from, to, fee, limit]() -> rpc::RPCWorker::MainCb {
|
||||
nlohmann::json result;
|
||||
std::string error;
|
||||
try {
|
||||
@@ -190,12 +190,15 @@ void ShieldDialog::render(App* app)
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
}
|
||||
return [result, error]() {
|
||||
return [app, result, error]() {
|
||||
s_operation_pending = false;
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success(TR("shield_started"));
|
||||
// Register with the shared poller so an async failure is
|
||||
// surfaced (and balances refresh) even after this dialog closes.
|
||||
app->trackOperation(s_operation_id);
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Shield failed: " + error);
|
||||
@@ -210,7 +213,7 @@ void ShieldDialog::render(App* app)
|
||||
double fee = s_fee;
|
||||
int limit = s_utxo_limit;
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), fromAddrs, to, fee, limit]() -> rpc::RPCWorker::MainCb {
|
||||
app->worker()->post([app, rpc = app->rpc(), fromAddrs, to, fee, limit]() -> rpc::RPCWorker::MainCb {
|
||||
nlohmann::json addrs = nlohmann::json::array();
|
||||
for (const auto& addr : fromAddrs) addrs.push_back(addr);
|
||||
nlohmann::json result;
|
||||
@@ -221,12 +224,15 @@ void ShieldDialog::render(App* app)
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
}
|
||||
return [result, error]() {
|
||||
return [app, result, error]() {
|
||||
s_operation_pending = false;
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success(TR("merge_started"));
|
||||
// Register with the shared poller so an async failure is
|
||||
// surfaced (and balances refresh) even after this dialog closes.
|
||||
app->trackOperation(s_operation_id);
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Merge failed: " + error);
|
||||
|
||||
Reference in New Issue
Block a user