aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark/manager.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-31 08:54:35 -0700
committerAdam Soltys <[email protected]>2026-08-31 08:54:35 -0700
commit1e6fb6005948231cb56ecc071576df23b5d7b0be (patch)
tree3d41aec0a57d53d11f8c48b72c9f151e032af164 /src/ark/manager.js
parent03d471d5abd4447d96daca869de9d8d4fcf3283f (diff)
downloadcoinosv3-1e6fb6005948231cb56ecc071576df23b5d7b0be.tar.gz
coinosv3-1e6fb6005948231cb56ecc071576df23b5d7b0be.zip
The mailbox cursor stops leaping wounded messages: it advances only after a message's work is DONE (a final rejection counts, a transient failure does not), holds at the lowest failed checkpoint so a healthy message behind a sick one can't skip it, and the poll defers the rest of a batch on failure — one esplora hiccup mid-validateVtxo used to skip an arkoor receive forever (monogram's 204 sat, twice)
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
Diffstat (limited to 'src/ark/manager.js')
-rw-r--r--src/ark/manager.js33
1 files changed, 31 insertions, 2 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 492906d..b5c2aac 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -276,7 +276,14 @@ export class ArkManager {
const mailbox = this._mailboxKey();
const { messages } = await readMailbox(this.arkUrl, mailbox, this.state.mailboxCheckpoint);
let changed = false;
- for (const m of messages) changed = (await this._processMailboxMessage(m)) || changed;
+ for (const m of messages) {
+ // A transiently-failing message stops the batch: processing the ones
+ // behind it would advance the cursor past it (see the hold logic in
+ // _processMailboxMessage) — the next poll re-reads from where it
+ // stands, so nothing behind the failure is lost either.
+ try { changed = (await this._processMailboxMessage(m)) || changed; }
+ catch (e) { console.warn('ark: mailbox message deferred —', e.message); break; }
+ }
// Everything the first catch-up found is history, not news.
if (baselining) {
this.state.receiveAckTs = Date.now();
@@ -298,9 +305,31 @@ export class ArkManager {
// Handle one mailbox message (from a poll or the live stream). Returns
// whether state changed; the caller saves. Duplicate-safe: vtxos dedupe by
// id and the checkpoint only moves forward.
+ //
+ // The cursor moves only AFTER the message's work is done — a final
+ // rejection counts as done, a transient failure does not. Advancing first
+ // meant one esplora hiccup mid-validation skipped a coin forever: the
+ // throw aborted adoption, the cursor had already moved in memory, any
+ // later save persisted it, and every future read started past the
+ // message (monogram's 204-sat receive sat invisible for two days). And a
+ // stuck message is never leapt: later messages may process fine, but the
+ // cursor holds at the lowest failed checkpoint until it clears — else a
+ // healthy message right behind the sick one would skip it just the same.
async _processMailboxMessage(m) {
+ try {
+ const changed = await this._handleMailboxMessage(m);
+ if (this._mailboxStuckAt === m.checkpoint) this._mailboxStuckAt = null;
+ if (this._mailboxStuckAt != null && m.checkpoint > this._mailboxStuckAt) return changed;
+ if (m.checkpoint > this.state.mailboxCheckpoint) { this.state.mailboxCheckpoint = m.checkpoint; return true; }
+ return changed;
+ } catch (e) {
+ if (this._mailboxStuckAt == null || m.checkpoint < this._mailboxStuckAt) this._mailboxStuckAt = m.checkpoint;
+ throw e;
+ }
+ }
+
+ async _handleMailboxMessage(m) {
let changed = false;
- if (m.checkpoint > this.state.mailboxCheckpoint) { this.state.mailboxCheckpoint = m.checkpoint; changed = true; }
if (m.kind === 'lnSendFinished') {
const a = this.state.actions.find((x) =>
x.type === 'ln-pay' && x.paymentHash === m.paymentHash && !['done', 'failed'].includes(x.step));