fix(lws): treat an existing account as registered, not as a node outage

register_account called add_account unconditionally when provisioning was off,
which is production's default. monero-lws answers a duplicate with
account_exists as an HTTP 500, the backend maps that to a node error, and the
user is told NODE_UNAVAILABLE. A routine re-registration was therefore
indistinguishable from the node being down.

Reported 2026-09-08: a tip failed with an upstream error while both LWS
instances were active and had logged no warnings in 24 hours. Nothing was wrong
with the node or the account.

The provisioning path already checked existence first and documented why. That
check now guards both paths, so registration is idempotent and the two branches
cannot drift apart again.
This commit is contained in:
2026-09-08 09:44:23 -04:00
parent bd5c861436
commit afa3ec7679
+18 -13
View File
@@ -226,22 +226,27 @@ impl LwsClient {
view_key: &str,
provision_minors: u32,
) -> Result<(), AppError> {
if provision_minors == 0 {
// Provisioning off (the dark default): exactly the call this made
// before, including monero-lws answering a duplicate `add_account`
// with `account_exists` (an HTTP 500 it maps to a node error).
return self.admin_add_account(address, view_key).await;
}
// Provisioning on: an account that ALREADY exists must still be brought
// up to the required ceiling. monero-lws fails a duplicate `add_account`
// (`db::storage::do_add_account` returns `lws::error::account_exists`,
// served as a 500), so calling it unconditionally would abort before the
// provisioning below and make the account permanently unprovisionable
// through this path. Existence is checked first instead; nothing about
// the account's scan state is touched.
// Registration is idempotent. An account that is already scanning is
// success, not a failure.
//
// This used to call `add_account` unconditionally when provisioning was
// off, so re-registering an existing account got `account_exists` back as
// an HTTP 500, which maps to a node error and reaches the user as
// NODE_UNAVAILABLE. A routine re-registration was indistinguishable from
// the node being down: reported 2026-09-08 by a user whose tip failed
// with an upstream error while both LWS instances were healthy and had
// logged nothing at all.
//
// The existence check now guards both paths rather than only the
// provisioning one, so the two cannot drift apart again.
if self.account_scan_height(address).await?.is_none() {
self.admin_add_account(address, view_key).await?;
}
if provision_minors == 0 {
return Ok(());
}
// Provisioning on: an account that already exists must still be brought up
// to the required ceiling, so this runs whether or not the add just ran.
self.provision_account0_covering(address, view_key, provision_minors)
.await?;
Ok(())