diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index cedb13e5ab..6df3e67f1f 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { "branches": 93.34, - "functions": 97.36, + "functions": 97.37, "lines": 98.33, "statements": 98.07 } diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 5c8b635e87..90cb50cc76 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -286,6 +286,11 @@ export type SnapRuntimeData = { * A mutex to prevent concurrent state updates. */ stateMutex: Mutex; + + /** + * A mutex to prevent concurrent state decryption. + */ + getStateMutex: Mutex; }; export type SnapError = { @@ -2046,33 +2051,36 @@ export class SnapController extends BaseController< */ async getSnapState(snapId: SnapId, encrypted: boolean): Promise { const runtime = this.#getRuntimeExpect(snapId); - const cachedState = encrypted ? runtime.state : runtime.unencryptedState; + return await runtime.getStateMutex.runExclusive(async () => { + const cachedState = encrypted ? runtime.state : runtime.unencryptedState; - if (cachedState !== undefined) { - return cachedState; - } + if (cachedState !== undefined) { + return cachedState; + } - const state = encrypted - ? this.state.snapStates[snapId] - : this.state.unencryptedSnapStates[snapId]; + const state = encrypted + ? this.state.snapStates[snapId] + : this.state.unencryptedSnapStates[snapId]; - if (state === null || state === undefined) { - return null; - } + if (state === null || state === undefined) { + return null; + } - if (!encrypted) { - // For performance reasons, we do not validate that the state is JSON, - // since we control serialization. - const json = JSON.parse(state); - runtime.unencryptedState = json; + if (!encrypted) { + // For performance reasons, we do not validate that the state is JSON, + // since we control serialization. + const json = JSON.parse(state); + runtime.unencryptedState = json; - return json; - } + return json; + } - const decrypted = await this.#decryptSnapState(snapId, state); - runtime.state = decrypted; + const decrypted = await this.#decryptSnapState(snapId, state); + // eslint-disable-next-line require-atomic-updates + runtime.state = decrypted; - return decrypted; + return decrypted; + }); } /** @@ -3968,6 +3976,7 @@ export class SnapController extends BaseController< interpreter, stopping: false, stateMutex: new Mutex(), + getStateMutex: new Mutex(), }); }