Skip to content

Commit b421ca1

Browse files
authored
Add mutex to getSnapState to prevent concurrent decryption (#3234)
This wraps `getSnapState` in a mutex, to ensure we only decrypt once when calling it at the same time. The first call will decrypt the state and cache it in the Snaps runtime, and the next call will just use the cached result.
1 parent 92f8922 commit b421ca1

File tree

2 files changed

+30
-21
lines changed

2 files changed

+30
-21
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"branches": 93.34,
3-
"functions": 97.36,
3+
"functions": 97.37,
44
"lines": 98.33,
55
"statements": 98.07
66
}

packages/snaps-controllers/src/snaps/SnapController.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,11 @@ export type SnapRuntimeData = {
286286
* A mutex to prevent concurrent state updates.
287287
*/
288288
stateMutex: Mutex;
289+
290+
/**
291+
* A mutex to prevent concurrent state decryption.
292+
*/
293+
getStateMutex: Mutex;
289294
};
290295

291296
export type SnapError = {
@@ -2046,33 +2051,36 @@ export class SnapController extends BaseController<
20462051
*/
20472052
async getSnapState(snapId: SnapId, encrypted: boolean): Promise<Json> {
20482053
const runtime = this.#getRuntimeExpect(snapId);
2049-
const cachedState = encrypted ? runtime.state : runtime.unencryptedState;
2054+
return await runtime.getStateMutex.runExclusive(async () => {
2055+
const cachedState = encrypted ? runtime.state : runtime.unencryptedState;
20502056

2051-
if (cachedState !== undefined) {
2052-
return cachedState;
2053-
}
2057+
if (cachedState !== undefined) {
2058+
return cachedState;
2059+
}
20542060

2055-
const state = encrypted
2056-
? this.state.snapStates[snapId]
2057-
: this.state.unencryptedSnapStates[snapId];
2061+
const state = encrypted
2062+
? this.state.snapStates[snapId]
2063+
: this.state.unencryptedSnapStates[snapId];
20582064

2059-
if (state === null || state === undefined) {
2060-
return null;
2061-
}
2065+
if (state === null || state === undefined) {
2066+
return null;
2067+
}
20622068

2063-
if (!encrypted) {
2064-
// For performance reasons, we do not validate that the state is JSON,
2065-
// since we control serialization.
2066-
const json = JSON.parse(state);
2067-
runtime.unencryptedState = json;
2069+
if (!encrypted) {
2070+
// For performance reasons, we do not validate that the state is JSON,
2071+
// since we control serialization.
2072+
const json = JSON.parse(state);
2073+
runtime.unencryptedState = json;
20682074

2069-
return json;
2070-
}
2075+
return json;
2076+
}
20712077

2072-
const decrypted = await this.#decryptSnapState(snapId, state);
2073-
runtime.state = decrypted;
2078+
const decrypted = await this.#decryptSnapState(snapId, state);
2079+
// eslint-disable-next-line require-atomic-updates
2080+
runtime.state = decrypted;
20742081

2075-
return decrypted;
2082+
return decrypted;
2083+
});
20762084
}
20772085

20782086
/**
@@ -3968,6 +3976,7 @@ export class SnapController extends BaseController<
39683976
interpreter,
39693977
stopping: false,
39703978
stateMutex: new Mutex(),
3979+
getStateMutex: new Mutex(),
39713980
});
39723981
}
39733982

0 commit comments

Comments
 (0)