Skip to content

Commit

Permalink
fix: Skip unnecesary provider initialization (#2967)
Browse files Browse the repository at this point in the history
Since we only expose the `request` function from the provider and don't
allow listening to events or using `provider.chainId` etc we can
initialize the provider without making the `metamask_getProviderState`
request. This saves us a potential network request that may add overhead
to booting the Snap. Specifically the clients call `net_version` since
this property is required in the `MetaMaskInpageProvider`.

Closes #2968
  • Loading branch information
FrederikBolding authored Dec 19, 2024
1 parent 336b83f commit 0a4f8db
Show file tree
Hide file tree
Showing 11 changed files with 22 additions and 199 deletions.
18 changes: 2 additions & 16 deletions packages/snaps-controllers/src/snaps/SnapController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1652,14 +1652,7 @@ describe('SnapController', () => {
const stream = mux.createStream('metamask-provider');
const engine = new JsonRpcEngine();
const middleware = createAsyncMiddleware(async (req, res, _next) => {
if (req.method === 'metamask_getProviderState') {
res.result = {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
};
} else if (req.method === 'eth_blockNumber') {
if (req.method === 'eth_blockNumber') {
await sleep(100);
res.result = MOCK_BLOCK_NUMBER;
}
Expand Down Expand Up @@ -1737,14 +1730,7 @@ describe('SnapController', () => {
const stream = mux.createStream('metamask-provider');
const engine = new JsonRpcEngine();
const middleware = createAsyncMiddleware(async (req, res, _next) => {
if (req.method === 'metamask_getProviderState') {
res.result = {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
};
} else if (req.method === 'eth_blockNumber') {
if (req.method === 'eth_blockNumber') {
await sleep(100);
res.result = MOCK_BLOCK_NUMBER;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ export const getNodeEES = (messenger: ReturnType<typeof getNodeEESMessenger>) =>
const stream = mux.createStream('metamask-provider');
const engine = new JsonRpcEngine();
engine.push((req, res, next, end) => {
if (req.method === 'metamask_getProviderState') {
res.result = {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
};
return end();
} else if (req.method === 'eth_blockNumber') {
if (req.method === 'eth_blockNumber') {
res.result = MOCK_BLOCK_NUMBER;
return end();
}
Expand Down
10 changes: 0 additions & 10 deletions packages/snaps-controllers/src/test-utils/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,6 @@ export const createService = <
const stream = mux.createStream('metamask-provider');
const engine = new JsonRpcEngine();
engine.push((req, res, next, end) => {
if (req.method === 'metamask_getProviderState') {
res.result = {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
};
return end();
}

if (req.method === 'eth_blockNumber') {
res.result = MOCK_BLOCK_NUMBER;
return end();
Expand Down
6 changes: 3 additions & 3 deletions packages/snaps-execution-environments/coverage.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"branches": 80.68,
"functions": 89.26,
"lines": 90.67,
"statements": 90.06
"functions": 89.33,
"lines": 90.68,
"statements": 90.08
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment
/// <reference path="../../../../node_modules/ses/types.d.ts" />
import { createIdRemapMiddleware } from '@metamask/json-rpc-engine';
import type { RequestArguments } from '@metamask/providers';
import { StreamProvider } from '@metamask/providers/stream-provider';
import type { RequestArguments, StreamProvider } from '@metamask/providers';
import { errorCodes, rpcErrors, serializeError } from '@metamask/rpc-errors';
import type { SnapsEthereumProvider, SnapsProvider } from '@metamask/snaps-sdk';
import { getErrorData } from '@metamask/snaps-sdk';
Expand Down Expand Up @@ -40,6 +39,7 @@ import type { CommandMethodsMapping } from './commands';
import { getCommandMethodImplementations } from './commands';
import { createEndowments } from './endowments';
import { addEventListener, removeEventListener } from './globalEvents';
import { SnapProvider } from './SnapProvider';
import { sortParamKeys } from './sortParams';
import {
assertEthereumOutboundRequest,
Expand Down Expand Up @@ -369,12 +369,12 @@ export class BaseSnapExecutor {
});
};

const provider = new StreamProvider(this.rpcStream, {
const provider = new SnapProvider(this.rpcStream, {
jsonRpcStreamName: 'metamask-provider',
rpcMiddleware: [createIdRemapMiddleware()],
});

await provider.initialize();
provider.initializeSync();

const snap = this.createSnapGlobal(provider);
const ethereum = this.createEIP1193Provider(provider);
Expand Down
10 changes: 10 additions & 0 deletions packages/snaps-execution-environments/src/common/SnapProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StreamProvider } from '@metamask/providers/stream-provider';

export class SnapProvider extends StreamProvider {
// Since only the request function is exposed to the Snap, we can initialize the provider
// without making the metamask_getProviderState request, saving us a
// potential network request before boot.
initializeSync() {
this._initializeState();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export class TestSnapExecutor extends BaseSnapExecutor {
code: string,
endowments: string[],
) {
const providerRequestPromise = this.readRpc();
await this.writeCommand({
jsonrpc: '2.0',
id,
Expand All @@ -124,26 +123,6 @@ export class TestSnapExecutor extends BaseSnapExecutor {
if ('clock' in setTimeout) {
jest.advanceTimersByTime(1);
}

const providerRequest = await providerRequestPromise;
await this.writeRpc({
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: providerRequest.data.id!,
result: {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
},
},
});

if ('clock' in setTimeout) {
jest.advanceTimersByTime(1);
}
}

public async writeCommand(message: JsonRpcRequest): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
MOCK_SNAP_ID,
MockWindowPostMessageStream,
} from '@metamask/snaps-utils/test-utils';
import type { JsonRpcRequest } from '@metamask/utils';

import { IFrameSnapExecutor } from './IFrameSnapExecutor';

Expand Down Expand Up @@ -39,22 +38,6 @@ async function getResponse(
});
}

/**
* Wait for a outbound request from the stream.
*
* @param stream - The stream to wait for a response on.
* @returns The raw JSON-RPC response object.
*/
async function getOutboundRequest(
stream: MockWindowPostMessageStream,
): Promise<JsonRpcRequest> {
return new Promise((resolve) => {
stream.once('outbound', (data) => {
resolve(data.data);
});
});
}

describe('IFrameSnapExecutor', () => {
before(() => {
// @ts-expect-error - `globalThis.process` is not optional.
Expand Down Expand Up @@ -104,26 +87,6 @@ describe('IFrameSnapExecutor', () => {
},
});

const outboundRequest = await getOutboundRequest(mockStream);
expect(outboundRequest.method).toBe('metamask_getProviderState');

writeMessage(mockStream, {
name: 'jsonRpc',
data: {
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
id: outboundRequest.id,
result: {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
},
},
},
});

expect(await getResponse(mockStream)).toStrictEqual({
jsonrpc: '2.0',
id: 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import 'ses';
import { HandlerType, SNAP_STREAM_NAMES } from '@metamask/snaps-utils';
import { MOCK_ORIGIN, MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';
import type { Json, JsonRpcRequest, JsonRpcSuccess } from '@metamask/utils';
import type { Json, JsonRpcSuccess } from '@metamask/utils';
import { EventEmitter } from 'stream';

import { ChildProcessSnapExecutor } from './ChildProcessSnapExecutor';
Expand Down Expand Up @@ -34,18 +34,6 @@ describe('ChildProcessSnapExecutor', () => {
// Utility functions
const emit = (data: Json) => parentEmitter.emit('message', { data });
const emitChunk = (name: string, data: Json) => emit({ name, data });
const waitForOutbound = (request: Partial<JsonRpcRequest<any>>): any =>
new Promise((resolve) => {
childEmitter.on('message', ({ data: { name, data } }) => {
if (
name === SNAP_STREAM_NAMES.JSON_RPC &&
data.name === 'metamask-provider' &&
data.data.method === request.method
) {
resolve(data.data);
}
});
});

const waitForResponse = async (response: JsonRpcSuccess<string>) =>
new Promise((resolve) => {
Expand All @@ -72,24 +60,6 @@ describe('ChildProcessSnapExecutor', () => {
params: [MOCK_SNAP_ID, CODE, []],
});

const providerRequest = await waitForOutbound({
method: 'metamask_getProviderState',
});

emitChunk(SNAP_STREAM_NAMES.JSON_RPC, {
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
id: providerRequest.id,
result: {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
},
},
});

expect(
await waitForResponse({ result: 'OK', id: 1, jsonrpc: '2.0' }),
).not.toBeNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-unassigned-import
import 'ses';
import { SNAP_STREAM_NAMES, HandlerType } from '@metamask/snaps-utils';
import type { Json, JsonRpcRequest, JsonRpcSuccess } from '@metamask/utils';
import type { Json, JsonRpcSuccess } from '@metamask/utils';
import { EventEmitter } from 'stream';
import { parentPort } from 'worker_threads';

Expand Down Expand Up @@ -48,18 +48,6 @@ describe('ThreadSnapExecutor', () => {
// Utility functions
const emit = (data: Json) => parentEmitter.emit('message', { data });
const emitChunk = (name: string, data: Json) => emit({ name, data });
const waitForOutbound = (request: Partial<JsonRpcRequest<any>>): any =>
new Promise((resolve) => {
childEmitter.on('message', ({ data: { name, data } }) => {
if (
name === SNAP_STREAM_NAMES.JSON_RPC &&
data.name === 'metamask-provider' &&
data.data.method === request.method
) {
resolve(data.data);
}
});
});

const waitForResponse = async (response: JsonRpcSuccess<string>) =>
new Promise((resolve) => {
Expand All @@ -86,24 +74,6 @@ describe('ThreadSnapExecutor', () => {
params: [FAKE_SNAP_NAME, CODE, []],
});

const providerRequest = await waitForOutbound({
method: 'metamask_getProviderState',
});

emitChunk(SNAP_STREAM_NAMES.JSON_RPC, {
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
id: providerRequest.id,
result: {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
},
},
});

expect(
await waitForResponse({ result: 'OK', id: 1, jsonrpc: '2.0' }),
).not.toBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
MockWindowPostMessageStream,
spy,
} from '@metamask/snaps-utils/test-utils';
import type { JsonRpcRequest } from '@metamask/utils';

import { WebWorkerSnapExecutor } from './WebWorkerSnapExecutor';

Expand Down Expand Up @@ -41,22 +40,6 @@ async function getResponse(
});
}

/**
* Wait for a outbound request from the stream.
*
* @param stream - The stream to wait for a response on.
* @returns The raw JSON-RPC response object.
*/
async function getOutboundRequest(
stream: MockWindowPostMessageStream,
): Promise<JsonRpcRequest> {
return new Promise((resolve) => {
stream.once('outbound', (data) => {
resolve(data.data);
});
});
}

describe('WebWorkerSnapExecutor', () => {
let consoleSpy: SpyFunction<unknown, unknown>;

Expand Down Expand Up @@ -116,26 +99,6 @@ describe('WebWorkerSnapExecutor', () => {
},
});

const outboundRequest = await getOutboundRequest(mockStream);
expect(outboundRequest.method).toBe('metamask_getProviderState');

writeMessage(mockStream, {
name: 'jsonRpc',
data: {
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
id: outboundRequest.id,
result: {
isUnlocked: false,
accounts: [],
chainId: '0x1',
networkVersion: '1',
},
},
},
});

expect(await getResponse(mockStream)).toStrictEqual({
jsonrpc: '2.0',
id: 2,
Expand Down

0 comments on commit 0a4f8db

Please sign in to comment.