Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Reduce unnecessary JSON validation #2844

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,33 +106,6 @@ describe('AbstractExecutionService', () => {
);
});

it('throws an error if RPC request is non JSON serializable', async () => {
const { service } = createService(MockExecutionService);
await service.executeSnap({
snapId: MOCK_SNAP_ID,
sourceCode: `
module.exports.onRpcRequest = () => null;
`,
endowments: [],
});

await expect(
service.handleRpcRequest(MOCK_SNAP_ID, {
origin: 'foo.com',
handler: HandlerType.OnRpcRequest,
request: {
id: 6,
method: 'bar',
params: undefined,
},
}),
).rejects.toThrow(
'Invalid JSON-RPC request: At path: params -- Expected the value to satisfy a union of `record | array`, but received: [object Object].',
);

await service.terminateAllSnaps();
});

it('throws an error if execution environment fails to respond to ping', async () => {
const { service } = createService(MockExecutionService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,17 @@ export abstract class AbstractExecutionService<WorkerType>

const remainingTime = timer.remaining;

const request = {
jsonrpc: '2.0',
method: 'executeSnap',
params: { snapId, sourceCode, endowments },
id: nanoid(),
};

assertIsJsonRpcRequest(request);

const result = await withTimeout(
this.command(job.id, {
jsonrpc: '2.0',
method: 'executeSnap',
params: { snapId, sourceCode, endowments },
id: nanoid(),
}),
this.command(job.id, request),
remainingTime,
);

Expand All @@ -433,8 +437,6 @@ export abstract class AbstractExecutionService<WorkerType>
jobId: string,
message: JsonRpcRequest,
): Promise<Json | undefined> {
assertIsJsonRpcRequest(message);

const job = this.jobs.get(jobId);
if (!job) {
throw new Error(`Job with id "${jobId}" not found.`);
Expand Down
32 changes: 32 additions & 0 deletions packages/snaps-controllers/src/snaps/SnapController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,7 @@
});

// This isn't stable in CI unfortunately
it.skip('throws if the Snap is terminated while executing', async () => {

Check warning on line 1851 in packages/snaps-controllers/src/snaps/SnapController.test.tsx

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (@metamask/snaps-controllers)

Disabled test
const { manifest, sourceCode, svgIcon } =
await getMockSnapFilesWithUpdatedChecksum({
sourceCode: `
Expand Down Expand Up @@ -3764,6 +3764,38 @@
snapController.destroy();
});

it('handlers throw if the request is not valid JSON', async () => {
const fakeSnap = getPersistedSnapObject({ status: SnapStatus.Running });
const snapId = fakeSnap.id;
const snapController = getSnapController(
getSnapControllerOptions({
state: {
snaps: {
[snapId]: fakeSnap,
},
},
}),
);
await expect(
snapController.handleRequest({
snapId,
origin: 'foo.com',
handler: HandlerType.OnRpcRequest,
request: {
method: 'bar',
params: BigInt(0),
},
}),
).rejects.toThrow(
rpcErrors.invalidRequest({
message:
'Invalid JSON-RPC request: At path: params -- Expected the value to satisfy a union of `record | array`, but received: 0.',
}),
);

snapController.destroy();
});

it('handlers will throw if there are too many pending requests before a snap has started', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ export class BaseSnapExecutor {
const originalRequest = provider.request.bind(provider);

const request = async (args: RequestArguments) => {
// As part of the sanitization, we validate that the args are valid JSON.
const sanitizedArgs = sanitizeRequestArguments(args);
assertSnapOutboundRequest(sanitizedArgs);
return await withTeardown(
Expand Down Expand Up @@ -512,6 +513,7 @@ export class BaseSnapExecutor {
const originalRequest = provider.request.bind(provider);

const request = async (args: RequestArguments) => {
// As part of the sanitization, we validate that the args are valid JSON.
const sanitizedArgs = sanitizeRequestArguments(args);
assertEthereumOutboundRequest(sanitizedArgs);
return await withTeardown(
Expand Down
22 changes: 0 additions & 22 deletions packages/snaps-execution-environments/src/common/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ describe('assertSnapOutboundRequest', () => {
'The method does not exist / is not available.',
);
});

it('throws for invalid JSON values', () => {
expect(() =>
assertSnapOutboundRequest({
method: 'snap_notify',
params: [undefined],
}),
).toThrow(
'Provided value is not JSON-RPC compatible: Expected the value to satisfy a union of `literal | boolean | finite number | string | array | record`, but received: [object Object].',
);
});
});

describe('assertEthereumOutboundRequest', () => {
Expand All @@ -66,17 +55,6 @@ describe('assertEthereumOutboundRequest', () => {
'The method does not exist / is not available.',
);
});

it('throws for invalid JSON values', () => {
expect(() =>
assertEthereumOutboundRequest({
method: 'eth_blockNumber',
params: [undefined],
}),
).toThrow(
'Provided value is not JSON-RPC compatible: Expected the value to satisfy a union of `literal | boolean | finite number | string | array | record`, but received: [object Object].',
);
});
});

describe('isValidResponse', () => {
Expand Down
21 changes: 1 addition & 20 deletions packages/snaps-execution-environments/src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { StreamProvider, RequestArguments } from '@metamask/providers';
import { rpcErrors } from '@metamask/rpc-errors';
import {
assert,
assertStruct,
getJsonSize,
getSafeJson,
isObject,
JsonStruct,
} from '@metamask/utils';
import { assert, getJsonSize, getSafeJson, isObject } from '@metamask/utils';

import { log } from '../logging';

Expand Down Expand Up @@ -122,12 +115,6 @@ export function assertSnapOutboundRequest(args: RequestArguments) {
},
}),
);
assertStruct(
args,
JsonStruct,
'Provided value is not JSON-RPC compatible',
rpcErrors.invalidParams,
);
}

/**
Expand All @@ -153,12 +140,6 @@ export function assertEthereumOutboundRequest(args: RequestArguments) {
},
}),
);
assertStruct(
args,
JsonStruct,
'Provided value is not JSON-RPC compatible',
rpcErrors.invalidParams,
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import { ChainIdStruct, HandlerType } from '@metamask/snaps-utils';
import type { Infer } from '@metamask/superstruct';
import {
any,
array,
assign,
enums,
Expand Down Expand Up @@ -87,7 +88,10 @@ export const SnapRpcRequestArgumentsStruct = tuple([
assign(
JsonRpcRequestWithoutIdStruct,
object({
params: optional(record(string(), JsonStruct)),
// Previously this would validate that the parameters were valid JSON.
// This is already validated for all messages received by the executor.
// If that assumption changes, this should once again validate JSON.
params: optional(record(string(), any())),
}),
),
]);
Expand Down
Loading