Skip to content

Commit

Permalink
Added network clients
Browse files Browse the repository at this point in the history
  • Loading branch information
BelfordZ committed Apr 3, 2023
1 parent d71226e commit e3aff5d
Show file tree
Hide file tree
Showing 14 changed files with 1,790 additions and 1,094 deletions.
9 changes: 6 additions & 3 deletions packages/network-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@
"dependencies": {
"@metamask/base-controller": "workspace:^",
"@metamask/controller-utils": "workspace:^",
"@metamask/eth-json-rpc-infura": "^8.0.0",
"@metamask/eth-json-rpc-middleware": "^11.0.0",
"@metamask/eth-json-rpc-provider": "^1.0.0",
"@metamask/swappable-obj-proxy": "^2.1.0",
"@metamask/utils": "^3.3.1",
"async-mutex": "^0.2.6",
"babel-runtime": "^6.26.0",
"eth-json-rpc-infura": "^5.1.0",
"eth-block-tracker": "^7.0.0",
"eth-query": "^2.1.2",
"immer": "^9.0.6",
"uuid": "^8.3.2",
"web3-provider-engine": "^16.0.3"
"json-rpc-engine": "^6.1.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@json-rpc-specification/meta-schema": "^1.0.6",
Expand Down
84 changes: 41 additions & 43 deletions packages/network-controller/src/NetworkController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import EthQuery from 'eth-query';
import Subprovider from 'web3-provider-engine/subproviders/provider';
import createInfuraProvider from 'eth-json-rpc-infura/src/createProvider';
import createMetamaskProvider from 'web3-provider-engine/zero';
import { createEventEmitterProxy } from '@metamask/swappable-obj-proxy';
import type { SwappableProxy } from '@metamask/swappable-obj-proxy';
import { Mutex } from 'async-mutex';
Expand All @@ -19,8 +16,15 @@ import {
isNetworkType,
BUILT_IN_NETWORKS,
} from '@metamask/controller-utils';

import { JsonRpcEngine, JsonRpcMiddleware } from 'json-rpc-engine';
import { providerFromEngine } from '@metamask/eth-json-rpc-provider';
import { PollingBlockTracker } from 'eth-block-tracker';
import { assertIsStrictHexString } from '@metamask/utils';
import {
createInfuraClient,
InfuraNetworkType,
} from './clients/createInfuraClient';
import { createJsonRpcClient } from './clients/createJsonRpcClient';

/**
* @type ProviderConfig
Expand Down Expand Up @@ -252,8 +256,6 @@ export class NetworkController extends BaseControllerV2<
type: NetworkType,
rpcTarget?: string,
chainId?: string,
ticker?: string,
nickname?: string,
) {
this.update((state) => {
state.isCustomNetwork = this.getIsCustomNetwork(chainId);
Expand All @@ -269,8 +271,10 @@ export class NetworkController extends BaseControllerV2<
this.setupStandardProvider(LOCALHOST_RPC_URL);
break;
case NetworkType.rpc:
rpcTarget &&
this.setupStandardProvider(rpcTarget, chainId, ticker, nickname);
if (chainId === undefined) {
throw new Error('chainId must be passed in for custom rpcs');
}
rpcTarget && this.setupStandardProvider(rpcTarget, chainId);
break;
default:
throw new Error(`Unrecognized network type: '${type}'`);
Expand All @@ -293,8 +297,8 @@ export class NetworkController extends BaseControllerV2<
state.network = 'loading';
state.networkDetails = {};
});
const { rpcTarget, type, chainId, ticker } = this.state.providerConfig;
this.configureProvider(type, rpcTarget, chainId, ticker);
const { rpcTarget, type, chainId } = this.state.providerConfig;
this.configureProvider(type, rpcTarget, chainId);
this.lookupNetwork();
}

Expand All @@ -307,20 +311,12 @@ export class NetworkController extends BaseControllerV2<
}
}

private setupInfuraProvider(type: NetworkType) {
const infuraProvider = createInfuraProvider({
network: type,
projectId: this.infuraProjectId,
});
const infuraSubprovider = new Subprovider(infuraProvider);
const config = {
dataSubprovider: infuraSubprovider,
engineParams: {
blockTrackerProvider: infuraProvider,
pollingInterval: 12000,
},
};
this.updateProvider(createMetamaskProvider(config));
private setupInfuraProvider(type: InfuraNetworkType) {
const { networkMiddleware, blockTracker } = createInfuraClient(
type,
this.infuraProjectId || '',
);
this.updateProvider(networkMiddleware, blockTracker);
}

private getIsCustomNetwork(chainId?: string) {
Expand All @@ -332,34 +328,37 @@ export class NetworkController extends BaseControllerV2<
);
}

private setupStandardProvider(
rpcTarget: string,
chainId?: string,
ticker?: string,
nickname?: string,
) {
const config = {
private setupStandardProvider(rpcTarget: string, chainId?: string) {
const { networkMiddleware, blockTracker } = createJsonRpcClient(
rpcTarget,
chainId,
engineParams: { pollingInterval: 12000 },
nickname,
rpcUrl: rpcTarget,
ticker,
};
this.updateProvider(createMetamaskProvider(config));
);

this.updateProvider(networkMiddleware, blockTracker);
}

private updateProvider(provider: Provider) {
// extensions network controller saves a copy of the blockTracker.
// need to figure out if we migrate this functionality or not.
private updateProvider(
networkMiddleware: JsonRpcMiddleware<unknown, unknown>,
blockTracker: PollingBlockTracker,
) {
this.safelyStopProvider(this.#provider);

const engine = new JsonRpcEngine();
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);

this.#setProviderAndBlockTracker({
provider,
blockTracker: provider._blockTracker,
blockTracker,
});
this.registerProvider();
}

private safelyStopProvider(provider: Provider | undefined) {
setTimeout(() => {
provider?.stop();
provider?.removeAllListeners();
}, 500);
}

Expand All @@ -374,9 +373,8 @@ export class NetworkController extends BaseControllerV2<
*
*/
initializeProvider() {
const { type, rpcTarget, chainId, ticker, nickname } =
this.state.providerConfig;
this.configureProvider(type, rpcTarget, chainId, ticker, nickname);
const { type, rpcTarget, chainId } = this.state.providerConfig;
this.configureProvider(type, rpcTarget, chainId);
this.registerProvider();
this.lookupNetwork();
}
Expand Down
74 changes: 74 additions & 0 deletions packages/network-controller/src/clients/createInfuraClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
createScaffoldMiddleware,
JsonRpcMiddleware,
mergeMiddleware,
} from 'json-rpc-engine';
import {
createBlockRefMiddleware,
createRetryOnEmptyMiddleware,
createBlockCacheMiddleware,
createInflightCacheMiddleware,
createBlockTrackerInspectorMiddleware,
} from '@metamask/eth-json-rpc-middleware';
import { providerFromMiddleware } from '@metamask/eth-json-rpc-provider';

import { NetworksChainId } from '@metamask/controller-utils';

import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura';
import { PollingBlockTracker } from 'eth-block-tracker';

// could improve this type... this is not a very complete list
export type InfuraNetworkType = 'mainnet' | 'goerli' | 'sepolia';

/**
* Construct middleware to manage calls to infura.
*
* @param network - type of network
* @param projectId - infura project id (api key)
* @returns middleware and a blockTracker
*/
export function createInfuraClient(
network: InfuraNetworkType,
projectId: string,
) {
const infuraMiddleware = createInfuraMiddleware({
network,
projectId,
maxAttempts: 5,
source: 'metamask',
});
const infuraProvider = providerFromMiddleware(infuraMiddleware);
const blockTracker = new PollingBlockTracker({ provider: infuraProvider });

const networkMiddleware = mergeMiddleware([
createNetworkAndChainIdMiddleware(network),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockRefMiddleware({ blockTracker, provider: infuraProvider }),
createRetryOnEmptyMiddleware({ blockTracker, provider: infuraProvider }),
createBlockTrackerInspectorMiddleware({ blockTracker }),
infuraMiddleware,
]);
return { networkMiddleware, blockTracker };
}

/**
* Create middleware to implement static / scafolded methods.
*
* @param network - type of network
* @returns the middleware
*/
function createNetworkAndChainIdMiddleware(
network: InfuraNetworkType,
): JsonRpcMiddleware<unknown, unknown> {
const chainId = NetworksChainId[network];

if (typeof chainId === undefined) {
throw new Error(`createInfuraClient - unknown network "${network}"`);
}

return createScaffoldMiddleware({
eth_chainId: `0x${parseInt(chainId, 10).toString(16)}`,
net_version: chainId,
});
}
91 changes: 91 additions & 0 deletions packages/network-controller/src/clients/createJsonRpcClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
createAsyncMiddleware,
JsonRpcMiddleware,
mergeMiddleware,
} from 'json-rpc-engine';
import {
createFetchMiddleware,
createBlockRefRewriteMiddleware,
createBlockCacheMiddleware,
createInflightCacheMiddleware,
createBlockTrackerInspectorMiddleware,
} from '@metamask/eth-json-rpc-middleware';
import { PollingBlockTracker } from 'eth-block-tracker';
import { providerFromMiddleware } from '@metamask/eth-json-rpc-provider';
import { CreateClientResult } from './types';

const SECOND = 1000;

/**
* Create client middleware for a custom rpc endpoint.
*
* @param rpcUrl - url of the rpc endpoint.
* @param chainId - the chain id for the rpc endpoint. This value will always be returned by eth_chainId.
* @returns The network middleware and the block tracker.
*/
export function createJsonRpcClient(
rpcUrl: string,
chainId?: string,
): CreateClientResult {
const blockTrackerOpts = process.env.IN_TEST // eslint-disable-line node/no-process-env
? { pollingInterval: SECOND }
: {};
const fetchMiddleware = createFetchMiddleware({
rpcUrl,
fetch,
btoa,
});
const blockProvider = providerFromMiddleware(fetchMiddleware);
const blockTracker = new PollingBlockTracker({
...blockTrackerOpts,
provider: blockProvider,
});
const testMiddlewares = process.env.IN_TEST // eslint-disable-line node/no-process-env
? [createEstimateGasDelayTestMiddleware()]
: [];

const networkMiddleware = mergeMiddleware([
...testMiddlewares,
createChainIdMiddleware(chainId),
createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightCacheMiddleware(),
createBlockTrackerInspectorMiddleware({ blockTracker }),
fetchMiddleware,
]);

return { networkMiddleware, blockTracker };
}

/**
* Create middleware to catch calls to eth_chainId.
*
* @param chainId - the chain id to use as the response
* @returns the middleware
*/
function createChainIdMiddleware(
chainId?: string,
): JsonRpcMiddleware<unknown, unknown> {
return (req, res, next, end) => {
if (req.method === 'eth_chainId') {
res.result = chainId;
return end();
}
return next();
};
}

/**
* For use in tests only.
* Adds a delay to `eth_estimateGas` calls.
*
* @returns the middleware implementing estimate gas
*/
function createEstimateGasDelayTestMiddleware() {
return createAsyncMiddleware(async (req, _, next) => {
if (req.method === 'eth_estimateGas') {
await new Promise((resolve) => setTimeout(resolve, SECOND * 2));
}
return next();
});
}
7 changes: 7 additions & 0 deletions packages/network-controller/src/clients/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PollingBlockTracker } from 'eth-block-tracker';
import { JsonRpcMiddleware } from 'json-rpc-engine';

export type CreateClientResult = {
networkMiddleware: JsonRpcMiddleware<any, any>;
blockTracker: PollingBlockTracker;
};
Loading

0 comments on commit e3aff5d

Please sign in to comment.