aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/manager.js35
-rw-r--r--src/features/ark.js4
-rw-r--r--src/i18n.js1
3 files changed, 38 insertions, 2 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 7f24b7d..4a0a9c6 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -1920,6 +1920,33 @@ export class ArkManager {
this._save();
}
+ // The server no longer knows the participation we submitted. Either the
+ // round ran under a DIFFERENT submission — a retry from this or another
+ // device minted a new unlock hash, the inputs are spent, and the output
+ // reached the wallet through the mailbox — or it was dropped before any
+ // round ran, and the inputs are still ours to submit again. Left alone,
+ // the action sat "in progress" for weeks, asking on every sync.
+ async _repairSubmitted(action, inputRecs) {
+ const states = await Promise.all(inputRecs.map((v) =>
+ getVtxoStatus(this.arkUrl, this._decoded(v).point.raw, this._keyForVtxo(v).privkey).catch(() => null)));
+ if (states.some((st) => st == null)) return; // couldn't tell today; ask again next sync
+ const spent = inputRecs.filter((v, i) => states[i] === VTXO_STATE_SPENT);
+ if (spent.length === inputRecs.length) {
+ for (const v of inputRecs) v.state = 'spent';
+ action.step = 'failed';
+ action.superseded = true; // not a failure of the renewal, only of this record of it
+ action.lastError = 'superseded: the round ran under another submission';
+ } else if (!spent.length) {
+ action.step = 'created';
+ delete action.unlockHash;
+ delete action.lastError;
+ } else {
+ await this._repairRefresh(action, inputRecs);
+ return;
+ }
+ this._save();
+ }
+
// A refresh claim can be reached from two directions at once — the sync
// loop resuming the action, and a mailbox breadcrumb claiming the moment
// the round is announced. Both are safe server-side (cosign and forfeit are
@@ -1962,7 +1989,13 @@ export class ArkManager {
this._save();
}
if (action.step === 'submitted') {
- const status = await roundParticipationStatus(this.arkUrl, hex.decode(action.unlockHash));
+ let status;
+ try { status = await roundParticipationStatus(this.arkUrl, hex.decode(action.unlockHash)); }
+ catch (e) {
+ if (!/not found/i.test(e.message || '')) throw e; // transient: retry on next sync
+ await this._repairSubmitted(action, inputRecs);
+ return;
+ }
if (status.status === 0 || !status.fundingTx) return; // round pending; retry on next sync
const fundingTx = parseTx(status.fundingTx);
const confirmed = await this.chain.getTxStatus(fundingTx.txid);
diff --git a/src/features/ark.js b/src/features/ark.js
index fa2f23d..2a47ef5 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -2259,7 +2259,9 @@ export function arkFeature(ctx) {
...renewing.map((a) => h('div', { class: 'col', style: 'gap:2px' },
h('div', { class: 'small' }, t('arkRenewingLine', { amount: fmtSats(a.inAmountSat || (a.outAmountSat + (a.feeSat || 0))), fee: a.feeSat ? t('arkRenewingFee', { fee: fmtSats(a.feeSat) }) : '' })),
h('div', { class: 'small muted' }, stageOf(a)),
- a.lastError ? h('div', { class: 'small faint' }, a.lastError) : null))) : null;
+ // a check that failed is retried on the next sync; the reason is a
+ // server string nobody should have to read
+ a.lastError ? h('div', { class: 'small faint' }, t('arkRenewStageRetry')) : null))) : null;
const tick = (checked, onChange, title) => h('input', {
type: 'checkbox', checked, title, style: 'flex:0 0 auto;margin:0', onChange,
});
diff --git a/src/i18n.js b/src/i18n.js
index 4bba36f..3e20a98 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -438,6 +438,7 @@ const STR = {
arkRenewStageQueued: 'Joining the next round — the wallet submits it on its next sync.',
arkRenewStageRound: 'In a round. The fresh coin arrives once the round’s transaction has a confirmation, about a block from now, the next time this wallet syncs.',
arkRenewStageFinishing: 'Finishing: signing the fresh coin.',
+ arkRenewStageRetry: 'The last check with the server didn’t go through; the wallet keeps trying.',
arkRenewingHistory: 'Renewing',
arkRenewingOpen: 'See the coins',
arkCoinsExitTitle: 'Unilateral exit',