Skip to content

Commit

Permalink
Reduce load on Infura in gas estimate fallback (#705)
Browse files Browse the repository at this point in the history
Usually we use the MetaSwap API for gas fee estimates, but if that is
down for some reason, we fall back to hitting Infura directly.

A little while ago we were working on some UI improvements to the
extension around gas fees, and to support this some changes to the
MetaSwap API were made so that it requests the last 20,000 blocks from
the network and uses their data to obtain a baseline for base fees so
that we can display a network stability/congestion meter.

We made the same changes to the gas fee estimation fallback logic.
**This ended up being a very bad decision.** Because none of the the
requests to Infura that the fallback logic makes are cached, when the
MetaSwap API goes down, the number of requests made to Infura
skyrockets and Infura suffers performance-wise. This problem already
existed, but has been severely exacerbated by the new changes to the
fallback logic. In fact, recently the Infura team had to disable the
`eth_feeHistory` endpoint because some of the Ethereum nodes crashed
under all of the load.

To address this, this commit updates the part of the fallback logic
which is responsible for calculating network congestion to always return
0.5 instead of making the aforementioned 20,000-block request. This
means that the network stability meter in the extension will stay
static, but that is absolutely preferable to blasting Infura.
  • Loading branch information
mcmire committed Mar 4, 2022
1 parent 9c5b75a commit ee93293
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 29 deletions.
10 changes: 1 addition & 9 deletions src/gas/fetchGasEstimatesViaEthFeeHistory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@ describe('fetchGasEstimatesViaEthFeeHistory', () => {

it('calculates target fees for low, medium, and high transaction priority levels, as well as the network congestion level', async () => {
const blocksByDataset = {
longRange: [
{
number: new BN(1),
baseFeePerGas: new BN(1),
gasUsedRatio: 1,
priorityFeesByPercentile: {},
},
],
mediumRange: [
{
number: new BN(2),
Expand Down Expand Up @@ -187,7 +179,7 @@ describe('fetchGasEstimatesViaEthFeeHistory', () => {
.mockReturnValue(priorityFeeTrend);

when(mockedCalculateNetworkCongestion)
.calledWith(blocksByDataset.longRange)
.calledWith([])
.mockReturnValue(networkCongestion);

const gasFeeEstimates = await fetchGasEstimatesViaEthFeeHistory(ethQuery);
Expand Down
4 changes: 1 addition & 3 deletions src/gas/fetchGasEstimatesViaEthFeeHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ export default async function fetchGasEstimatesViaEthFeeHistory(
blocksByDataset.mediumRange,
);
const priorityFeeTrend = calculatePriorityFeeTrend(blocksByDataset.tinyRange);
const networkCongestion = calculateNetworkCongestion(
blocksByDataset.longRange,
);
const networkCongestion = calculateNetworkCongestion([]);

return {
...levelSpecificEstimates,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { BN } from 'ethereumjs-util';
import { mocked } from 'ts-jest/utils';
import { when } from 'jest-when';
import fetchBlockFeeHistory, { FeeHistoryBlock } from '../fetchBlockFeeHistory';
import { EthQuery } from './types';
import BlockFeeHistoryDatasetFetcher from './BlockFeeHistoryDatasetFetcher';

jest.mock('../fetchBlockFeeHistory');

const mockedFetchBlockFeeHistory = mocked(fetchBlockFeeHistory, true);

describe('BlockFeeHistoryDatasetFetcher', () => {
let fakeEthQuery: EthQuery;
let fakeEndBlockNumber: BN;
let fakeBlocks: FeeHistoryBlock<never>[];

beforeEach(() => {
fakeEthQuery = {
async getBlockByNumber() {
return { number: new BN(1), baseFeePerGas: new BN(1) };
},
};
fakeEndBlockNumber = new BN(1);
fakeBlocks = [
{
number: new BN(1),
baseFeePerGas: new BN(1),
gasUsedRatio: 1,
},
];
});

describe('forMediumRange', () => {
it('returns 200 blocks along with the 10th and 95th reward percentiles (excluding the next block)', async () => {
when(mockedFetchBlockFeeHistory)
.calledWith({
ethQuery: fakeEthQuery,
endBlock: fakeEndBlockNumber,
numberOfBlocks: 200,
percentiles: [10, 95],
})
.mockResolvedValue(fakeBlocks);

const fetcher = new BlockFeeHistoryDatasetFetcher({
ethQuery: fakeEthQuery,
endBlockNumber: fakeEndBlockNumber,
});

expect(await fetcher.forMediumRange()).toStrictEqual(fakeBlocks);
});
});

describe('forSmallRange', () => {
it('returns 5 blocks along with the 10th, 20th, and 30th reward percentiles (excluding the next block)', async () => {
when(mockedFetchBlockFeeHistory)
.calledWith({
ethQuery: fakeEthQuery,
endBlock: fakeEndBlockNumber,
numberOfBlocks: 5,
percentiles: [10, 20, 30],
})
.mockResolvedValue(fakeBlocks);

const fetcher = new BlockFeeHistoryDatasetFetcher({
ethQuery: fakeEthQuery,
endBlockNumber: fakeEndBlockNumber,
});

expect(await fetcher.forSmallRange()).toStrictEqual(fakeBlocks);
});
});

describe('forTinyRange', () => {
it('returns 2 blocks along with the 50th reward percentiles (excluding the next block)', async () => {
when(mockedFetchBlockFeeHistory)
.calledWith({
ethQuery: fakeEthQuery,
endBlock: fakeEndBlockNumber,
numberOfBlocks: 2,
percentiles: [50],
})
.mockResolvedValue(fakeBlocks);

const fetcher = new BlockFeeHistoryDatasetFetcher({
ethQuery: fakeEthQuery,
endBlockNumber: fakeEndBlockNumber,
});

expect(await fetcher.forTinyRange()).toStrictEqual(fakeBlocks);
});
});

describe('forLatestWithNextBlock', () => {
it('returns 1 block along with the 10th and 90th reward percentiles (including the next block)', async () => {
when(mockedFetchBlockFeeHistory)
.calledWith({
ethQuery: fakeEthQuery,
endBlock: fakeEndBlockNumber,
numberOfBlocks: 1,
includeNextBlock: true,
percentiles: [10, 95],
})
.mockResolvedValue(fakeBlocks);

const fetcher = new BlockFeeHistoryDatasetFetcher({
ethQuery: fakeEthQuery,
endBlockNumber: fakeEndBlockNumber,
});

expect(await fetcher.forLatestWithNextBlock()).toStrictEqual(fakeBlocks);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ export default class BlockFeeHistoryDatasetFetcher {

async forAll() {
const [
longRange,
mediumRange,
smallRange,
tinyRange,
latestWithNextBlock,
] = await Promise.all([
this.forLongRange(),
this.forMediumRange(),
this.forSmallRange(),
this.forTinyRange(),
Expand All @@ -42,7 +40,6 @@ export default class BlockFeeHistoryDatasetFetcher {
>[];

return {
longRange,
mediumRange,
smallRange,
tinyRange,
Expand All @@ -51,10 +48,6 @@ export default class BlockFeeHistoryDatasetFetcher {
};
}

forLongRange() {
return this.fetchExcludingNextBlock({ numberOfBlocks: 20_000 });
}

forMediumRange() {
return this.fetchExcludingNextBlock({
numberOfBlocks: 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ describe('calculateNetworkCongestion', () => {
]);
expect(networkCongestion).toStrictEqual(0.5);
});

it('returns 0.5 when given an empty array', () => {
const networkCongestion = calculateNetworkCongestion([]);
expect(networkCongestion).toStrictEqual(0.5);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import { FeeHistoryBlock } from '../fetchBlockFeeHistory';
export default function fetchNetworkCongestionLevel(
blocks: FeeHistoryBlock<never>[],
): number {
const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas;
const sortedBaseFeesPerGas = blocks
.map((block) => block.baseFeePerGas)
.sort((a, b) => a.cmp(b));
const indexOfBaseFeeNearestToLatest = sortedBaseFeesPerGas.findIndex(
(baseFeePerGas) => baseFeePerGas.gte(latestBaseFeePerGas),
);
return indexOfBaseFeeNearestToLatest !== -1
? indexOfBaseFeeNearestToLatest / (blocks.length - 1)
: 0;
if (blocks.length > 0) {
const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas;
const sortedBaseFeesPerGas = blocks
.map((block) => block.baseFeePerGas)
.sort((a, b) => a.cmp(b));
const indexOfBaseFeeNearestToLatest = sortedBaseFeesPerGas.findIndex(
(baseFeePerGas) => baseFeePerGas.gte(latestBaseFeePerGas),
);
return indexOfBaseFeeNearestToLatest !== -1
? indexOfBaseFeeNearestToLatest / (blocks.length - 1)
: 0;
}
return 0.5;
}

0 comments on commit ee93293

Please sign in to comment.