Skip to content

Commit 23c22bd

Browse files
authored
fix(assets-controller): hardened error handling (#8389)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it changes how balance/detection/staked-balance update callbacks propagate failures, potentially masking bugs but preventing unhandled rejections from breaking polling ticks or subscriptions. > > **Overview** > Improves resilience of `assets-controller` update pipelines by **catching and logging errors inside subscription/polling callbacks** instead of letting them throw/reject. > > `AssetsController` now guards `onActiveChainsUpdated`, and `RpcDataSource`/`StakedBalanceDataSource` wrap balance, detection, and staked-balance update handlers so poll ticks can’t fail due to uncaught exceptions or unhandled promise rejections. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit db04572. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0e12807 commit 23c22bd

3 files changed

Lines changed: 34 additions & 13 deletions

File tree

packages/assets-controller/src/AssetsController.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,8 @@ export class AssetsController extends BaseController<
672672
#unsubscribeBasicFunctionality: (() => void) | null = null;
673673

674674
readonly #onActiveChainsUpdated: (
675-
dataSourceId: string,
676-
activeChains: ChainId[],
675+
dataSourceName: string,
676+
chains: ChainId[],
677677
previousChains: ChainId[],
678678
) => void;
679679

@@ -712,7 +712,14 @@ export class AssetsController extends BaseController<
712712
chains: ChainId[],
713713
previousChains: ChainId[],
714714
): void => {
715-
this.#handleActiveChainsUpdate(dataSourceName, chains, previousChains);
715+
try {
716+
this.#handleActiveChainsUpdate(dataSourceName, chains, previousChains);
717+
} catch (error) {
718+
log('Failed to handle active chains update', {
719+
dataSourceName,
720+
error,
721+
});
722+
}
716723
};
717724

718725
this.#backendWebsocketDataSource = new BackendWebsocketDataSource({

packages/assets-controller/src/data-sources/RpcDataSource.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,14 @@ export class RpcDataSource extends AbstractDataSource<
272272
balanceFetcherMessenger,
273273
{ pollingInterval: balanceInterval },
274274
);
275-
this.#balanceFetcher.setOnBalanceUpdate(
276-
this.#handleBalanceUpdate.bind(this),
277-
);
275+
// Polling controller awaits this callback; rejections must not become unhandled.
276+
this.#balanceFetcher.setOnBalanceUpdate(async (result) => {
277+
try {
278+
await this.#handleBalanceUpdate(result);
279+
} catch (error) {
280+
log('Balance update handler failed', { error });
281+
}
282+
});
278283

279284
// Initialize TokenDetector with polling interval
280285
const tokensApiClient = new TokensApiClient();
@@ -287,9 +292,14 @@ export class RpcDataSource extends AbstractDataSource<
287292
useExternalService: this.#useExternalService,
288293
},
289294
);
290-
this.#tokenDetector.setOnDetectionUpdate(
291-
this.#handleDetectionUpdate.bind(this),
292-
);
295+
// Sync throw in the detector would reject the poll tick if uncaught.
296+
this.#tokenDetector.setOnDetectionUpdate((result) => {
297+
try {
298+
this.#handleDetectionUpdate(result);
299+
} catch (error) {
300+
log('Detection update handler failed', { error });
301+
}
302+
});
293303

294304
this.#subscribeToNetworkController();
295305
this.#subscribeToTransactionEvents();

packages/assets-controller/src/data-sources/StakedBalanceDataSource.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,14 @@ export class StakedBalanceDataSource extends AbstractDataSource<
192192
this.#getProvider(hexChainId),
193193
});
194194

195-
// Wire the callback so polling results flow back to subscriptions
196-
this.#stakedBalanceFetcher.setOnStakedBalanceUpdate(
197-
this.#handleStakedBalanceUpdate.bind(this),
198-
);
195+
// Polling controller invokes this synchronously; keep failures inside the poll tick.
196+
this.#stakedBalanceFetcher.setOnStakedBalanceUpdate((result) => {
197+
try {
198+
this.#handleStakedBalanceUpdate(result);
199+
} catch (error) {
200+
log('Staked balance update handler failed', { error });
201+
}
202+
});
199203

200204
this.#messenger.subscribe(
201205
'TransactionController:transactionConfirmed',

0 commit comments

Comments
 (0)