diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ark/manager.js | 35 | ||||
| -rw-r--r-- | src/features/ark.js | 4 | ||||
| -rw-r--r-- | src/i18n.js | 1 |
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', |
