Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove block range limit in eth_getLogs when filtering with a single address #2236

Merged
merged 11 commits into from
Mar 28, 2024
3 changes: 2 additions & 1 deletion docs/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,8 @@
},
{
"name": "eth_getLogs",
"summary": "Returns an array of all logs matching filter with given id.",
"summary": "Returns an array of all logs matching a given filter object.",
"description": "The block range filter, _i.e._, `fromBlock` and `toBlock` arguments, cannot be larger than `ETH_GET_LOGS_BLOCK_RANGE_LIMIT` (defaults to `1000`). However, when `address` represents a single address, either a `string` or an array with a single element, this restriction is lifted.",
"params": [
{
"name": "Filter",
Expand Down
2 changes: 1 addition & 1 deletion packages/relay/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export interface Eth {
blockHash: string | null,
fromBlock: string | null,
toBlock: string | null,
address: string | null,
address: string | string[] | null,
topics: any[] | null,
requestId?: string,
): Promise<Log[]>;
Expand Down
2 changes: 1 addition & 1 deletion packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,7 @@ export class EthImpl implements Eth {
blockHash: string | null,
fromBlock: string | 'latest',
toBlock: string | 'latest',
address: string | [string] | null,
address: string | string[] | null,
topics: any[] | null,
requestIdPrefix?: string,
): Promise<Log[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,8 @@ export class CommonService implements ICommonService {
fromBlock: string,
toBlock: string,
requestIdPrefix?: string,
address?: string | string[] | null,
) {
const blockRangeLimit =
process.env.TEST === 'true'
? constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT
: Number(process.env.ETH_GET_LOGS_BLOCK_RANGE_LIMIT);

if (this.blockTagIsLatestOrPending(toBlock)) {
toBlock = CommonService.blockLatest;
}
Expand Down Expand Up @@ -141,7 +137,16 @@ export class CommonService implements ICommonService {

if (fromBlockNum > toBlockNum) {
return false;
} else if (toBlockNum - fromBlockNum > blockRangeLimit) {
}

const blockRangeLimit =
process.env.TEST === 'true'
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
? constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT
: Number(process.env.ETH_GET_LOGS_BLOCK_RANGE_LIMIT);
const isSingleAddress = Array.isArray(address)
? address.length === 1
: typeof address === 'string' && address !== '';
if (!isSingleAddress && toBlockNum - fromBlockNum > blockRangeLimit) {
throw predefined.RANGE_TOO_LARGE(blockRangeLimit);
}
}
Expand Down Expand Up @@ -268,7 +273,7 @@ export class CommonService implements ICommonService {
}
}

public async getLogsByAddress(address: string | [string], params: any, requestIdPrefix) {
public async getLogsByAddress(address: string | string[], params: any, requestIdPrefix) {
const addresses = Array.isArray(address) ? address : [address];
const logPromises = addresses.map((addr) =>
this.mirrorNodeClient.getContractResultsLogsByAddress(addr, params, undefined, requestIdPrefix),
Expand All @@ -283,7 +288,7 @@ export class CommonService implements ICommonService {
return logs;
}

public async getLogsWithParams(address: string | [string] | null, params, requestIdPrefix?: string): Promise<Log[]> {
public async getLogsWithParams(address: string | string[] | null, params, requestIdPrefix?: string): Promise<Log[]> {
const EMPTY_RESPONSE = [];

let logResults;
Expand Down Expand Up @@ -321,7 +326,7 @@ export class CommonService implements ICommonService {
blockHash: string | null,
fromBlock: string | 'latest',
toBlock: string | 'latest',
address: string | [string] | null,
address: string | string[] | null,
topics: any[] | null,
requestIdPrefix?: string,
): Promise<Log[]> {
Expand All @@ -332,7 +337,9 @@ export class CommonService implements ICommonService {
if (!(await this.validateBlockHashAndAddTimestampToParams(params, blockHash, requestIdPrefix))) {
return EMPTY_RESPONSE;
}
} else if (!(await this.validateBlockRangeAndAddTimestampToParams(params, fromBlock, toBlock, requestIdPrefix))) {
} else if (
!(await this.validateBlockRangeAndAddTimestampToParams(params, fromBlock, toBlock, requestIdPrefix, address))
) {
return EMPTY_RESPONSE;
}

Expand Down
83 changes: 62 additions & 21 deletions packages/relay/tests/lib/eth/eth_getLogs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,45 @@ describe('@ethGetLogs using MirrorNode', async function () {
expectLogData3(result[2]);
});

[CONTRACT_ADDRESS_1, [CONTRACT_ADDRESS_1]].forEach((address) => {
it(`single address filter \`${JSON.stringify(address)}\` and large block range`, async function () {
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
const filteredLogs = {
logs: [DEFAULT_LOGS.logs[0], DEFAULT_LOGS.logs[1], DEFAULT_LOGS.logs[2]],
};
restMock.onGet(CONTRACTS_LOGS_WITH_FILTER).reply(200, filteredLogs);
for (const log of filteredLogs.logs) {
restMock.onGet(`contracts/${log.address}`).reply(200, DEFAULT_CONTRACT);
}

const fromBlock = {
...DEFAULT_BLOCK,
number: 1,
};
const toBlock = {
...DEFAULT_BLOCK,
number: 1003,
};

const blockBeyondMaximumRange = {
...DEFAULT_BLOCK,
number: 1007,
};

restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [blockBeyondMaximumRange] });
restMock.onGet('blocks/1').reply(200, fromBlock);
restMock.onGet('blocks/1003').reply(200, toBlock);

const result = await ethImpl.getLogs(null, '0x1', '0x3eb', address, null);

expect(result).to.exist;

expect(result.length).to.eq(3);
expectLogData1(result[0]);
expectLogData2(result[1]);
expectLogData3(result[2]);
});
});

it('multiple addresses filter', async function () {
const filteredLogsAddress1 = {
logs: [DEFAULT_LOGS.logs[0], DEFAULT_LOGS.logs[1], DEFAULT_LOGS.logs[2]],
Expand Down Expand Up @@ -439,27 +478,29 @@ describe('@ethGetLogs using MirrorNode', async function () {
expectLogData1(result[0]);
});

it('when block range is too large', async function () {
const fromBlock = {
...DEFAULT_BLOCK,
number: 1,
};
const toBlock = {
...DEFAULT_BLOCK,
number: 1003,
};

const blockBeyondMaximumRange = {
...DEFAULT_BLOCK,
number: 1007,
};

restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [blockBeyondMaximumRange] });
restMock.onGet('blocks/1').reply(200, fromBlock);
restMock.onGet('blocks/1003').reply(200, toBlock);

await ethGetLogsFailing(ethImpl, [null, '0x1', '0x3eb', null, null], (error) => {
expect(error.message).to.equal('Exceeded maximum block range: 1000');
[null, [], [CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2]].forEach((address) => {
it(`when block range is too large with address \`${JSON.stringify(address)}\``, async function () {
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
const fromBlock = {
...DEFAULT_BLOCK,
number: 1,
};
const toBlock = {
...DEFAULT_BLOCK,
number: 1003,
};

const blockBeyondMaximumRange = {
...DEFAULT_BLOCK,
number: 1007,
};

restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, { blocks: [blockBeyondMaximumRange] });
restMock.onGet('blocks/1').reply(200, fromBlock);
restMock.onGet('blocks/1003').reply(200, toBlock);

await ethGetLogsFailing(ethImpl, [null, '0x1', '0x3eb', address, null], (error) => {
expect(error.message).to.equal('Exceeded maximum block range: 1000');
});
});
});

Expand Down
25 changes: 25 additions & 0 deletions packages/server/tests/acceptance/rpc_batch1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,31 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
}
});

it('should be able to use `address` param with a large block range', async () => {
const blockRangeLimit = constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT;
constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT = 10;
try {
//when we pass only address, it defaults to the latest block
const logs = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_LOGS,
[
{
fromBlock: numberTo0x(latestBlock - constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT - 1),
address: contractAddress,
},
],
requestId,
);
expect(logs.length).to.be.greaterThan(0);

for (const i in logs) {
expect(logs[i].address).to.equal(contractAddress);
}
} finally {
constants.DEFAULT_ETH_GET_LOGS_BLOCK_RANGE_LIMIT = blockRangeLimit;
}
});

it('should be able to use `address` param with multiple addresses', async () => {
const logs = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_LOGS,
Expand Down
Loading