Skip to content

Commit c3afc8a

Browse files
committed
fix: prevent crashes from missing keys and history records
Fix unhandled promise rejections and crashes caused by race conditions in WalletConnect message processing. These changes prevent Lambda timeouts and 502 errors by gracefully handling expected edge cases. Changes: 1. crypto.decode() - Handle missing symmetric keys gracefully - Wrap generateSharedKey() in try-catch (was outside, causing crashes) - Return undefined on decode failure instead of throwing - Change error logging to warn (expected race condition) 2. history.resolve() - Handle missing history records gracefully - Wrap getRecord() in try-catch to prevent crashes - Log warnings instead of errors for missing records - Continue processing other messages when record not found 3. engine.onRelayMessage() - Handle undefined payload from decode - Check for undefined payload after decode - Early return with warning if decode fails - Prevents downstream crashes from undefined values 4. engine.onRelayEventResponse() - Handle missing history records - Wrap history.get() in try-catch - Early return with warning if record not found - Prevents crashes when response arrives before request recorded These fixes address stack traces showing: - "No matching key. history: <id>" errors - "onRelayMessage() -> failed to process inbound message" errors - Lambda 502 "Internal Server Error" responses All changes maintain backward compatibility and use appropriate log levels (warn for expected race conditions, error for unexpected).
1 parent d203cf1 commit c3afc8a

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

packages/core/src/controllers/crypto.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,22 @@ export class Crypto implements ICrypto {
143143
const message = decodeTypeTwoEnvelope(encoded, opts?.encoding);
144144
return safeJsonParse(message);
145145
}
146-
if (isTypeOneEnvelope(params)) {
147-
const selfPublicKey = params.receiverPublicKey;
148-
const peerPublicKey = params.senderPublicKey;
149-
topic = await this.generateSharedKey(selfPublicKey, peerPublicKey);
150-
}
151146
try {
147+
if (isTypeOneEnvelope(params)) {
148+
const selfPublicKey = params.receiverPublicKey;
149+
const peerPublicKey = params.senderPublicKey;
150+
topic = await this.generateSharedKey(selfPublicKey, peerPublicKey);
151+
}
152152
const symKey = this.getSymKey(topic);
153153
const message = decrypt({ symKey, encoded, encoding: opts?.encoding });
154154
const payload = safeJsonParse(message);
155155
return payload;
156156
} catch (error) {
157-
this.logger.error(
157+
this.logger.warn(
158158
`Failed to decode message from topic: '${topic}', clientId: '${await this.getClientId()}'`,
159159
);
160-
this.logger.error(error);
160+
this.logger.warn(error instanceof Error ? error.message : String(error));
161+
return undefined;
161162
}
162163
};
163164

packages/core/src/controllers/history.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,18 @@ export class JsonRpcHistory extends IJsonRpcHistory {
9797
this.logger.debug(`Updating JSON-RPC response history record`);
9898
this.logger.trace({ type: "method", method: "update", response });
9999
if (!this.records.has(response.id)) return;
100-
const record = await this.getRecord(response.id);
101-
if (typeof record.response !== "undefined") return;
102-
record.response = isJsonRpcError(response)
103-
? { error: response.error }
104-
: { result: response.result };
105-
this.records.set(record.id, record);
106-
this.persist();
107-
this.events.emit(HISTORY_EVENTS.updated, record);
100+
try {
101+
const record = await this.getRecord(response.id);
102+
if (typeof record.response !== "undefined") return;
103+
record.response = isJsonRpcError(response)
104+
? { error: response.error }
105+
: { result: response.result };
106+
this.records.set(record.id, record);
107+
this.persist();
108+
this.events.emit(HISTORY_EVENTS.updated, record);
109+
} catch (error) {
110+
this.logger.warn(`Failed to resolve history record ${response.id}: ${error instanceof Error ? error.message : String(error)}`);
111+
}
108112
};
109113

110114
public get: IJsonRpcHistory["get"] = async (topic, id) => {

packages/sign-client/src/controllers/engine.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,11 @@ export class Engine extends IEngine {
19871987
encoding: transportType === TRANSPORT_TYPES.link_mode ? BASE64URL : BASE64,
19881988
});
19891989

1990+
if (!payload) {
1991+
this.client.logger.warn(`onRelayMessage() -> decode returned undefined for topic: ${topic}`);
1992+
return;
1993+
}
1994+
19901995
if (isJsonRpcRequest(payload)) {
19911996
this.client.core.history.set(topic, payload);
19921997
await this.onRelayEventRequest({
@@ -2089,7 +2094,13 @@ export class Engine extends IEngine {
20892094

20902095
private onRelayEventResponse: EnginePrivate["onRelayEventResponse"] = async (event) => {
20912096
const { topic, payload, transportType } = event;
2092-
const record = await this.client.core.history.get(topic, payload.id);
2097+
let record;
2098+
try {
2099+
record = await this.client.core.history.get(topic, payload.id);
2100+
} catch (error) {
2101+
this.client.logger.warn(`onRelayEventResponse() -> history record not found for id ${payload.id}: ${error instanceof Error ? error.message : String(error)}`);
2102+
return;
2103+
}
20932104
const resMethod = record.request.method as JsonRpcTypes.WcMethod;
20942105

20952106
switch (resMethod) {

0 commit comments

Comments
 (0)