1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

NetworkController: Use messenger for events (#18041)

Currently, the network controller notifies consumers about events by
emitting them directly from the controller. In order to migrate the
controller to the core repo, where controllers use the BaseControllerV2
interface, events should be emitted via a messenger object.

This commit updates the network controller to use a messenger, and then
updates all of the controllers that listen for network events to use the
messenger as well.
This commit is contained in:
Elliot Winkler 2023-03-30 12:39:36 -06:00 committed by GitHub
parent b3e45ea4bc
commit 9b0a6ecc90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 508 additions and 253 deletions

View File

@ -13,7 +13,7 @@ import { convertHexToDecimal } from '@metamask/controller-utils';
import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import DetectTokensController from './detect-tokens';
import NetworkController, { NETWORK_EVENTS } from './network';
import NetworkController, { NetworkControllerEventTypes } from './network';
import PreferencesController from './preferences';
describe('DetectTokensController', function () {
@ -34,7 +34,11 @@ describe('DetectTokensController', function () {
beforeEach(async function () {
keyringMemStore = new ObservableStore({ isUnlocked: false });
network = new NetworkController({ infuraProjectId: 'foo' });
const networkControllerMessenger = new ControllerMessenger();
network = new NetworkController({
messenger: networkControllerMessenger,
infuraProjectId: 'foo',
});
network.initializeProvider(networkControllerProviderConfig);
provider = network.getProviderAndBlockTracker().provider;
@ -54,6 +58,8 @@ describe('DetectTokensController', function () {
network,
provider,
tokenListController,
onInfuraIsBlocked: sinon.stub(),
onInfuraIsUnblocked: sinon.stub(),
});
preferences.setAddresses([
'0x7e57e2',
@ -82,17 +88,20 @@ describe('DetectTokensController', function () {
preferences.store,
),
onNetworkStateChange: (cb) =>
network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const networkState = network.store.getState();
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
chainId: convertHexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
}),
networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
() => {
const networkState = network.store.getState();
const modifiedNetworkState = {
...networkState,
providerConfig: {
...networkState.provider,
chainId: convertHexToDecimal(networkState.provider.chainId),
},
};
return cb(modifiedNetworkState);
},
),
});
sandbox

View File

@ -15,7 +15,6 @@ import {
} from '../../../shared/constants/network';
import * as Utils from '../lib/util';
import MetaMetricsController from './metametrics';
import { NETWORK_EVENTS } from './network';
const segment = createSegmentMock(2, 10000);
@ -72,17 +71,17 @@ function getMockNetworkController() {
},
network: 'loading',
};
const on = sinon.stub().withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE);
const onNetworkDidChange = sinon.stub();
const updateState = (newState) => {
state = { ...state, ...newState };
on.getCall(0).args[1]();
onNetworkDidChange.getCall(0).args[0]();
};
return {
store: {
getState: () => state,
updateState,
},
on,
onNetworkDidChange,
};
}
@ -136,10 +135,8 @@ function getMetaMetricsController({
segment: segmentInstance || segment,
getCurrentChainId: () =>
networkController.store.getState().provider.chainId,
onNetworkDidChange: networkController.on.bind(
networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
),
onNetworkDidChange:
networkController.onNetworkDidChange.bind(networkController),
preferencesStore,
version: '0.0.1',
environment: 'test',

View File

@ -1 +1 @@
export { default, NETWORK_EVENTS } from './network-controller';
export { default, NetworkControllerEventTypes } from './network-controller';

View File

@ -7,6 +7,9 @@ import {
createEventEmitterProxy,
} from 'swappable-obj-proxy';
import EthQuery from 'eth-query';
// ControllerMessenger is referred to in the JSDocs
// eslint-disable-next-line no-unused-vars
import { ControllerMessenger } from '@metamask/base-controller';
import { v4 as random } from 'uuid';
import {
INFURA_PROVIDER_TYPES,
@ -36,6 +39,8 @@ import { createNetworkClient } from './create-network-client';
const env = process.env.METAMASK_ENV;
const fetchWithTimeout = getFetchWithTimeout();
const name = 'NetworkController';
let defaultProviderConfigOpts;
if (process.env.IN_TEST) {
defaultProviderConfigOpts = {
@ -66,15 +71,30 @@ const defaultNetworkDetailsState = {
EIPS: { 1559: undefined },
};
export const NETWORK_EVENTS = {
// Fired after the actively selected network is changed
NETWORK_DID_CHANGE: 'networkDidChange',
// Fired when the actively selected network *will* change
NETWORK_WILL_CHANGE: 'networkWillChange',
// Fired when Infura returns an error indicating no support
INFURA_IS_BLOCKED: 'infuraIsBlocked',
// Fired when not using an Infura network or when Infura returns no error, indicating support
INFURA_IS_UNBLOCKED: 'infuraIsUnblocked',
/**
* The set of event types that this controller can publish via its messenger.
*/
export const NetworkControllerEventTypes = {
/**
* Fired after the current network is changed.
*/
NetworkDidChange: `${name}:networkDidChange`,
/**
* Fired when there is a request to change the current network, but no state
* changes have occurred yet.
*/
NetworkWillChange: `${name}:networkWillChange`,
/**
* Fired after the network is changed to an Infura network, but when Infura
* returns an error denying support for the user's location.
*/
InfuraIsBlocked: `${name}:infuraIsBlocked`,
/**
* Fired after the network is changed to an Infura network and Infura does not
* return an error denying support for the user's location, or after the
* network is changed to a custom network.
*/
InfuraIsUnblocked: `${name}:infuraIsUnblocked`,
};
export default class NetworkController extends EventEmitter {
@ -83,14 +103,22 @@ export default class NetworkController extends EventEmitter {
/**
* Construct a NetworkController.
*
* @param {object} [options] - NetworkController options.
* @param {object} options - Options for this controller.
* @param {ControllerMessenger} options.messenger - The controller messenger.
* @param {object} [options.state] - Initial controller state.
* @param {string} [options.infuraProjectId] - The Infura project ID.
* @param {string} [options.trackMetaMetricsEvent] - A method to forward events to the MetaMetricsController
*/
constructor({ state = {}, infuraProjectId, trackMetaMetricsEvent } = {}) {
constructor({
messenger,
state = {},
infuraProjectId,
trackMetaMetricsEvent,
} = {}) {
super();
this.messenger = messenger;
// create stores
this.providerStore = new ObservableStore(
state.provider || { ...defaultProviderConfig },
@ -134,11 +162,8 @@ export default class NetworkController extends EventEmitter {
throw new Error('Invalid Infura project ID');
}
this._infuraProjectId = infuraProjectId;
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
this.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
this.lookupNetwork();
});
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
}
/**
@ -210,7 +235,7 @@ export default class NetworkController extends EventEmitter {
if (isInfura) {
this._checkInfuraAvailability(type);
} else {
this.emit(NETWORK_EVENTS.INFURA_IS_UNBLOCKED);
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
}
let networkVersion;
@ -373,9 +398,17 @@ export default class NetworkController extends EventEmitter {
const rpcUrl = `https://${network}.infura.io/v3/${this._infuraProjectId}`;
let networkChanged = false;
this.once(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const listener = () => {
networkChanged = true;
});
this.messenger.unsubscribe(
NetworkControllerEventTypes.NetworkDidChange,
listener,
);
};
this.messenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
listener,
);
try {
const response = await fetchWithTimeout(rpcUrl, {
@ -393,14 +426,14 @@ export default class NetworkController extends EventEmitter {
}
if (response.ok) {
this.emit(NETWORK_EVENTS.INFURA_IS_UNBLOCKED);
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
} else {
const responseMessage = await response.json();
if (networkChanged) {
return;
}
if (responseMessage.error === INFURA_BLOCKED_KEY) {
this.emit(NETWORK_EVENTS.INFURA_IS_BLOCKED);
this.messenger.publish(NetworkControllerEventTypes.InfuraIsBlocked);
}
}
} catch (err) {
@ -410,7 +443,7 @@ export default class NetworkController extends EventEmitter {
_switchNetwork(opts) {
// Indicate to subscribers that network is about to change
this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE);
this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange);
// Set loading state
this._setNetworkState('loading');
// Reset network details
@ -418,7 +451,11 @@ export default class NetworkController extends EventEmitter {
// Configure the provider appropriately
this._configureProvider(opts);
// Notify subscribers that network has changed
this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type);
this.messenger.publish(
NetworkControllerEventTypes.NetworkDidChange,
opts.type,
);
this.lookupNetwork();
}
_configureProvider({ type, rpcUrl, chainId }) {

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@ import { normalize as normalizeAddress } from 'eth-sig-util';
import { IPFS_DEFAULT_GATEWAY_URL } from '../../../shared/constants/network';
import { LedgerTransportTypes } from '../../../shared/constants/hardware-wallets';
import { ThemeType } from '../../../shared/constants/preferences';
import { NETWORK_EVENTS } from './network';
export default class PreferencesController {
/**
@ -70,7 +69,8 @@ export default class PreferencesController {
...opts.initState,
};
this.network = opts.network;
this._onInfuraIsBlocked = opts.onInfuraIsBlocked;
this._onInfuraIsUnblocked = opts.onInfuraIsUnblocked;
this.store = new ObservableStore(initState);
this.store.setMaxListeners(13);
this.openPopup = opts.openPopup;
@ -511,10 +511,11 @@ export default class PreferencesController {
//
_subscribeToInfuraAvailability() {
this.network.on(NETWORK_EVENTS.INFURA_IS_BLOCKED, () => {
this._onInfuraIsBlocked(() => {
this._setInfuraBlocked(true);
});
this.network.on(NETWORK_EVENTS.INFURA_IS_UNBLOCKED, () => {
this._onInfuraIsUnblocked(() => {
this._setInfuraBlocked(false);
});
}

View File

@ -19,8 +19,10 @@ describe('preferences controller', function () {
const networkControllerProviderConfig = {
getAccounts: () => undefined,
};
const networkControllerMessenger = new ControllerMessenger();
network = new NetworkController({
infuraProjectId: 'foo',
messenger: networkControllerMessenger,
state: {
provider: {
type: 'mainnet',
@ -50,6 +52,8 @@ describe('preferences controller', function () {
network,
provider,
tokenListController,
onInfuraIsBlocked: sinon.spy(),
onInfuraIsUnblocked: sinon.spy(),
});
});

View File

@ -41,7 +41,6 @@ import fetchEstimatedL1Fee from '../../../ui/helpers/utils/optimism/fetchEstimat
import { Numeric } from '../../../shared/modules/Numeric';
import { EtherDenomination } from '../../../shared/constants/common';
import { NETWORK_EVENTS } from './network';
// The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator
const MAX_GAS_LIMIT = 2500000;
@ -114,6 +113,7 @@ export default class SwapsController {
fetchTradesInfo = defaultFetchTradesInfo,
getCurrentChainId,
getEIP1559GasFeeEstimates,
onNetworkDidChange,
}) {
this.store = new ObservableStore({
swapsState: { ...initialState.swapsState },
@ -137,7 +137,7 @@ export default class SwapsController {
this.ethersProvider = new Web3Provider(provider);
this._currentNetwork = networkController.store.getState().network;
networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, (network) => {
onNetworkDidChange((network) => {
if (network !== 'loading' && network !== this._currentNetwork) {
this._currentNetwork = network;
this.ethersProvider = new Web3Provider(provider);

View File

@ -14,7 +14,6 @@ import {
FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
} from '../../../shared/constants/smartTransactions';
import SwapsController, { utils } from './swaps';
import { NETWORK_EVENTS } from './network';
const MOCK_FETCH_PARAMS = {
slippage: 3,
@ -104,10 +103,6 @@ function getMockNetworkController() {
};
},
},
on: sinon
.stub()
.withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE)
.callsArgAsync(1),
};
}
@ -162,6 +157,7 @@ describe('SwapsController', function () {
return new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController: getMockNetworkController(),
onNetworkDidChange: sinon.stub(),
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
getTokenRatesState: MOCK_TOKEN_RATES_STORE,
@ -209,9 +205,11 @@ describe('SwapsController', function () {
it('should replace ethers instance when network changes', function () {
const networkController = getMockNetworkController();
const onNetworkDidChange = sinon.stub();
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
onNetworkDidChange,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
getTokenRatesState: MOCK_TOKEN_RATES_STORE,
@ -219,9 +217,9 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
const changeNetwork = onNetworkDidChange.getCall(0).args[0];
onNetworkDidChange(NETWORK_IDS.MAINNET);
changeNetwork(NETWORK_IDS.MAINNET);
const newEthersInstance = swapsController.ethersProvider;
assert.notStrictEqual(
@ -233,9 +231,11 @@ describe('SwapsController', function () {
it('should not replace ethers instance when network changes to loading', function () {
const networkController = getMockNetworkController();
const onNetworkDidChange = sinon.stub();
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
onNetworkDidChange,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
getTokenRatesState: MOCK_TOKEN_RATES_STORE,
@ -243,9 +243,9 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
const changeNetwork = onNetworkDidChange.getCall(0).args[0];
onNetworkDidChange('loading');
changeNetwork('loading');
const newEthersInstance = swapsController.ethersProvider;
assert.strictEqual(
@ -257,9 +257,11 @@ describe('SwapsController', function () {
it('should not replace ethers instance when network changes to the same network', function () {
const networkController = getMockNetworkController();
const onNetworkDidChange = sinon.stub();
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
onNetworkDidChange,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
getTokenRatesState: MOCK_TOKEN_RATES_STORE,
@ -267,9 +269,9 @@ describe('SwapsController', function () {
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
const changeNetwork = onNetworkDidChange.getCall(0).args[0];
onNetworkDidChange(NETWORK_IDS.GOERLI);
changeNetwork(NETWORK_IDS.GOERLI);
const newEthersInstance = swapsController.ethersProvider;
assert.strictEqual(

View File

@ -135,7 +135,9 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
import { setupMultiplex } from './lib/stream-utils';
import EnsController from './controllers/ens';
import NetworkController, { NETWORK_EVENTS } from './controllers/network';
import NetworkController, {
NetworkControllerEventTypes,
} from './controllers/network';
import PreferencesController from './controllers/preferences';
import AppStateController from './controllers/app-state';
import CachedBalancesController from './controllers/cached-balances';
@ -249,7 +251,12 @@ export default class MetamaskController extends EventEmitter {
showApprovalRequest: opts.showUserConfirmation,
});
const networkControllerMessenger = this.controllerMessenger.getRestricted({
name: 'NetworkController',
allowedEvents: Object.values(NetworkControllerEventTypes),
});
this.networkController = new NetworkController({
messenger: networkControllerMessenger,
state: initState.NetworkController,
infuraProjectId: opts.infuraProjectId,
trackMetaMetricsEvent: (...args) =>
@ -292,7 +299,14 @@ export default class MetamaskController extends EventEmitter {
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
openPopup: opts.openPopup,
network: this.networkController,
onInfuraIsBlocked: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.InfuraIsBlocked,
),
onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.InfuraIsUnblocked,
),
tokenListController: this.tokenListController,
provider: this.provider,
});
@ -320,8 +334,7 @@ export default class MetamaskController extends EventEmitter {
onPreferencesStateChange: (listener) =>
this.preferencesController.store.subscribe(listener),
onNetworkStateChange: (cb) =>
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const networkState = this.networkController.store.getState();
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
...networkState,
providerConfig: {
@ -427,9 +440,9 @@ export default class MetamaskController extends EventEmitter {
this.metaMetricsController = new MetaMetricsController({
segment,
preferencesStore: this.preferencesController.store,
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange,
),
getNetworkIdentifier: () => {
const { type, rpcUrl } =
@ -464,9 +477,9 @@ export default class MetamaskController extends EventEmitter {
clientId: SWAPS_CLIENT_ID,
getProvider: () =>
this.networkController.getProviderAndBlockTracker().provider,
onNetworkStateChange: this.networkController.on.bind(
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange,
),
getCurrentNetworkEIP1559Compatibility:
this.networkController.getEIP1559Compatibility.bind(
@ -578,9 +591,9 @@ export default class MetamaskController extends EventEmitter {
provider: this.provider,
getCurrentChainId: () =>
this.networkController.store.getState().provider.chainId,
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange,
),
});
@ -590,9 +603,9 @@ export default class MetamaskController extends EventEmitter {
this.incomingTransactionsController = new IncomingTransactionsController({
blockTracker: this.blockTracker,
onNetworkDidChange: this.networkController.on.bind(
this.networkController,
NETWORK_EVENTS.NETWORK_DID_CHANGE,
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange,
),
getCurrentChainId: () =>
this.networkController.store.getState().provider.chainId,
@ -1041,15 +1054,18 @@ export default class MetamaskController extends EventEmitter {
}
});
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, async () => {
const { ticker } = this.networkController.store.getState().provider;
try {
await this.currencyRateController.setNativeCurrency(ticker);
} catch (error) {
// TODO: Handle failure to get conversion rate more gracefully
console.error(error);
}
});
networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
async () => {
const { ticker } = this.networkController.store.getState().provider;
try {
await this.currencyRateController.setNativeCurrency(ticker);
} catch (error) {
// TODO: Handle failure to get conversion rate more gracefully
console.error(error);
}
},
);
this.networkController.lookupNetwork();
this.decryptMessageManager = new DecryptMessageManager({
@ -1083,6 +1099,10 @@ export default class MetamaskController extends EventEmitter {
this.txController.txGasUtil,
),
networkController: this.networkController,
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange,
),
provider: this.provider,
getProviderConfig: () => this.networkController.store.getState().provider,
getTokenRatesState: () => this.tokenRatesController.state,
@ -1122,17 +1142,23 @@ export default class MetamaskController extends EventEmitter {
);
// ensure accountTracker updates balances after network change
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
this.accountTracker._updateAccounts();
});
networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange,
() => {
this.accountTracker._updateAccounts();
},
);
// clear unapproved transactions and messages when the network will change
this.networkController.on(NETWORK_EVENTS.NETWORK_WILL_CHANGE, () => {
this.txController.txStateManager.clearUnapprovedTxs();
this.encryptionPublicKeyManager.clearUnapproved();
this.decryptMessageManager.clearUnapproved();
this.signController.clearUnapproved();
});
networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkWillChange,
() => {
this.txController.txStateManager.clearUnapprovedTxs();
this.encryptionPublicKeyManager.clearUnapproved();
this.decryptMessageManager.clearUnapproved();
this.signController.clearUnapproved();
},
);
this.metamaskMiddleware = createMetamaskMiddleware({
static: {