Skip to content

Commit

Permalink
fix: publish 'error' message for failure after upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Feb 27, 2024
1 parent 8b0ec4c commit 5397e0c
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 11 deletions.
34 changes: 34 additions & 0 deletions packages/smart-wallet/src/offerWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const offerWatcherGuard = harden({
.optional(M.record())
.returns(),
publishResult: M.call(M.any()).returns(),
handleError: M.call(M.error()).returns(),
}),
paymentWatcher: M.interface('paymentWatcher', {
onFulfilled: M.call(PaymentPKeywordRecordShape, SeatShape).returns(
Expand Down Expand Up @@ -131,6 +132,9 @@ export const prepareOfferWatcher = baggage => {
}),
{
helper: {
/**
* @param {Record<string, unknown>} offerStatusUpdates
*/
updateStatus(offerStatusUpdates) {
const { state } = this;
state.status = harden({ ...state.status, ...offerStatusUpdates });
Expand Down Expand Up @@ -189,6 +193,22 @@ export const prepareOfferWatcher = baggage => {
facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
}
},
/**
* Called when the offer result promise rejects. The other two watchers
* are waiting for particular values out of Zoe but they settle at the same time
* and don't need their own error handling.
* @param {Error} err
*/
handleError(err) {
const { facets } = this;
facets.helper.updateStatus({ error: err.toString() });
const { seatRef } = this.state;
void E.when(E(seatRef).hasExited(), hasExited => {
if (!hasExited) {
void E(seatRef).tryExit();
}
});
},
},

/** @type {OutcomeWatchers['paymentWatcher']} */
Expand All @@ -205,13 +225,17 @@ export const prepareOfferWatcher = baggage => {
facets.helper.updateStatus({ payouts: amounts });
},
/**
* If promise disconnected, watch again. Or if there's an Error, handle it.
*
* @param {Error} err
* @param {UserSeat} seat
*/
onRejected(err, seat) {
const { facets } = this;
if (isUpgradeDisconnection(err)) {
void watchForPayout(facets, seat);
} else {
facets.helper.handleError(err);
}
},
},
Expand All @@ -223,13 +247,17 @@ export const prepareOfferWatcher = baggage => {
facets.helper.publishResult(result);
},
/**
* If promise disconnected, watch again. Or if there's an Error, handle it.
*
* @param {Error} err
* @param {UserSeat} seat
*/
onRejected(err, seat) {
const { facets } = this;
if (isUpgradeDisconnection(err)) {
void watchForOfferResult(facets, seat);
} else {
facets.helper.handleError(err);
}
},
},
Expand All @@ -242,6 +270,12 @@ export const prepareOfferWatcher = baggage => {
facets.helper.updateStatus({ numWantsSatisfied: numSatisfied });
},
/**
* If promise disconnected, watch again.
*
* Errors are handled by the paymentWatcher because numWantsSatisfied()
* and getPayouts() settle the same (they await the same promise and
* then synchronously return a local value).
*
* @param {Error} err
* @param {UserSeat} seat
*/
Expand Down
19 changes: 8 additions & 11 deletions packages/smart-wallet/src/smartWallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ export const prepareSmartWallet = (baggage, shared) => {
}),
};

// TODO move to top level so its type can be exported
/**
* Make the durable object to return, but taking some parameters that are awaited by a wrapping function.
* This is necessary because the class kit construction helpers, `initState` and `finish` run synchronously
Expand Down Expand Up @@ -990,21 +991,23 @@ export const prepareSmartWallet = (baggage, shared) => {
// await so that any errors are caught and handled below
await watchOfferOutcomes(watcher, seatRef);
} catch (err) {
facets.helper.logWalletError('OFFER ERROR:', err);
// This block only runs if the block above fails during one vat incarnation.
facets.helper.logWalletError('IMMEDIATE OFFER ERROR:', err);

// Notify the user
// Update status to observers
if (err.upgradeMessage === 'vat upgraded') {
// The offer watchers will reconnect. Don't reclaim or exit
return;
} else if (watcher) {
watcher.helper.updateStatus({ error: err.toString() });
// The watcher's onRejected will updateStatus()
} else {
facets.helper.updateStatus({
error: err.toString(),
...offerSpec,
});
}

// Backstop recovery, in case something very basic fails.
if (offerSpec?.proposal?.give) {
facets.payments
.tryReclaimingWithdrawnPayments(offerSpec.id)
Expand All @@ -1016,14 +1019,8 @@ export const prepareSmartWallet = (baggage, shared) => {
);
}

if (seatRef) {
void E.when(E(seatRef).hasExited(), hasExited => {
if (!hasExited) {
void E(seatRef).tryExit();
}
});
}

// XXX tests rely on throwing immediate errors, not covering the
// error handling in the event the failure is after an upgrade
throw err;
}
},
Expand Down

0 comments on commit 5397e0c

Please sign in to comment.