diff --git a/alm/Dockerfile b/alm/Dockerfile index 66c11784..db2810c4 100644 --- a/alm/Dockerfile +++ b/alm/Dockerfile @@ -19,7 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build COPY commons/package.json ./commons/ COPY alm/package.json ./alm/ COPY yarn.lock . -RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production +RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile COPY ./commons ./commons COPY ./alm ./alm diff --git a/alm/package.json b/alm/package.json index 41ede67e..7fed33df 100644 --- a/alm/package.json +++ b/alm/package.json @@ -58,6 +58,7 @@ ] }, "devDependencies": { - "eslint-plugin-prettier": "^3.1.3" + "eslint-plugin-prettier": "^3.1.3", + "node-fetch": "^2.6.1" } } diff --git a/alm/scripts/createSnapshots.js b/alm/scripts/createSnapshots.js index 604c72d4..b57e8953 100644 --- a/alm/scripts/createSnapshots.js +++ b/alm/scripts/createSnapshots.js @@ -3,6 +3,8 @@ const { BRIDGE_VALIDATORS_ABI, HOME_AMB_ABI } = require('commons') const path = require('path') require('dotenv').config() const Web3 = require('web3') +const fetch = require('node-fetch') +const { URL } = require('url') const fs = require('fs') @@ -10,7 +12,9 @@ const { COMMON_HOME_RPC_URL, COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_RPC_URL, - COMMON_FOREIGN_BRIDGE_ADDRESS + COMMON_FOREIGN_BRIDGE_ADDRESS, + ALM_FOREIGN_EXPLORER_API, + ALM_HOME_EXPLORER_API } = process.env const generateSnapshot = async (side, url, bridgeAddress) => { @@ -19,6 +23,31 @@ const generateSnapshot = async (side, url, bridgeAddress) => { const snapshot = {} const web3 = new Web3(new Web3.providers.HttpProvider(url)) + const api = side === 'home' ? ALM_HOME_EXPLORER_API : ALM_FOREIGN_EXPLORER_API + + const getPastEventsWithFallback = (contract, eventName, options) => + contract.getPastEvents(eventName, options).catch(async e => { + if (e.message.includes('exceed maximum block range')) { + const abi = contract.options.jsonInterface.find(abi => abi.type === 'event' && abi.name === eventName) + + const url = new URL(api) + url.searchParams.append('module', 'logs') + url.searchParams.append('action', 'getLogs') + url.searchParams.append('address', contract.options.address) + url.searchParams.append('fromBlock', options.fromBlock) + url.searchParams.append('toBlock', options.toBlock || 'latest') + url.searchParams.append('topic0', web3.eth.abi.encodeEventSignature(abi)) + + const logs = await fetch(url).then(res => res.json()) + + return logs.result.map(log => ({ + transactionHash: log.transactionHash, + blockNumber: parseInt(log.blockNumber.slice(2), 16), + returnValues: web3.eth.abi.decodeLog(abi.inputs, log.data, log.topics.slice(1)) + })) + } + throw e + }) const currentBlockNumber = await web3.eth.getBlockNumber() snapshot.snapshotBlockNumber = currentBlockNumber @@ -29,10 +58,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => { const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress) // Save RequiredBlockConfirmationChanged events - let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', { - fromBlock: 0, - toBlock: currentBlockNumber - }) + let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback( + bridgeContract, + 'RequiredBlockConfirmationChanged', + { + fromBlock: 0, + toBlock: currentBlockNumber + } + ) // In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB // manually generate an event for this. Example Sokol - Kovan bridge @@ -59,10 +92,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => { const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress) // Save RequiredSignaturesChanged events - const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', { - fromBlock: 0, - toBlock: currentBlockNumber - }) + const RequiredSignaturesChangedEvents = await getPastEventsWithFallback( + validatorContract, + 'RequiredSignaturesChanged', + { + fromBlock: 0, + toBlock: currentBlockNumber + } + ) snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({ blockNumber: e.blockNumber, returnValues: { @@ -71,7 +108,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => { })) // Save ValidatorAdded events - const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', { + const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', { fromBlock: 0, toBlock: currentBlockNumber }) @@ -85,7 +122,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => { })) // Save ValidatorRemoved events - const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', { + const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', { fromBlock: 0, toBlock: currentBlockNumber }) diff --git a/alm/src/components/ManualExecutionButton.tsx b/alm/src/components/ManualExecutionButton.tsx index 5a7d1f90..3034013d 100644 --- a/alm/src/components/ManualExecutionButton.tsx +++ b/alm/src/components/ManualExecutionButton.tsx @@ -6,6 +6,7 @@ import { DOUBLE_EXECUTION_ATTEMPT_ERROR, EXECUTION_FAILED_ERROR, EXECUTION_OUT_OF_GAS_ERROR, + FOREIGN_EXPLORER_API, INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' @@ -92,7 +93,13 @@ export const ManualExecutionButton = ({ }) .on('error', async (e: Error, receipt: TransactionReceipt) => { if (e.message.includes('Transaction has been reverted by the EVM')) { - const successExecutionData = await getSuccessExecutionData(bridge, 'RelayedMessage', library, messageId) + const successExecutionData = await getSuccessExecutionData( + bridge, + 'RelayedMessage', + library, + messageId, + FOREIGN_EXPLORER_API + ) if (successExecutionData) { setExecutionData(successExecutionData) setError(DOUBLE_EXECUTION_ATTEMPT_ERROR) diff --git a/alm/src/hooks/useBlockConfirmations.ts b/alm/src/hooks/useBlockConfirmations.ts index 470cfc23..b9427e68 100644 --- a/alm/src/hooks/useBlockConfirmations.ts +++ b/alm/src/hooks/useBlockConfirmations.ts @@ -4,6 +4,8 @@ import { useStateProvider } from '../state/StateProvider' import { Contract } from 'web3-eth-contract' import { getRequiredBlockConfirmations } from '../utils/contract' import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' +import Web3 from 'web3' +import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants' export interface UseBlockConfirmationsParams { fromHome: boolean @@ -19,9 +21,11 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio contract: Contract, receipt: TransactionReceipt, setResult: Function, - snapshotProvider: SnapshotProvider + snapshotProvider: SnapshotProvider, + web3: Web3, + api: string ) => { - const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider) + const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider, web3, api) setResult(result) } @@ -29,10 +33,12 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio () => { const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider - if (!bridgeContract || !receipt) return - callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider) + const web3 = fromHome ? home.web3 : foreign.web3 + const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API + if (!bridgeContract || !receipt || !web3) return + callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider, web3, api) }, - [home.bridgeContract, foreign.bridgeContract, receipt, fromHome] + [home.bridgeContract, foreign.bridgeContract, receipt, fromHome, home.web3, foreign.web3] ) return { diff --git a/alm/src/hooks/useValidatorContract.ts b/alm/src/hooks/useValidatorContract.ts index 8557883f..cc232647 100644 --- a/alm/src/hooks/useValidatorContract.ts +++ b/alm/src/hooks/useValidatorContract.ts @@ -6,6 +6,7 @@ import { BRIDGE_VALIDATORS_ABI } from '../abis' import { useStateProvider } from '../state/StateProvider' import { TransactionReceipt } from 'web3-eth' import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' +import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants' export interface useValidatorContractParams { fromHome: boolean @@ -30,10 +31,12 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract contract: Maybe, receipt: TransactionReceipt, setResult: Function, - snapshotProvider: SnapshotProvider + snapshotProvider: SnapshotProvider, + web3: Web3, + api: string ) => { if (!contract) return - const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider) + const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api) setResult(result) } @@ -41,32 +44,35 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract contract: Maybe, receipt: TransactionReceipt, setResult: Function, - snapshotProvider: SnapshotProvider + snapshotProvider: SnapshotProvider, + web3: Web3, + api: string ) => { if (!contract) return - const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider) + const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api) setResult(result) } + const web3 = fromHome ? home.web3 : foreign.web3 + const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API + const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract + const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider + useEffect( () => { - const web3 = fromHome ? home.web3 : foreign.web3 - const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract - if (!web3 || !bridgeContract) return callValidatorContract(bridgeContract, web3, setValidatorContract) }, - [home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome] + [web3, bridgeContract] ) useEffect( () => { - if (!receipt) return - const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider - callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider) - callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider) + if (!web3 || !receipt) return + callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api) + callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api) }, - [validatorContract, receipt, fromHome] + [validatorContract, receipt, web3, snapshotProvider, api] ) return { diff --git a/alm/src/utils/__tests__/contracts.test.ts b/alm/src/utils/__tests__/contracts.test.ts index 7296658d..94bc02b6 100644 --- a/alm/src/utils/__tests__/contracts.test.ts +++ b/alm/src/utils/__tests__/contracts.test.ts @@ -16,7 +16,7 @@ describe('getRequiredBlockConfirmations', () => { test('Should call requiredBlockConfirmations method if no events present', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [] }, methods: methodsBuilder('1') @@ -37,7 +37,7 @@ describe('getRequiredBlockConfirmations', () => { }) test('Should not call to get events if block number was included in the snapshot', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => []), + getPastEvents: jest.fn().mockImplementation(async () => []), methods: methodsBuilder('3') } as unknown) as Contract @@ -64,7 +64,7 @@ describe('getRequiredBlockConfirmations', () => { }) test('Should call to get events if block number was not included in the snapshot', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => [ + getPastEvents: jest.fn().mockImplementation(async () => [ { blockNumber: 9, returnValues: { @@ -102,7 +102,7 @@ describe('getRequiredBlockConfirmations', () => { }) test('Should use the most updated event', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => [ + getPastEvents: jest.fn().mockImplementation(async () => [ { blockNumber: 9, returnValues: { @@ -141,7 +141,7 @@ describe('getRequiredBlockConfirmations', () => { describe('getRequiredSignatures', () => { test('Should not call to get events if block number was included in the snapshot', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => []) + getPastEvents: jest.fn().mockImplementation(async () => []) } as unknown) as Contract const snapshotProvider = ({ @@ -173,7 +173,7 @@ describe('getRequiredSignatures', () => { }) test('Should call to get events if block number is higher than the snapshot block number', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => [ + getPastEvents: jest.fn().mockImplementation(async () => [ { blockNumber: 15, returnValues: { @@ -216,7 +216,7 @@ describe('getRequiredSignatures', () => { }) test('Should use the most updated event before the block number', async () => { const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => [ + getPastEvents: jest.fn().mockImplementation(async () => [ { blockNumber: 15, returnValues: { @@ -270,7 +270,7 @@ describe('getValidatorList', () => { test('Should return the current validator list if no events found', async () => { const currentValidators = [validator1, validator2, validator3] const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => []), + getPastEvents: jest.fn().mockImplementation(async () => []), methods: methodsBuilder(currentValidators) } as unknown) as Contract @@ -301,7 +301,7 @@ describe('getValidatorList', () => { test('If validator was added later from snapshot it should not include it', async () => { const currentValidators = [validator1, validator2, validator3] const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => []), + getPastEvents: jest.fn().mockImplementation(async () => []), methods: methodsBuilder(currentValidators) } as unknown) as Contract @@ -340,7 +340,7 @@ describe('getValidatorList', () => { test('If validator was added later from chain it should not include it', async () => { const currentValidators = [validator1, validator2, validator3] const contract = ({ - getPastEvents: jest.fn().mockImplementation(event => { + getPastEvents: jest.fn().mockImplementation(async event => { if (event === 'ValidatorAdded') { return [ { @@ -385,7 +385,7 @@ describe('getValidatorList', () => { test('If validator was removed later from snapshot it should include it', async () => { const currentValidators = [validator1, validator2] const contract = ({ - getPastEvents: jest.fn().mockImplementation(() => []), + getPastEvents: jest.fn().mockImplementation(async () => []), methods: methodsBuilder(currentValidators) } as unknown) as Contract @@ -424,7 +424,7 @@ describe('getValidatorList', () => { test('If validator was removed later from chain it should include it', async () => { const currentValidators = [validator1, validator2] const contract = ({ - getPastEvents: jest.fn().mockImplementation(event => { + getPastEvents: jest.fn().mockImplementation(async event => { if (event === 'ValidatorRemoved') { return [ { diff --git a/alm/src/utils/__tests__/getFinalizationEvent.test.ts b/alm/src/utils/__tests__/getFinalizationEvent.test.ts index bbce1486..e5eec296 100644 --- a/alm/src/utils/__tests__/getFinalizationEvent.test.ts +++ b/alm/src/utils/__tests__/getFinalizationEvent.test.ts @@ -50,7 +50,7 @@ beforeEach(() => { describe('getFinalizationEvent', () => { test('should get finalization event and not try to get failed or pending transactions', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [event] } } as unknown) as Contract @@ -102,7 +102,7 @@ describe('getFinalizationEvent', () => { }) test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [] } } as unknown) as Contract @@ -147,7 +147,7 @@ describe('getFinalizationEvent', () => { }) test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [] }, options: { @@ -199,7 +199,7 @@ describe('getFinalizationEvent', () => { }) test('should retry to get finalization event and not to try to get failed transaction if pending transactions found if home to foreign transaction', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [] }, options: { @@ -258,7 +258,7 @@ describe('getFinalizationEvent', () => { }) test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => { const contract = ({ - getPastEvents: () => { + getPastEvents: async () => { return [] }, options: { diff --git a/alm/src/utils/contract.ts b/alm/src/utils/contract.ts index f5578c01..7e9824e5 100644 --- a/alm/src/utils/contract.ts +++ b/alm/src/utils/contract.ts @@ -1,18 +1,33 @@ import { Contract } from 'web3-eth-contract' import { EventData } from 'web3-eth-contract' import { SnapshotProvider } from '../services/SnapshotProvider' +import { getLogs } from './explorer' +import Web3 from 'web3' + +const getPastEventsWithFallback = ( + api: string, + web3: Web3 | null, + contract: Contract, + eventName: string, + options: any +) => + contract + .getPastEvents(eventName, options) + .catch(() => (api && web3 ? getLogs(api, web3, contract, eventName, options) : [])) export const getRequiredBlockConfirmations = async ( contract: Contract, blockNumber: number, - snapshotProvider: SnapshotProvider + snapshotProvider: SnapshotProvider, + web3: Web3 | null = null, + api: string = '' ) => { const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber) const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() let contractEvents: EventData[] = [] if (blockNumber > snapshotBlockNumber) { - contractEvents = await contract.getPastEvents('RequiredBlockConfirmationChanged', { + contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredBlockConfirmationChanged', { fromBlock: snapshotBlockNumber + 1, toBlock: blockNumber }) @@ -38,14 +53,16 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali export const getRequiredSignatures = async ( contract: Contract, blockNumber: number, - snapshotProvider: SnapshotProvider + snapshotProvider: SnapshotProvider, + web3: Web3 | null = null, + api: string = '' ) => { const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber) const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() let contractEvents: EventData[] = [] if (blockNumber > snapshotBlockNumber) { - contractEvents = await contract.getPastEvents('RequiredSignaturesChanged', { + contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredSignaturesChanged', { fromBlock: snapshotBlockNumber + 1, toBlock: blockNumber }) @@ -59,7 +76,13 @@ export const getRequiredSignatures = async ( return parseInt(requiredSignatures) } -export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => { +export const getValidatorList = async ( + contract: Contract, + blockNumber: number, + snapshotProvider: SnapshotProvider, + web3: Web3 | null = null, + api: string = '' +) => { const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber) const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber) const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() @@ -67,10 +90,10 @@ export const getValidatorList = async (contract: Contract, blockNumber: number, const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber const [currentList, added, removed] = await Promise.all([ contract.methods.validatorList().call(), - contract.getPastEvents('ValidatorAdded', { + getPastEventsWithFallback(api, web3, contract, 'ValidatorAdded', { fromBlock }), - contract.getPastEvents('ValidatorRemoved', { + getPastEventsWithFallback(api, web3, contract, 'ValidatorRemoved', { fromBlock }) ]) diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index 07293efc..70d604b5 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -7,6 +7,9 @@ import { MAX_TX_SEARCH_BLOCK_RANGE, SUBMIT_SIGNATURE_HASH } from '../config/constants' +import { AbiItem } from 'web3-utils' +import Web3 from 'web3' +import { Contract } from 'web3-eth-contract' export interface APITransaction { timeStamp: string @@ -47,10 +50,15 @@ export interface GetTransactionParams extends GetPendingTransactionParams { } export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => { - const params = `module=account&action=txlist&address=${account}&filterby=from&startblock=${startBlock}&endblock=${endBlock}` - const url = api.includes('blockscout') ? `${api}?${params}` : `${api}&${params}` + const url = new URL(api) + url.searchParams.append('module', 'account') + url.searchParams.append('action', 'txlist') + url.searchParams.append('address', account) + url.searchParams.append('filterby', 'from') + url.searchParams.append('startblock', startBlock.toString()) + url.searchParams.append('endblock', endBlock.toString()) - const result = await fetch(url).then(res => res.json()) + const result = await fetch(url.toString()).then(res => res.json()) if (result.message === 'No transactions found') { return [] @@ -66,10 +74,13 @@ export const fetchPendingTransactions = async ({ if (!api.includes('blockscout')) { return [] } - const url = `${api}?module=account&action=pendingtxlist&address=${account}` + const url = new URL(api) + url.searchParams.append('module', 'account') + url.searchParams.append('action', 'pendingtxlist') + url.searchParams.append('address', account) try { - const result = await fetch(url).then(res => res.json()) + const result = await fetch(url.toString()).then(res => res.json()) if (result.status === '0') { return [] } @@ -85,9 +96,13 @@ export const getClosestBlockByTimestamp = async (api: string, timestamp: number) throw new Error('Blockscout does not support getblocknobytime') } - const url = `${api}&module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` + const url = new URL(api) + url.searchParams.append('module', 'block') + url.searchParams.append('action', 'getblocknobytime') + url.searchParams.append('timestamp', timestamp.toString()) + url.searchParams.append('closest', 'before') - const blockNumber = await fetch(url).then(res => res.json()) + const blockNumber = await fetch(url.toString()).then(res => res.json()) return parseInt(blockNumber.result) } @@ -144,6 +159,41 @@ export const getAccountTransactions = async ({ return transactionsCache[key].transactions } +export const getLogs = async ( + api: string, + web3: Web3, + contract: Contract, + event: string, + options: { fromBlock: number; toBlock: number | 'latest'; topics: (string | null)[] } +) => { + const abi = contract.options.jsonInterface.find((abi: AbiItem) => abi.type === 'event' && abi.name === event)! + + const url = new URL(api) + url.searchParams.append('module', 'logs') + url.searchParams.append('action', 'getLogs') + url.searchParams.append('address', contract.options.address) + url.searchParams.append('fromBlock', options.fromBlock.toString()) + url.searchParams.append('toBlock', options.toBlock.toString() || 'latest') + + const topics = [web3.eth.abi.encodeEventSignature(abi), ...(options.topics || [])] + for (let i = 0; i < topics.length; i++) { + if (topics[i] !== null) { + url.searchParams.append(`topic${i}`, topics[i] as string) + for (let j = 0; j < i; j++) { + url.searchParams.append(`topic${j}_${i}_opr`, 'and') + } + } + } + + const logs = await fetch(url.toString()).then(res => res.json()) + + return logs.result.map((log: any) => ({ + transactionHash: log.transactionHash, + blockNumber: parseInt(log.blockNumber.slice(2), 16), + returnValues: web3.eth.abi.decodeLog(abi.inputs!, log.data, log.topics.slice(1)) + })) +} + const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase() export const getFailedTransactions = async ( diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index 3406007e..2ead8972 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -1,16 +1,45 @@ import { Contract, EventData } from 'web3-eth-contract' import Web3 from 'web3' -import { CACHE_KEY_EXECUTION_FAILED, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { + CACHE_KEY_EXECUTION_FAILED, + FOREIGN_EXPLORER_API, + HOME_EXPLORER_API, + VALIDATOR_CONFIRMATION_STATUS +} from '../config/constants' import { ExecutionData } from '../hooks/useMessageConfirmations' -import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer' +import { + APIPendingTransaction, + APITransaction, + GetTransactionParams, + GetPendingTransactionParams, + getLogs +} from './explorer' import { getBlock, MessageObject } from './web3' import validatorsCache from '../services/ValidatorsCache' import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider' -export const getSuccessExecutionData = async (contract: Contract, eventName: string, web3: Web3, messageId: string) => { +const getPastEventsWithFallback = (api: string, web3: Web3, contract: Contract, eventName: string, options: any) => + contract.getPastEvents(eventName, options).catch( + () => + api + ? getLogs(api, web3, contract, eventName, { + fromBlock: options.fromBlock, + toBlock: options.toBlock, + topics: [null, null, options.filter.messageId] + }) + : [] + ) + +export const getSuccessExecutionData = async ( + contract: Contract, + eventName: string, + web3: Web3, + messageId: string, + api: string = '' +) => { // Since it filters by the message id, only one event will be fetched // so there is no need to limit the range of the block to reduce the network traffic - const events: EventData[] = await contract.getPastEvents(eventName, { + const events: EventData[] = await getPastEventsWithFallback(api, web3, contract, eventName, { fromBlock: 0, toBlock: 'latest', filter: { @@ -57,7 +86,8 @@ export const getFinalizationEvent = async ( setExecutionEventsFetched: Function ) => { if (!contract || !web3 || !waitingBlocksResolved) return - const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id) + const api = fromHome ? FOREIGN_EXPLORER_API : HOME_EXPLORER_API + const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id, api) if (successExecutionData) { setResult(successExecutionData) } else { diff --git a/yarn.lock b/yarn.lock index f6dc08e6..06f3fdd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13953,6 +13953,11 @@ node-fetch@^2.1.2, node-fetch@^2.3.0, node-fetch@^2.5.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"