Skip to content

Commit

Permalink
Add initial provider API tests for Infura client
Browse files Browse the repository at this point in the history
We are working on migrating the extension to a unified network
controller, but before we do so we want to extract some of the existing
pieces, specifically `createInfuraClient` and `createJsonRpcClient`,
which provide the majority of the behavior exhibited within the provider
API that the existing NetworkController exposes. This necessitates that
we understand and test that behavior as a whole.

With that in mind, this commit starts with the Infura-specific network
client and adds some initial functional tests for `createInfuraClient`,
specifically covering three pieces of middleware provided by
`eth-json-rpc-middleware`: `createNetworkAndChainIdMiddleware`,
`createBlockCacheMiddleware`, and `createBlockRefMiddleware`.

These tests exercise logic that originate from multiple different places
and combine in sometimes surprising ways, and as a result, understanding
the nature of the tests can be tricky. I've tried to explain the logic
(both of the implementation and the tests) via comments. Additionally,
debugging why a certain test is failing is not the most fun thing in the
world, so to aid with this, I've added some logging to the underlying
packages used when a request passes through the middleware stack.
Because some middleware change the request being made, or make new
requests altogether, this greatly helps to peel back the curtain, as
failures from Nock do not supply much meaningful information on their
own. This logging is disabled by default, but can be activated by
setting `DEBUG_PROVIDER_TESTS=1` alongside the `jest` command.

Also note that we are using a custom version of `eth-block-tracker`
which provides a `destroy` method, which we use in tests to properly
ensure that the block tracker is stopped before moving on to the next
step. This change comes from [this PR][1] which has yet to be merged.

[1]: MetaMask/eth-block-tracker#106
  • Loading branch information
mcmire committed Aug 15, 2022
1 parent 9cf358a commit f27dd3c
Show file tree
Hide file tree
Showing 11 changed files with 1,500 additions and 59 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ module.exports = {
'**/__snapshots__/*.snap',
'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/controllers/network/provider-api-tests/*.js',
'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js',
'app/scripts/platforms/*.test.js',
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ notes.txt

# TypeScript
tsout/


app/scripts/controllers/network/network.provider.test.js
233 changes: 233 additions & 0 deletions app/scripts/controllers/network/createInfuraClient.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/**
* @jest-environment node
*/

import { withInfuraClient } from './provider-api-tests/helpers';
import {
testsForRpcMethodThatDoesNotSupportParams,
testsForRpcMethodsThatCheckForBlockHashInResponse,
testsForRpcMethodThatSupportsMultipleParams,
} from './provider-api-tests/shared-tests';

describe('createInfuraClient', () => {
// The first time an RPC method is requested, the latest block number is
// pulled from the block tracker, the RPC method is delegated to Infura, and
// the result is cached under that block number, as long as the result is
// "non-empty". The next time the same request takes place, Infura is not hit,
// and the result is pulled from the cache.
//
// For most RPC methods here, a "non-empty" result is a result that is not
// null, undefined, or a non-standard "nil" value that geth produces.
//
// Some RPC methods are cacheable. Consult the definitive list of cacheable
// RPC methods in `cacheTypeForPayload` within `eth-json-rpc-middleware`.

describe('when the RPC method is eth_chainId', () => {
it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a hex string', async () => {
const chainId = await withInfuraClient(
{ network: 'ropsten' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'eth_chainId',
});
},
);

expect(chainId).toStrictEqual('0x3');
});
});

describe('when the RPC method is net_version', () => {
it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a decimal string', async () => {
const chainId = await withInfuraClient(
{ network: 'ropsten' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);

expect(chainId).toStrictEqual('3');
});
});

// == RPC methods that do not support params
//
// For `eth_getTransactionByHash` and `eth_getTransactionReceipt`, a
// "non-empty" result is not only one that is not null, undefined, or a
// geth-specific "nil", but additionally a result that has a `blockHash` that
// is not 0x0.

describe('eth_blockNumber', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_blockNumber');
});

describe('eth_compileLLL', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_compileLLL');
});

describe('eth_compileSerpent', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_compileSerpent');
});

describe('eth_compileSolidity', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_compileSolidity');
});

describe('eth_estimateGas', () => {
const method = 'eth_estimateGas';
testsForRpcMethodThatDoesNotSupportParams('eth_estimateGas');
});

describe('eth_gasPrice', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_gasPrice');
});

describe('eth_getBlockByHash', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_getBlockByHash');
});

describe('eth_getBlockTransactionCountByHash', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getBlockTransactionCountByHash',
);
});

describe('eth_getBlockTransactionCountByNumber', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getBlockTransactionCountByNumber',
);
});

describe('eth_getCompilers', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_getCompilers');
});

describe('eth_getFilterLogs', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_getFilterLogs');
});

describe('eth_getTransactionByBlockHashAndIndex', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getTransactionByBlockHashAndIndex',
);
});

describe('eth_getTransactionByBlockNumberAndIndex', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getTransactionByBlockNumberAndIndex',
);
});

describe('eth_getTransactionByHash', () => {
testsForRpcMethodsThatCheckForBlockHashInResponse(
'eth_getTransactionByHash',
);
});

describe('eth_getTransactionReceipt', () => {
testsForRpcMethodsThatCheckForBlockHashInResponse(
'eth_getTransactionReceipt',
);
});

describe('eth_getUncleByBlockHashAndIndex', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getUncleByBlockHashAndIndex',
);
});

describe('eth_getUncleByBlockNumberAndIndex', () => {
testsForRpcMethodThatDoesNotSupportParams(
'eth_getUncleByBlockNumberAndIndex',
);
});

describe('eth_getUncleCountByBlockHash', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_getUncleCountByBlockHash');
});

describe('eth_getUncleCountByBlockNumber', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_getUncleCountByBlockNumber');
});

describe('eth_protocolVersion', () => {
testsForRpcMethodThatDoesNotSupportParams('eth_protocolVersion');
});

describe('shh_version', () => {
testsForRpcMethodThatDoesNotSupportParams('shh_version');
});

describe('test_blockCache', () => {
testsForRpcMethodThatDoesNotSupportParams('test_blockCache');
});

describe('test_forkCache', () => {
testsForRpcMethodThatDoesNotSupportParams('test_forkCache');
});

describe('test_permaCache', () => {
testsForRpcMethodThatDoesNotSupportParams('test_permaCache');
});

describe('web3_clientVersion', () => {
testsForRpcMethodThatDoesNotSupportParams('web3_clientVersion');
});

describe('web3_sha3', () => {
testsForRpcMethodThatDoesNotSupportParams('web3_sha3');
});

// == RPC methods that support multiple params (including a "block" param)
//
// RPC methods in this category take a non-empty `params` array, and more
// importantly, one of these items can specify which block the method applies
// to. This block param may either be a tag ("earliest", "latest", or
// "pending"), or a specific block number; or this param may not be
// provided altogether, in which case it defaults to "latest". Also,
// "earliest" is just a synonym for "0x00".
//
// The fact that these methods support arguments affects the caching strategy,
// because if two requests are made with the same method but with different
// arguments, then they will be cached separately. Also, the block param
// changes the caching strategy slightly: if "pending" is specified, then the
// request is never cached.

describe('eth_getBlockByNumber', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_getBlockByNumber', {
numberOfParams: 0,
});
});

describe('eth_getBalance', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_getBalance', {
numberOfParams: 1,
});
});

describe('eth_getCode', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_getCode', {
numberOfParams: 1,
});
});

describe('eth_getTransactionCount', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_getTransactionCount', {
numberOfParams: 1,
});
});

describe('eth_call', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_call', {
numberOfParams: 1,
});
});

describe('eth_getStorageAt', () => {
testsForRpcMethodThatSupportsMultipleParams('eth_getStorageAt', {
numberOfParams: 2,
});
});
});
Loading

0 comments on commit f27dd3c

Please sign in to comment.