1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Move MetaMask middleware out of network controller (#16863)

The "MetaMask middleware" is the set of middleware for handling methods that
we don't send to the network. This includes signing, encryption, `getAccounts`,
and methods that rely on pending transaction state.

Previously this middleware was setup as part of the network client, despite
having nothing to do with the network. They have been moved into the main RPC
pipeline established in `metamask-controller.js` instead.

This is a pure refactor, and should have no functional changes. The middleware
are still run in exactly the same order with the same arguments.
This commit is contained in:
Mark Stacey 2023-01-06 13:44:50 -03:30 committed by GitHub
parent cd2249f193
commit 87ce653c86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 199 additions and 228 deletions

View File

@ -27,7 +27,6 @@ import {
isSafeChainId,
} from '../../../../shared/modules/network.utils';
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout';
import createMetamaskMiddleware from './createMetamaskMiddleware';
import createInfuraClient from './createInfuraClient';
import createJsonRpcClient from './createJsonRpcClient';
@ -136,8 +135,7 @@ export default class NetworkController extends EventEmitter {
await this._blockTracker.destroy();
}
async initializeProvider(providerParams) {
this._baseProviderParams = providerParams;
async initializeProvider() {
const { type, rpcUrl, chainId } = this.getProviderConfig();
this._configureProvider({ type, rpcUrl, chainId });
await this.lookupNetwork();
@ -470,9 +468,6 @@ export default class NetworkController extends EventEmitter {
provider: networkProvider,
blockTracker,
});
const metamaskMiddleware = createMetamaskMiddleware(
this._baseProviderParams,
);
const engine = new JsonRpcEngine();
subscriptionManager.events.on('notification', (message) =>
@ -480,7 +475,6 @@ export default class NetworkController extends EventEmitter {
);
engine.push(filterMiddleware);
engine.push(subscriptionManager.middleware);
engine.push(metamaskMiddleware);
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);

View File

@ -1,8 +1,10 @@
import sinon from 'sinon';
import nock from 'nock';
import { getNetworkDisplayName } from './util';
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
import NetworkController, { NETWORK_EVENTS } from './network-controller';
const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key];
/**
* Construct a successful RPC response.
*

View File

@ -1,49 +0,0 @@
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../../shared/constants/transaction';
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key];
export function formatTxMetaForRpcResult(txMeta) {
const { r, s, v, hash, txReceipt, txParams } = txMeta;
const {
to,
data,
nonce,
gas,
from,
value,
gasPrice,
accessList,
maxFeePerGas,
maxPriorityFeePerGas,
} = txParams;
const formattedTxMeta = {
v,
r,
s,
to,
gas,
from,
hash,
nonce,
input: data || '0x',
value: value || '0x0',
accessList: accessList || null,
blockHash: txReceipt?.blockHash || null,
blockNumber: txReceipt?.blockNumber || null,
transactionIndex: txReceipt?.transactionIndex || null,
};
if (maxFeePerGas && maxPriorityFeePerGas) {
formattedTxMeta.gasPrice = maxFeePerGas;
formattedTxMeta.maxFeePerGas = maxFeePerGas;
formattedTxMeta.maxPriorityFeePerGas = maxPriorityFeePerGas;
formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
} else {
formattedTxMeta.gasPrice = gasPrice;
formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.LEGACY;
}
return formattedTxMeta;
}

View File

@ -1,100 +0,0 @@
import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
TRANSACTION_ENVELOPE_TYPES,
} from '../../../../shared/constants/transaction';
import { formatTxMetaForRpcResult } from './util';
describe('network utils', () => {
describe('formatTxMetaForRpcResult', () => {
it('should correctly format the tx meta object (EIP-1559)', () => {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'other',
chainId: '0x5',
time: 1624408066355,
metamaskNetworkId: '5',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
};
const expectedResult = {
accessList: null,
blockHash: null,
blockNumber: null,
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
gasPrice: '0x77359400',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
nonce: '0x4b',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
transactionIndex: null,
type: '0x2',
v: '0x29',
value: '0x0',
};
const result = formatTxMetaForRpcResult(txMeta);
expect(result).toStrictEqual(expectedResult);
});
it('should correctly format the tx meta object (non EIP-1559)', () => {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'other',
chainId: '0x5',
time: 1624408066355,
metamaskNetworkId: '5',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
};
const expectedResult = {
accessList: null,
blockHash: null,
blockNumber: null,
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
gasPrice: '0x77359400',
nonce: '0x4b',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
transactionIndex: null,
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
v: '0x29',
value: '0x0',
};
const result = formatTxMetaForRpcResult(txMeta);
expect(result).toStrictEqual(expectedResult);
});
});
});

View File

@ -4,7 +4,7 @@ import { txMetaStub } from '../../../../test/stub/tx-meta-stub';
import {
createPendingNonceMiddleware,
createPendingTxMiddleware,
} from './middleware/pending';
} from './pending';
describe('PendingNonceMiddleware', () => {
describe('#createPendingNonceMiddleware', () => {

View File

@ -15,6 +15,7 @@ import {
PLATFORM_BRAVE,
} from '../../../shared/constants/app';
import { stripHexPrefix } from '../../../shared/modules/hexstring-utils';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
/**
* @see {@link getEnvironmentType}
@ -253,3 +254,48 @@ export function addUrlProtocolPrefix(urlString) {
}
return urlString;
}
export function formatTxMetaForRpcResult(txMeta) {
const { r, s, v, hash, txReceipt, txParams } = txMeta;
const {
to,
data,
nonce,
gas,
from,
value,
gasPrice,
accessList,
maxFeePerGas,
maxPriorityFeePerGas,
} = txParams;
const formattedTxMeta = {
v,
r,
s,
to,
gas,
from,
hash,
nonce,
input: data || '0x',
value: value || '0x0',
accessList: accessList || null,
blockHash: txReceipt?.blockHash || null,
blockNumber: txReceipt?.blockNumber || null,
transactionIndex: txReceipt?.transactionIndex || null,
};
if (maxFeePerGas && maxPriorityFeePerGas) {
formattedTxMeta.gasPrice = maxFeePerGas;
formattedTxMeta.maxFeePerGas = maxFeePerGas;
formattedTxMeta.maxPriorityFeePerGas = maxPriorityFeePerGas;
formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
} else {
formattedTxMeta.gasPrice = gasPrice;
formattedTxMeta.type = TRANSACTION_ENVELOPE_TYPES.LEGACY;
}
return formattedTxMeta;
}

View File

@ -9,7 +9,17 @@ import {
PLATFORM_CHROME,
PLATFORM_EDGE,
} from '../../../shared/constants/app';
import { deferredPromise, getEnvironmentType, getPlatform } from './util';
import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
TRANSACTION_ENVELOPE_TYPES,
} from '../../../shared/constants/transaction';
import {
deferredPromise,
getEnvironmentType,
getPlatform,
formatTxMetaForRpcResult,
} from './util';
describe('app utils', () => {
describe('getEnvironmentType', () => {
@ -208,4 +218,95 @@ describe('app utils', () => {
await expect(promise).resolves.toBe('test');
});
});
describe('formatTxMetaForRpcResult', () => {
it('should correctly format the tx meta object (EIP-1559)', () => {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'other',
chainId: '0x5',
time: 1624408066355,
metamaskNetworkId: '5',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
};
const expectedResult = {
accessList: null,
blockHash: null,
blockNumber: null,
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
gasPrice: '0x77359400',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
nonce: '0x4b',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
transactionIndex: null,
type: '0x2',
v: '0x29',
value: '0x0',
};
const result = formatTxMetaForRpcResult(txMeta);
expect(result).toStrictEqual(expectedResult);
});
it('should correctly format the tx meta object (non EIP-1559)', () => {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'other',
chainId: '0x5',
time: 1624408066355,
metamaskNetworkId: '5',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
v: '0x29',
};
const expectedResult = {
accessList: null,
blockHash: null,
blockNumber: null,
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
gas: '0x7b0d',
hash: '0x4bcb6cd6b182209585f8ad140260ddb35c81a575dd40f508d9767e652a9f60e7',
input: '0x',
gasPrice: '0x77359400',
nonce: '0x4b',
r: '0x4c3111e42ed5eec3dcecba1e234700f387e8693c373c61c3e54a762a26f1570e',
s: '0x18bfc4eeb7ebcfacc3bd59ea100a6834ea3265e65945dbec69aa2a06564fafff',
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
transactionIndex: null,
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
v: '0x29',
value: '0x0',
};
const result = formatTxMetaForRpcResult(txMeta);
expect(result).toStrictEqual(expectedResult);
});
});
});

View File

@ -146,6 +146,7 @@ import MetaMetricsController from './controllers/metametrics';
import { segment } from './lib/segment';
import createMetaRPCHandler from './lib/createMetaRPCHandler';
import { previousValueComparator } from './lib/util';
import createMetamaskMiddleware from './lib/createMetamaskMiddleware';
import {
CaveatMutatorFactories,
@ -246,9 +247,7 @@ export default class MetamaskController extends EventEmitter {
state: initState.NetworkController,
infuraProjectId: opts.infuraProjectId,
});
// now we can initialize the RPC provider, which other controllers require
this.initializeProvider();
this.networkController.initializeProvider();
this.provider =
this.networkController.getProviderAndBlockTracker().provider;
this.blockTracker =
@ -1050,6 +1049,48 @@ export default class MetamaskController extends EventEmitter {
this.messageManager.clearUnapproved();
});
this.metamaskMiddleware = createMetamaskMiddleware({
static: {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
version,
// account mgmt
getAccounts: async (
{ origin: innerOrigin },
{ suppressUnauthorizedError = true } = {},
) => {
if (innerOrigin === ORIGIN_METAMASK) {
const selectedAddress =
this.preferencesController.getSelectedAddress();
return selectedAddress ? [selectedAddress] : [];
} else if (this.isUnlocked()) {
return await this.getPermittedAccounts(innerOrigin, {
suppressUnauthorizedError,
});
}
return []; // changing this is a breaking change
},
// tx signing
processTransaction: this.newUnapprovedTransaction.bind(this),
// msg signing
processEthSignMessage: this.newUnsignedMessage.bind(this),
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
getPendingNonce: this.getPendingNonce.bind(this),
getPendingTransactionByHash: (hash) =>
this.txController.getTransactions({
searchCriteria: {
hash,
status: TRANSACTION_STATUSES.SUBMITTED,
},
})[0],
});
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => this._onStateUpdate(memState));
@ -1442,57 +1483,6 @@ export default class MetamaskController extends EventEmitter {
///: END:ONLY_INCLUDE_IN
}
/**
* Constructor helper: initialize a provider.
*/
initializeProvider() {
const version = this.platform.getVersion();
const providerOpts = {
static: {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
version,
// account mgmt
getAccounts: async (
{ origin },
{ suppressUnauthorizedError = true } = {},
) => {
if (origin === ORIGIN_METAMASK) {
const selectedAddress =
this.preferencesController.getSelectedAddress();
return selectedAddress ? [selectedAddress] : [];
} else if (this.isUnlocked()) {
return await this.getPermittedAccounts(origin, {
suppressUnauthorizedError,
});
}
return []; // changing this is a breaking change
},
// tx signing
processTransaction: this.newUnapprovedTransaction.bind(this),
// msg signing
processEthSignMessage: this.newUnsignedMessage.bind(this),
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
getPendingNonce: this.getPendingNonce.bind(this),
getPendingTransactionByHash: (hash) =>
this.txController.getTransactions({
searchCriteria: {
hash,
status: TRANSACTION_STATUSES.SUBMITTED,
},
})[0],
};
const providerProxy =
this.networkController.initializeProvider(providerOpts);
return providerProxy;
}
/**
* TODO:LegacyProvider: Delete
* Constructor helper: initialize a public config store.
@ -3919,6 +3909,8 @@ export default class MetamaskController extends EventEmitter {
);
}
engine.push(this.metamaskMiddleware);
// forward to metamask primary provider
engine.push(providerAsMiddleware(provider));
return engine;

View File

@ -190,21 +190,6 @@ describe('MetaMaskController', function () {
});
});
describe('#getAccounts', function () {
it('returns first address when dapp calls web3.eth.getAccounts', async function () {
const password = 'a-fake-password';
await metamaskController.createNewVaultAndRestore(password, TEST_SEED);
metamaskController.networkController._baseProviderParams.getAccounts(
(err, res) => {
assert.ifError(err);
assert.equal(res.length, 1);
assert.equal(res[0], '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc');
},
);
});
});
describe('#importAccountWithStrategy', function () {
const importPrivkey =
'4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553';