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

chore: Update json-rpc-provider & json-rpc-middleware & json-rpc-filters #10098

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
205db8f
feat: use metamask ethJsonRpcMiddleware
MarioAslau Jun 10, 2024
d927d34
fix(deps): eth-json-rpc-filters@4.2.2 -> @metamask/eth-json-rpc-filte…
legobeat Apr 16, 2024
4d2c65f
deps: json-rpc-engine@^6.1.0 -> @metamask/json-rpc-engine@^7.1.0
legobeat Apr 17, 2024
63d560a
Update JsonRpcRequest typings
legobeat Apr 17, 2024
fbd8509
chainId is now hex string, not number
legobeat Apr 18, 2024
f1e0653
chore(test): eth_sendTransaction test type workarounds
legobeat May 20, 2024
e4e7599
fixup: console.error in test
legobeat May 20, 2024
4a914af
fix(test): test for error cause message in RPCMethodMIddleware
legobeat May 20, 2024
e1d2a04
chore: update test
legobeat Jun 11, 2024
0c1a8a1
chore: wip: add @ts-expect-error directive
legobeat Jun 11, 2024
69a39cc
Merge branch 'feat/eth-json-rpc' into json-rpc-middleware-12
legobeat Jun 11, 2024
e160aa5
chore: update .iyarc
legobeat Jun 11, 2024
98fba92
Merge branch 'main' into json-rpc-middleware-12
tommasini Jun 11, 2024
242934d
feat: downgrade from 12 to 11
MarioAslau Jun 11, 2024
6cfbed3
Merge branch 'main' into json-rpc-middleware-12
MarioAslau Jun 11, 2024
dfae3be
Merge branch 'main' into json-rpc-middleware-12
MarioAslau Jun 20, 2024
1342855
middleware to latest version
tommasini Jun 24, 2024
cd52169
merge main and solve conflicts
tommasini Jun 24, 2024
c6e47ed
background bridge cleanup fix, bump json rpc engine to latest version
tommasini Jun 24, 2024
eb65e5c
fix depcheck issues
tommasini Jun 24, 2024
ff9d996
fix lint issues
tommasini Jun 24, 2024
6031165
fix lint and fix ts error
tommasini Jun 24, 2024
119c662
dedupe
tommasini Jun 24, 2024
ddfb55d
received always the Internal JSON-RPC error. error message
tommasini Jun 24, 2024
49cf6fa
Merge branch 'main' into chore/provider-middleware-filters
legobeat Jun 25, 2024
010a194
Merge branch 'main' into chore/provider-middleware-filters
legobeat Jun 25, 2024
306f90e
fix on snap bridge
tommasini Jun 25, 2024
a98f7d3
feat: update createLegacyMethodMiddleware tests
MarioAslau Jun 25, 2024
b7b8f28
feat: remove type
MarioAslau Jun 25, 2024
def0c9d
feat: removed unused imports
MarioAslau Jun 25, 2024
21b26c3
Revert "feat: removed unused imports"
MarioAslau Jun 25, 2024
6c61321
feat: removed unused imports
MarioAslau Jun 25, 2024
0debc05
Merge branch 'main' into chore/provider-middleware-filters
tommasini Jun 25, 2024
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
4 changes: 0 additions & 4 deletions .iyarc
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# ReDoS vulnerability, no impact to this application, and fix not backported yet to the versions we use

GHSA-c2qf-rxjj-qqgw

tommasini marked this conversation as resolved.
Show resolved Hide resolved
# ip SSRF improper categorization in isPublic, since it only affect dev tools on, and the server is actually a local server, this advisory shouldn't apply to this use cases

GHSA-2p57-rm9w-gvfp
14 changes: 5 additions & 9 deletions app/core/BackgroundBridge/BackgroundBridge.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable import/no-commonjs */
import URL from 'url-parse';
import { ChainId } from '@metamask/controller-utils';
import { JsonRpcEngine } from 'json-rpc-engine';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import MobilePortStream from '../MobilePortStream';
import { setupMultiplex } from '../../util/streams';
import {
Expand All @@ -22,14 +22,14 @@ import {
selectProviderConfig,
} from '../../selectors/networkController';
import { store } from '../../store';
const createFilterMiddleware = require('@metamask/eth-json-rpc-filters');
const createSubscriptionManager = require('@metamask/eth-json-rpc-filters/subscriptionManager');
import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware';
///: BEGIN:ONLY_INCLUDE_IF(snaps)
import snapMethodMiddlewareBuilder from '../Snaps/SnapsMethodMiddleware';
import { SubjectType } from '@metamask/permission-controller';
///: END:ONLY_INCLUDE_IF

const createFilterMiddleware = require('eth-json-rpc-filters');
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager');
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware');
const pump = require('pump');
// eslint-disable-next-line import/no-nodejs-modules
const EventEmitter = require('events').EventEmitter;
Expand Down Expand Up @@ -350,11 +350,7 @@ export class BackgroundBridge extends EventEmitter {

pump(outStream, providerStream, outStream, (err) => {
// handle any middleware cleanup
this.engine._middleware.forEach((mid) => {
if (mid.destroy && typeof mid.destroy === 'function') {
mid.destroy();
}
});
this.engine.destroy();
tommasini marked this conversation as resolved.
Show resolved Hide resolved
if (err) Logger.log('Error with provider stream conn', err);
});
}
Expand Down
50 changes: 31 additions & 19 deletions app/core/RPCMethods/RPCMethodMiddleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {
JsonRpcEngine,
import { JsonRpcEngine, JsonRpcMiddleware } from '@metamask/json-rpc-engine';
import type { ProviderConfig } from '@metamask/network-controller';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type { TransactionParams } from '@metamask/transaction-controller';
import type {
Json,
JsonRpcFailure,
JsonRpcMiddleware,
JsonRpcParams,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcSuccess,
} from 'json-rpc-engine';
import type { TransactionParams } from '@metamask/transaction-controller';
import type { ProviderConfig } from '@metamask/network-controller';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
} from '@metamask/utils';
import Engine from '../Engine';
import { store } from '../../store';
import { getPermittedAccounts } from '../Permissions';
Expand Down Expand Up @@ -93,8 +94,8 @@ const jsonrpc = '2.0' as const;
* @throws If the given value is not a valid {@link JsonRpcSuccess} object.
*/
function assertIsJsonRpcSuccess(
response: JsonRpcResponse<unknown>,
): asserts response is JsonRpcSuccess<unknown> {
response: JsonRpcResponse<Json>,
): asserts response is JsonRpcSuccess<Json> {
if ('error' in response) {
throw new Error(`Response failed with error '${JSON.stringify('error')}'`);
} else if (!('result' in response)) {
Expand Down Expand Up @@ -195,8 +196,8 @@ async function callMiddleware({
middleware,
request,
}: {
middleware: JsonRpcMiddleware<unknown, unknown>;
request: JsonRpcRequest<unknown>;
middleware: JsonRpcMiddleware<JsonRpcParams, Json>;
request: JsonRpcRequest<JsonRpcParams>;
}) {
const engine = new JsonRpcEngine();
engine.push(middleware);
Expand Down Expand Up @@ -376,7 +377,6 @@ describe('getRpcMethodMiddleware', () => {
permissionController.createPermissionMiddleware({
origin: hostMock,
});
// @ts-expect-error JsonRpcId (number | string | void) doesn't match PS middleware's id, which is (string | number | null)
engine.push(permissionMiddleware);
const middleware = getRpcMethodMiddleware(getMinimalOptions());
engine.push(middleware);
Expand Down Expand Up @@ -943,7 +943,8 @@ describe('getRpcMethodMiddleware', () => {
it('returns a JSON-RPC error if an error is thrown when adding this transaction', async () => {
// Omit `from` and `chainId` here to skip validation for simplicity
// Downcast needed here because `from` is required by this type
const mockTransactionParameters = {} as TransactionParams;
const mockTransactionParameters = {} as (TransactionParams &
JsonRpcParams)[];
// Transaction fails before returning a result
mockAddTransaction.mockImplementation(async () => {
throw new Error('Failed to add transaction');
Expand All @@ -958,20 +959,26 @@ describe('getRpcMethodMiddleware', () => {
method: 'eth_sendTransaction',
params: [mockTransactionParameters],
};
const expectedError = rpcErrors.internal('Failed to add transaction');
const expectedError = rpcErrors.internal('Internal JSON-RPC error.');
const expectedErrorCauseMessage = 'Failed to add transaction';

const response = await callMiddleware({ middleware, request });

expect((response as JsonRpcFailure).error.code).toBe(expectedError.code);
expect((response as JsonRpcFailure).error.message).toBe(
expectedError.message,
);
// @ts-expect-error - TODO: This should type
expect((response as JsonRpcFailure).error.data.cause.message).toBe(
tommasini marked this conversation as resolved.
Show resolved Hide resolved
expectedErrorCauseMessage,
);
});

it('returns a JSON-RPC error if an error is thrown after approval', async () => {
// Omit `from` and `chainId` here to skip validation for simplicity
// Downcast needed here because `from` is required by this type
const mockTransactionParameters = {} as TransactionParams;
const mockTransactionParameters = {} as (TransactionParams &
JsonRpcParams)[];
setupGlobalState({
addTransactionResult: Promise.reject(
new Error('Failed to process transaction'),
Expand All @@ -987,14 +994,19 @@ describe('getRpcMethodMiddleware', () => {
method: 'eth_sendTransaction',
params: [mockTransactionParameters],
};
const expectedError = rpcErrors.internal('Failed to process transaction');
const expectedError = rpcErrors.internal('Internal JSON-RPC error.');
const expectedErrorCauseMessage = 'Failed to process transaction';

const response = await callMiddleware({ middleware, request });

expect((response as JsonRpcFailure).error.code).toBe(expectedError.code);
expect((response as JsonRpcFailure).error.message).toBe(
expectedError.message,
);
// @ts-expect-error - TODO: This should type
expect((response as JsonRpcFailure).error.data.cause.message).toBe(
expectedErrorCauseMessage,
);
});
});

Expand Down Expand Up @@ -1060,7 +1072,7 @@ describe('getRpcMethodMiddleware', () => {
method: 'personal_ecRecover',
params: [helloWorldMessage],
};
const expectedError = rpcErrors.internal('Missing signature parameter');
const expectedError = rpcErrors.internal('Internal JSON-RPC error.');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know if there's a possibility of breaking UI since the error shown is different than what was there before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing question! There is a thread going on around this!

const response = await callMiddleware({ middleware, request });

Expand All @@ -1079,9 +1091,9 @@ describe('getRpcMethodMiddleware', () => {
jsonrpc,
id: 1,
method: 'personal_ecRecover',
params: [undefined, helloWorldSignature],
params: [undefined, helloWorldSignature] as JsonRpcParams,
};
const expectedError = rpcErrors.internal('Missing data parameter');
const expectedError = rpcErrors.internal('Internal JSON-RPC error.');

const response = await callMiddleware({ middleware, request });

Expand Down
9 changes: 5 additions & 4 deletions app/core/RPCMethods/RPCMethodMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Alert } from 'react-native';
import { getVersion } from 'react-native-device-info';
import { createAsyncMiddleware } from 'json-rpc-engine';
import { createAsyncMiddleware } from '@metamask/json-rpc-engine';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import {
EndFlowOptions,
Expand All @@ -15,6 +15,7 @@ import {
PermissionController,
permissionRpcMethods,
} from '@metamask/permission-controller';
import type { Hex } from '@metamask/utils';
import Networks, {
blockTagParamIndex,
getAllNetworks,
Expand Down Expand Up @@ -113,7 +114,7 @@ export const checkActiveAccountAndChainId = async ({
isWalletConnect,
}: {
address?: string;
chainId?: number;
chainId?: Hex;
channelId?: string;
hostname: string;
isWalletConnect: boolean;
Expand Down Expand Up @@ -224,7 +225,7 @@ const generateRawSignature = async ({
title: { current: string };
icon: { current: string | undefined };
analytics: { [key: string]: string | boolean };
chainId: number;
chainId: Hex;
isMMSDK: boolean;
channelId?: string;
getSource: () => string;
Expand Down Expand Up @@ -533,7 +534,7 @@ export const getRpcMethodMiddleware = ({
chainId,
}: {
from?: string;
chainId?: number;
chainId?: Hex;
}) => {
await checkActiveAccountAndChainId({
hostname,
Expand Down
18 changes: 14 additions & 4 deletions app/core/RPCMethods/createLegacyMethodMiddleware/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
JsonRpcEngine,
JsonRpcEngineEndCallback,
JsonRpcEngineNextCallback,
} from 'json-rpc-engine';
} from '@metamask/json-rpc-engine';
import {
Json,
JsonRpcParams,
Expand Down Expand Up @@ -55,6 +55,8 @@ jest.mock('./util', () => {
});

describe('createLegacyMethodMiddleware', () => {
const INTERNAL_JSON_RPC_ERROR = 'Internal JSON-RPC error.';

const method1 = 'method1';

const getDefaultHooks = () => ({
Expand Down Expand Up @@ -150,7 +152,11 @@ describe('createLegacyMethodMiddleware', () => {
});
assertIsJsonRpcFailure(response);

expect(response.error.message).toBe('test error');
tommasini marked this conversation as resolved.
Show resolved Hide resolved
// Type assertion for the error not having cause object
const errorData = response.error.data as { cause?: Error };

expect(response.error.message).toBe(INTERNAL_JSON_RPC_ERROR);
expect(errorData.cause?.message).toBe('test error');
});

it('should handle errors thrown by the implementation', async () => {
Expand All @@ -166,7 +172,11 @@ describe('createLegacyMethodMiddleware', () => {
});
assertIsJsonRpcFailure(response);

expect(response.error.message).toBe('test error');
// Type assertion for the error not having cause object
const errorData = response.error.data as { cause?: Error };

expect(response.error.message).toBe(INTERNAL_JSON_RPC_ERROR);
expect(errorData.cause?.message).toBe('test error');
});

it('should handle non-errors thrown by the implementation', async () => {
Expand All @@ -183,7 +193,7 @@ describe('createLegacyMethodMiddleware', () => {
assertIsJsonRpcFailure(response);

expect(response.error).toMatchObject({
message: 'Internal JSON-RPC error.',
message: INTERNAL_JSON_RPC_ERROR,
data: 'foo',
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { rpcErrors } from '@metamask/rpc-errors';
import type { JsonRpcMiddleware } from 'json-rpc-engine';
import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';

import { UNSUPPORTED_RPC_METHODS } from '../utils';
import { Json, JsonRpcParams } from '@metamask/utils';

/**
* Creates a middleware that rejects explicitly unsupported RPC methods with the
* appropriate error.
*/
const createUnsupportedMethodMiddleware = (): JsonRpcMiddleware<
unknown,
void
JsonRpcParams,
Json
> =>
async function unsupportedMethodMiddleware(req, _res, next, end) {
if ((UNSUPPORTED_RPC_METHODS as Set<string>).has(req.method)) {
Expand Down
21 changes: 16 additions & 5 deletions app/core/RPCMethods/eth_sendTransaction.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// eslint-disable-next-line import/no-nodejs-modules
import { inspect } from 'util';
import type { JsonRpcRequest, PendingJsonRpcResponse } from 'json-rpc-engine';
import type {
Json,
JsonRpcParams,
JsonRpcRequest,
PendingJsonRpcResponse,
} from '@metamask/utils';
import type {
TransactionParams,
TransactionController,
Expand Down Expand Up @@ -40,8 +45,10 @@ jest.mock('../../util/transaction-controller', () => ({
* @returns The JSON-RPC request.
*/
function constructSendTransactionRequest(
params: unknown,
): JsonRpcRequest<unknown> & { method: 'eth_sendTransaction' } {
params: [TransactionParams & JsonRpcParams],
): JsonRpcRequest<[TransactionParams & JsonRpcParams]> & {
method: 'eth_sendTransaction';
} {
return {
jsonrpc: '2.0',
id: 1,
Expand All @@ -55,7 +62,7 @@ function constructSendTransactionRequest(
*
* @returns A pending JSON-RPC response.
*/
function constructPendingJsonRpcResponse(): PendingJsonRpcResponse<unknown> {
function constructPendingJsonRpcResponse(): PendingJsonRpcResponse<Json> {
return {
jsonrpc: '2.0',
id: 1,
Expand Down Expand Up @@ -136,7 +143,9 @@ function getMockAddTransaction({
describe('eth_sendTransaction', () => {
it('sends the transaction and returns the resulting hash', async () => {
const mockAddress = '0x0000000000000000000000000000000000000001';
const mockTransactionParameters = { from: mockAddress };
const mockTransactionParameters = {
from: mockAddress,
};
const expectedResult = 'fake-hash';
const pendingResult = constructPendingJsonRpcResponse();

Expand Down Expand Up @@ -164,6 +173,7 @@ describe('eth_sendTransaction', () => {
async () =>
await eth_sendTransaction({
hostname: 'example.metamask.io',
//@ts-expect-error - invalid parameters forced
req: constructSendTransactionRequest(invalidParameter),
res: constructPendingJsonRpcResponse(),
sendTransaction: getMockAddTransaction({
Expand All @@ -186,6 +196,7 @@ describe('eth_sendTransaction', () => {
async () =>
await eth_sendTransaction({
hostname: 'example.metamask.io',
//@ts-expect-error - invalid parameters forced
req: constructSendTransactionRequest(invalidParameter),
res: constructPendingJsonRpcResponse(),
sendTransaction: getMockAddTransaction({
Expand Down
Loading
Loading