Skip to content

Commit

Permalink
Added network clients
Browse files Browse the repository at this point in the history
  • Loading branch information
BelfordZ committed Dec 1, 2022
1 parent 5f1f69d commit b6a0952
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 86 deletions.
4 changes: 4 additions & 0 deletions packages/network-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,22 @@
"dependencies": {
"@metamask/base-controller": "workspace:~",
"@metamask/controller-utils": "workspace:~",
"@metamask/eth-json-rpc-infura": "^7.0.0",
"async-mutex": "^0.2.6",
"babel-runtime": "^6.26.0",
"eth-json-rpc-infura": "^5.1.0",
"eth-json-rpc-middleware": "^9.0.1",
"eth-query": "^2.1.2",
"immer": "^9.0.6",
"web3-provider-engine": "^16.0.3"
},
"devDependencies": {
"@metamask/auto-changelog": "^3.1.0",
"@metamask/safe-event-emitter": "^2.0.0",
"@types/jest": "^26.0.22",
"deepmerge": "^4.2.2",
"jest": "^26.4.2",
"nock": "^13.2.9",
"sinon": "^9.2.4",
"ts-jest": "^26.5.2",
"typedoc": "^0.22.15",
Expand Down
64 changes: 46 additions & 18 deletions packages/network-controller/src/NetworkController.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as sinon from 'sinon';
import Web3ProviderEngine from 'web3-provider-engine';
import { ControllerMessenger } from '@metamask/base-controller';
import { NetworkType, NetworksChainId } from '@metamask/controller-utils';
import {
Expand All @@ -8,9 +7,37 @@ import {
NetworkControllerOptions,
ProviderConfig,
} from './NetworkController';
import SafeEventEmitter from '@metamask/safe-event-emitter';
import nock from 'nock';

const RPC_TARGET = 'http://foo';

type WithMockedBlockTrackerOptions = {
nextBlockNumber?: () => string
};

const withMockedBlockTracker = (fn?: () => void, options: WithMockedBlockTrackerOptions = {}) => {
const nextBlockNumber = options.nextBlockNumber ? options.nextBlockNumber : () => '0x42';

nock(new RegExp('https://.*'))
.post(
new RegExp('.*'),
{
jsonrpc: '2.0',
id: new RegExp('.*'),
method: new RegExp('.*'),
params: []
}
)
.reply((uri, reqBody: any) => {
console.log(reqBody);
return [
200,
{ jsonrpc: '2.0', id: reqBody.id, result: nextBlockNumber() }
];
}).persist();
};

const setupController = (
pType: NetworkType,
messenger: NetworkControllerMessenger,
Expand All @@ -28,7 +55,7 @@ const setupController = (
messenger,
};
const controller = new NetworkController(networkControllerOpts);
controller.providerConfig = {} as ProviderConfig;
controller.setProviderType(controller.state.provider.type);
return controller;
};

Expand All @@ -45,9 +72,12 @@ describe('NetworkController', () => {

afterEach(() => {
sinon.restore();
// nock.restore();
// nock.cleanAll();
});

it('should set default state', () => {
it.only('should set default state', () => {
withMockedBlockTracker();
const controller = new NetworkController({
messenger,
infuraProjectId: 'potate',
Expand All @@ -56,7 +86,7 @@ describe('NetworkController', () => {
expect(controller.state).toStrictEqual({
network: 'loading',
isCustomNetwork: false,
properties: { isEIP1559Compatible: false },
properties: {},
provider: {
type: 'mainnet',
chainId: '1',
Expand All @@ -70,18 +100,16 @@ describe('NetworkController', () => {
messenger,
};
const controller = new NetworkController(networkControllerOpts);
controller.providerConfig = {} as ProviderConfig;
expect(controller.provider instanceof Web3ProviderEngine).toBe(true);
controller.setProviderType(controller.state.provider.type);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);
});

(
['kovan', 'rinkeby', 'ropsten', 'mainnet', 'localhost'] as NetworkType[]
).forEach((n) => {
it(`should create a provider instance for ${n} infura network`, () => {
const networkController = setupController(n, messenger);
expect(networkController.provider instanceof Web3ProviderEngine).toBe(
true,
);
expect(networkController.provider).toBeInstanceOf(SafeEventEmitter);
expect(networkController.state.isCustomNetwork).toBe(false);
});
});
Expand All @@ -101,8 +129,7 @@ describe('NetworkController', () => {
messenger,
};
const controller = new NetworkController(networkControllerOpts);
controller.providerConfig = {} as ProviderConfig;
expect(controller.provider instanceof Web3ProviderEngine).toBe(true);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);
expect(controller.state.isCustomNetwork).toBe(true);
});

Expand All @@ -120,20 +147,20 @@ describe('NetworkController', () => {
messenger,
};
const controller = new NetworkController(networkControllerOpts);
controller.providerConfig = {} as ProviderConfig;
expect(controller.provider instanceof Web3ProviderEngine).toBe(true);
controller.setProviderType(controller.state.provider.type);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);
expect(controller.state.isCustomNetwork).toBe(false);
});

it('should set new RPC target', () => {
const controller = new NetworkController({ messenger });
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
controller.setRpcTarget(RPC_TARGET, NetworksChainId.rpc);
expect(controller.state.provider.rpcTarget).toBe(RPC_TARGET);
expect(controller.state.isCustomNetwork).toBe(false);
});

it('should set new provider type', () => {
const controller = new NetworkController({ messenger });
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
controller.setProviderType('localhost');
expect(controller.state.provider.type).toBe('localhost');
expect(controller.state.isCustomNetwork).toBe(false);
Expand Down Expand Up @@ -180,7 +207,7 @@ describe('NetworkController', () => {
});

it('should throw when setting an unrecognized provider type', () => {
const controller = new NetworkController({ messenger });
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
expect(() => controller.setProviderType('junk' as NetworkType)).toThrow(
"Unrecognized network type: 'junk'",
);
Expand All @@ -194,8 +221,9 @@ describe('NetworkController', () => {
network: 'loading',
},
});
controller.providerConfig = {} as ProviderConfig;
controller.setProviderType(controller.state.provider.type);
controller.lookupNetwork = sinon.stub();
if (controller.provider === undefined) { throw new Error('provider is undefined'); }
controller.provider.emit('error', {});
expect((controller.lookupNetwork as any).called).toBe(true);
});
Expand All @@ -218,7 +246,7 @@ describe('NetworkController', () => {
};
messenger.subscribe(event, handleProviderChange);

controller.providerConfig = {} as ProviderConfig;
controller.setProviderType(controller.state.provider.type);
});
});
});
108 changes: 40 additions & 68 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 { Mutex } from 'async-mutex';
import type { Patch } from 'immer';
import {
Expand All @@ -15,6 +12,10 @@ import {
NetworksChainId,
NetworkType,
} from '@metamask/controller-utils';
import { JsonRpcEngine } from 'json-rpc-engine';
import createInfuraClient, { InfuraNetworkType } from './clients/createInfuraClient';
import { providerFromEngine, SafeEventEmitterProvider } from 'eth-json-rpc-middleware';
import createJsonRpcClient from './clients/createJsonRpcClient';

/**
* @type ProviderConfig
Expand Down Expand Up @@ -114,8 +115,6 @@ export class NetworkController extends BaseControllerV2<
> {
private ethQuery: EthQuery;

private internalProviderConfig: ProviderConfig = {} as ProviderConfig;

private infuraProjectId: string | undefined;

private mutex = new Mutex();
Expand Down Expand Up @@ -163,9 +162,7 @@ export class NetworkController extends BaseControllerV2<
private initializeProvider(
type: NetworkType,
rpcTarget?: string,
chainId?: string,
ticker?: string,
nickname?: string,
chainId?: string
) {
this.update((state) => {
state.isCustomNetwork = this.getIsCustomNetwork(chainId);
Expand All @@ -184,7 +181,7 @@ export class NetworkController extends BaseControllerV2<
break;
case RPC:
rpcTarget &&
this.setupStandardProvider(rpcTarget, chainId, ticker, nickname);
this.setupStandardProvider(rpcTarget, chainId as string);
break;
default:
throw new Error(`Unrecognized network type: '${type}'`);
Expand All @@ -197,33 +194,38 @@ export class NetworkController extends BaseControllerV2<
state.network = 'loading';
state.properties = {};
});
const { rpcTarget, type, chainId, ticker } = this.state.provider;
this.initializeProvider(type, rpcTarget, chainId, ticker);
const { rpcTarget, type, chainId } = this.state.provider;
this.initializeProvider(type, rpcTarget, chainId);
this.lookupNetwork();
}

private registerProvider() {
if (this.provider === undefined) {
throw new Error('dont call registerProvider when networkController.provider is unset');
}

this.provider.on('error', this.verifyNetwork.bind(this));
this.ethQuery = new EthQuery(this.provider);
}

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

private setupStandardProvider(
rpcTarget: string,
chainId?: string,
) {
const { networkMiddleware, blockTracker } = createJsonRpcClient(
rpcTarget,
chainId,
);

this.updateProvider(networkMiddleware, blockTracker);
}

private getIsCustomNetwork(chainId?: string) {
Expand All @@ -237,27 +239,15 @@ export class NetworkController extends BaseControllerV2<
);
}

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

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

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

this.provider = provider;
this.registerProvider();
}
Expand All @@ -274,27 +264,9 @@ export class NetworkController extends BaseControllerV2<

/**
* Ethereum provider object for the current network
* todo: should never be undefined (definitely assigned in constructor)
*/
provider: any;

/**
* Sets a new configuration for web3-provider-engine.
*
* TODO: Replace this wth a method.
*
* @param providerConfig - The web3-provider-engine configuration.
*/
set providerConfig(providerConfig: ProviderConfig) {
this.internalProviderConfig = providerConfig;
const { type, rpcTarget, chainId, ticker, nickname } = this.state.provider;
this.initializeProvider(type, rpcTarget, chainId, ticker, nickname);
this.registerProvider();
this.lookupNetwork();
}

get providerConfig() {
throw new Error('Property only used for setting');
}
provider: SafeEventEmitterProvider | undefined;

/**
* Refreshes the current network code.
Expand Down Expand Up @@ -339,7 +311,7 @@ export class NetworkController extends BaseControllerV2<
// If testnet the ticker symbol should use a testnet prefix
const ticker =
type in TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL &&
TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL[type].length > 0
TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL[type].length > 0
? TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL[type]
: 'ETH';

Expand Down
Loading

0 comments on commit b6a0952

Please sign in to comment.