aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-05-17 23:23:26 -0700
committerAdam Soltys <[email protected]>2026-05-17 23:23:26 -0700
commitb52eee57bd9055d0f2b9044d6b87ce90ca5e48cc (patch)
tree902c9845bca70e47d4ef6c521a0b95523f41b635 /lib
parent82e43982ca49cdec054bbd31c0e739730fc213f2 (diff)
downloadcoinos-server-b52eee57bd9055d0f2b9044d6b87ce90ca5e48cc.tar.gz
coinos-server-b52eee57bd9055d0f2b9044d6b87ce90ca5e48cc.zip
isolate per-chain failures in getAddressType and catchUp
Two related resilience fixes for when one chain backend (typically lq via the cs tunnel) is down or unreachable: - getAddressType: when lq.getAddressInfo throws on a connection error (ECONNREFUSED / ETIMEDOUT / "Unable to connect"), fail with "liquid temporarily unavailable" instead of the misleading "unrecognized address". Users get an actionable message and we can tell user error from infrastructure problems in the logs. - catchUp: wrap the per-chain listTransactions call in its own try/catch so a downed lq doesn't stop the bitcoin deposit-recovery loop (and vice versa). Logged via warnThrottled so a multi-hour outage doesn't spam the same error every tick. Also drops a redundant /confirm double-call comment from catchUp — with walletnotify reliable again, a single call is sufficient and avoids piling on the /confirm race the new atomic lock is preventing. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Diffstat (limited to 'lib')
-rw-r--r--lib/payments.ts33
1 files changed, 26 insertions, 7 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index e0040217..3005e71b 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -705,7 +705,16 @@ const getAddressType = async (a) => {
try {
await lq.getAddressInfo(a);
return PaymentType.liquid;
- } catch (e) {
+ } catch (e: any) {
+ // Distinguish "lq is down" from "address isn't liquid" so users get a useful
+ // message instead of "unrecognized address" when cs/lq is the actual problem.
+ if (
+ e?.message?.includes("Unable to connect") ||
+ e?.code === "ECONNREFUSED" ||
+ e?.code === "ETIMEDOUT"
+ ) {
+ fail("liquid temporarily unavailable");
+ }
fail("unrecognized address");
}
}
@@ -819,18 +828,28 @@ export const build = async ({
export const catchUp = async () => {
try {
const txns = [];
+ // Each chain is isolated: one being unreachable (e.g. lq when cs is down)
+ // must not stop the other chain's deposit-recovery loop.
for (const [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) {
- txns.push(
- ...(await n.listTransactions("*", 100)).filter((tx) => {
- tx.type = type;
- return tx.category === "receive" && tx.confirmations > 0;
- }),
- );
+ try {
+ txns.push(
+ ...(await n.listTransactions("*", 100)).filter((tx) => {
+ tx.type = type;
+ return tx.category === "receive" && tx.confirmations > 0;
+ }),
+ );
+ } catch (e: any) {
+ warnThrottled(`catchUp ${type}`, e.message);
+ }
}
for (const { txid, type } of txns) {
try {
if (await db.zScore("seen", txid)) continue;
+ // walletnotify is the primary path; catchUp is a safety net. The
+ // double-call I added 2026-05-05 was for when walletnotify was broken
+ // on cs's lq — now that's fixed, a single call suffices and avoids
+ // contributing to /confirm race conditions.
await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, {
json: { txid, wallet: config[type].wallet, type },
});