From 10c8581f46f94c9b193eab66c550533aa0c728a6 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 18 Sep 2024 12:50:48 +0530 Subject: [PATCH] Support topics filter in eth_getLogs RPC API --- packages/util/src/eth-rpc-handlers.ts | 109 ++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 15 deletions(-) diff --git a/packages/util/src/eth-rpc-handlers.ts b/packages/util/src/eth-rpc-handlers.ts index 7cfbb16a..df72a518 100644 --- a/packages/util/src/eth-rpc-handlers.ts +++ b/packages/util/src/eth-rpc-handlers.ts @@ -18,8 +18,9 @@ const ERROR_CONTRACT_METHOD_NOT_FOUND = 'Contract method not found'; const ERROR_METHOD_NOT_IMPLEMENTED = 'Method not implemented'; const ERROR_INVALID_BLOCK_TAG = 'Invalid block tag'; const ERROR_INVALID_BLOCK_HASH = 'Invalid block hash'; +const ERROR_INVALID_CONTRACT_ADDRESS = 'Invalid contract address'; +const ERROR_INVALID_TOPICS = 'Invalid topics'; const ERROR_BLOCK_NOT_FOUND = 'Block not found'; -const ERROR_TOPICS_FILTER_NOT_SUPPORTED = 'Topics filter not supported'; const ERROR_LIMIT_EXCEEDED = 'Query results exceeds limit'; const DEFAULT_BLOCK_TAG = 'latest'; @@ -114,20 +115,14 @@ export const createEthRPCHandlers = async ( // Parse arg params into where options const where: FindConditions = {}; - // TODO: Support topics filter - if (params.topics) { - throw new ErrorWithCode(CODE_INVALID_PARAMS, ERROR_TOPICS_FILTER_NOT_SUPPORTED); - } - // Address filter, address or a list of addresses if (params.address) { - if (Array.isArray(params.address)) { - if (params.address.length > 0) { - where.contract = In(params.address); - } - } else { - where.contract = Equal(params.address); - } + buildAddressFilter(params.address, where); + } + + // Topics filter + if (params.topics) { + buildTopicsFilter(params.topics, where); } // Block hash takes precedence over fromBlock / toBlock if provided @@ -229,10 +224,94 @@ const parseEthGetLogsBlockTag = async (indexer: IndexerInterface, blockTag: stri throw new ErrorWithCode(CODE_INVALID_PARAMS, ERROR_INVALID_BLOCK_TAG); }; +const buildAddressFilter = (address: any, where: FindConditions): void => { + if (Array.isArray(address)) { + // Validate input addresses + address.forEach((add: string) => { + if (!utils.isHexString(add, 20)) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, ERROR_INVALID_CONTRACT_ADDRESS); + } + }); + + if (address.length > 0) { + where.contract = In(address); + } + } else { + // Validate input address + if (!utils.isHexString(address, 20)) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, ERROR_INVALID_CONTRACT_ADDRESS); + } + + where.contract = Equal(address); + } +}; + +const buildTopicsFilter = (topics: any, where: FindConditions): void => { + // Check that topics is an array of size <= 4 + if (!Array.isArray(topics)) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, ERROR_INVALID_TOPICS); + } + + if (topics.length > 4) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, `${ERROR_INVALID_TOPICS}: exceeds max topics`); + } + + const topicsFilterLength = topics.length; + + if (topicsFilterLength > 0) { + addTopicCondition(topics[0], 'topic0', where); + } + + if (topicsFilterLength > 1) { + addTopicCondition(topics[1], 'topic1', where); + } + + if (topicsFilterLength > 2) { + addTopicCondition(topics[2], 'topic2', where); + } + + if (topicsFilterLength > 3) { + addTopicCondition(topics[3], 'topic3', where); + } +}; + +const addTopicCondition = ( + topicFilter: string[] | string, + topicIndex: 'topic0' | 'topic1' | 'topic2' | 'topic3', + where: FindConditions +): any => { + if (Array.isArray(topicFilter)) { + // Validate input topics + topicFilter.forEach((topic: string) => { + if (!utils.isHexString(topic, 32)) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, `${ERROR_INVALID_TOPICS}: expected hex string of size 32 for ${topicIndex}`); + } + }); + + if (topicFilter.length > 0) { + where[topicIndex] = In(topicFilter); + } + } else { + // Validate input address + if (!utils.isHexString(topicFilter, 32)) { + throw new ErrorWithCode(CODE_INVALID_PARAMS, `${ERROR_INVALID_TOPICS}: expected hex string of size 32 for ${topicIndex}`); + } + + where[topicIndex] = Equal(topicFilter); + } +}; + const transformEventsToLogs = async (events: Array): Promise => { return events.map(event => { const parsedExtraInfo = JSON.parse(event.extraInfo); + const topics: string[] = []; + [event.topic0, event.topic1, event.topic2, event.topic3].forEach(topic => { + if (topic) { + topics.push(topic); + } + }); + return { address: event.contract.toLowerCase(), blockHash: event.block.blockHash, @@ -240,8 +319,8 @@ const transformEventsToLogs = async (events: Array): Promise