Improve performance of BS requests in ALM (#516)

This commit is contained in:
Kirill Fedoseev 2021-02-26 05:38:13 +03:00 committed by GitHub
parent 626f9376b2
commit 9fd3f6ab82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 279 additions and 199 deletions

@ -39,10 +39,17 @@ export interface ConfirmationsContainerParams {
message: MessageObject message: MessageObject
receipt: Maybe<TransactionReceipt> receipt: Maybe<TransactionReceipt>
fromHome: boolean fromHome: boolean
timestamp: number homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
} }
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => { export const ConfirmationsContainer = ({
message,
receipt,
fromHome,
homeStartBlock,
foreignStartBlock
}: ConfirmationsContainerParams) => {
const { const {
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
@ -62,7 +69,8 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
message, message,
receipt, receipt,
fromHome, fromHome,
timestamp, homeStartBlock,
foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
blockConfirmations blockConfirmations

@ -10,6 +10,7 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { ConfirmationsContainer } from './ConfirmationsContainer' import { ConfirmationsContainer } from './ConfirmationsContainer'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { BackButton } from './commons/BackButton' import { BackButton } from './commons/BackButton'
import { useClosestBlock } from '../hooks/useClosestBlock'
export interface StatusContainerParam { export interface StatusContainerParam {
onBackToMain: () => void onBackToMain: () => void
@ -23,12 +24,15 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const { chainId, txHash, messageIdParam } = useParams() const { chainId, txHash, messageIdParam } = useParams()
const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString() const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString()
const validParameters = validChainId && validTxHash(txHash) const validParameters = validChainId && validTxHash(txHash)
const isHome = chainId === home.chainId.toString()
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({ const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
txHash: validParameters ? txHash : '', txHash: validParameters ? txHash : '',
chainId: validParameters ? parseInt(chainId) : 0, chainId: validParameters ? parseInt(chainId) : 0,
receiptParam receiptParam
}) })
const homeStartBlock = useClosestBlock(true, isHome, receipt, timestamp)
const foreignStartBlock = useClosestBlock(false, isHome, receipt, timestamp)
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam
@ -64,7 +68,6 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash
const formattedMessageId = formatTxHash(displayReference) const formattedMessageId = formatTxHash(displayReference)
const isHome = chainId === home.chainId.toString()
const txExplorerLink = getExplorerTxUrl(txHash, isHome) const txExplorerLink = getExplorerTxUrl(txHash, isHome)
const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND
@ -101,7 +104,13 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
)} )}
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />} {displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}
{displayConfirmations && ( {displayConfirmations && (
<ConfirmationsContainer message={messageToConfirm} receipt={receipt} fromHome={isHome} timestamp={timestamp} /> <ConfirmationsContainer
message={messageToConfirm}
receipt={receipt}
fromHome={isHome}
homeStartBlock={homeStartBlock}
foreignStartBlock={foreignStartBlock}
/>
)} )}
<BackButton onBackToMain={onBackToMain} /> <BackButton onBackToMain={onBackToMain} />
</div> </div>

@ -18,9 +18,8 @@ export const ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION: boolean =
export const HOME_RPC_POLLING_INTERVAL: number = 5000 export const HOME_RPC_POLLING_INTERVAL: number = 5000
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000 export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
export const BLOCK_RANGE: number = 50 export const BLOCK_RANGE: number = 500
export const ONE_DAY_TIMESTAMP: number = 86400 export const MAX_TX_SEARCH_BLOCK_RANGE: number = 10000
export const THREE_DAYS_TIMESTAMP: number = 259200
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f' export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
export const SUBMIT_SIGNATURE_HASH = '630cea8e' export const SUBMIT_SIGNATURE_HASH = '630cea8e'

@ -0,0 +1,68 @@
import { useEffect, useState } from 'react'
import { TransactionReceipt } from 'web3-eth'
import { useStateProvider } from '../state/StateProvider'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
import { getClosestBlockByTimestamp } from '../utils/explorer'
export function useClosestBlock(
searchHome: boolean,
fromHome: boolean,
receipt: Maybe<TransactionReceipt>,
timestamp: number
) {
const { home, foreign } = useStateProvider()
const [blockNumber, setBlockNumber] = useState<number | null>(null)
useEffect(
() => {
if (!receipt || blockNumber || !timestamp) return
if (fromHome === searchHome) {
setBlockNumber(receipt.blockNumber)
return
}
const web3 = searchHome ? home.web3 : foreign.web3
if (!web3) return
const getBlock = async () => {
// try to fast-fetch closest block number from the chain explorer
try {
const api = searchHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
setBlockNumber(await getClosestBlockByTimestamp(api, timestamp))
return
} catch {}
const lastBlock = await web3.eth.getBlock('latest')
if (lastBlock.timestamp <= timestamp) {
setBlockNumber(lastBlock.number)
return
}
const oldBlock = await web3.eth.getBlock(Math.max(lastBlock.number - 10000, 1))
const blockDiff = lastBlock.number - oldBlock.number
const timeDiff = (lastBlock.timestamp as number) - (oldBlock.timestamp as number)
const averageBlockTime = timeDiff / blockDiff
let currentBlock = lastBlock
let prevBlockDiff = Infinity
while (true) {
const timeDiff = (currentBlock.timestamp as number) - timestamp
const blockDiff = Math.ceil(timeDiff / averageBlockTime)
if (Math.abs(blockDiff) < 5 || Math.abs(blockDiff) >= Math.abs(prevBlockDiff)) {
setBlockNumber(currentBlock.number - blockDiff - 5)
break
}
prevBlockDiff = blockDiff
currentBlock = await web3.eth.getBlock(currentBlock.number - blockDiff)
}
}
getBlock()
},
[blockNumber, foreign.web3, fromHome, home.web3, receipt, searchHome, timestamp]
)
return blockNumber
}

@ -28,7 +28,8 @@ export interface useMessageConfirmationsParams {
message: MessageObject message: MessageObject
receipt: Maybe<TransactionReceipt> receipt: Maybe<TransactionReceipt>
fromHome: boolean fromHome: boolean
timestamp: number homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
requiredSignatures: number requiredSignatures: number
validatorList: string[] validatorList: string[]
blockConfirmations: number blockConfirmations: number
@ -56,7 +57,8 @@ export const useMessageConfirmations = ({
message, message,
receipt, receipt,
fromHome, fromHome,
timestamp, homeStartBlock,
foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
blockConfirmations blockConfirmations
@ -90,6 +92,17 @@ export const useMessageConfirmations = ({
return filteredList.length > 0 return filteredList.length > 0
} }
// start watching blocks at the start
useEffect(
() => {
if (!home.web3 || !foreign.web3) return
homeBlockNumberProvider.start(home.web3)
foreignBlockNumberProvider.start(foreign.web3)
},
[foreign.web3, home.web3]
)
// Check if the validators are waiting for block confirmations to verify the message // Check if the validators are waiting for block confirmations to verify the message
useEffect( useEffect(
() => { () => {
@ -105,9 +118,6 @@ export const useMessageConfirmations = ({
const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider
const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL
const web3 = fromHome ? home.web3 : foreign.web3
blockProvider.start(web3)
const targetBlock = receipt.blockNumber + blockConfirmations const targetBlock = receipt.blockNumber + blockConfirmations
checkSignaturesWaitingForBLocks( checkSignaturesWaitingForBLocks(
@ -123,7 +133,6 @@ export const useMessageConfirmations = ({
return () => { return () => {
unsubscribe() unsubscribe()
blockProvider.stop()
} }
}, },
[ [
@ -153,8 +162,6 @@ export const useMessageConfirmations = ({
}) })
} }
homeBlockNumberProvider.start(home.web3)
const fromBlock = receipt.blockNumber const fromBlock = receipt.blockNumber
const toBlock = fromBlock + BLOCK_RANGE const toBlock = fromBlock + BLOCK_RANGE
const messageHash = home.web3.utils.soliditySha3Raw(message.data) const messageHash = home.web3.utils.soliditySha3Raw(message.data)
@ -171,7 +178,6 @@ export const useMessageConfirmations = ({
return () => { return () => {
unsubscribe() unsubscribe()
homeBlockNumberProvider.stop()
} }
}, },
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected] [fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
@ -192,7 +198,6 @@ export const useMessageConfirmations = ({
}) })
} }
homeBlockNumberProvider.start(home.web3)
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
checkWaitingBlocksForExecution( checkWaitingBlocksForExecution(
@ -208,7 +213,6 @@ export const useMessageConfirmations = ({
return () => { return () => {
unsubscribe() unsubscribe()
homeBlockNumberProvider.stop()
} }
}, },
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved] [collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved]
@ -218,7 +222,7 @@ export const useMessageConfirmations = ({
// To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations // To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations
useEffect( useEffect(
() => { () => {
if (!waitingBlocksResolved || !timestamp || !requiredSignatures) return if (!waitingBlocksResolved || !homeStartBlock || !requiredSignatures) return
const subscriptions: Array<number> = [] const subscriptions: Array<number> = []
@ -239,7 +243,7 @@ export const useMessageConfirmations = ({
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, waitingBlocksResolved,
subscriptions, subscriptions,
timestamp, homeStartBlock,
getValidatorFailedTransactionsForMessage, getValidatorFailedTransactionsForMessage,
setFailedConfirmations, setFailedConfirmations,
getValidatorPendingTransactionsForMessage, getValidatorPendingTransactionsForMessage,
@ -259,7 +263,7 @@ export const useMessageConfirmations = ({
home.bridgeContract, home.bridgeContract,
requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
timestamp, homeStartBlock,
setConfirmations setConfirmations
] ]
) )
@ -270,6 +274,8 @@ export const useMessageConfirmations = ({
useEffect( useEffect(
() => { () => {
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
const startBlock = fromHome ? foreignStartBlock : homeStartBlock
if (!startBlock) return
const subscriptions: Array<number> = [] const subscriptions: Array<number> = []
@ -285,6 +291,7 @@ export const useMessageConfirmations = ({
const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
getFinalizationEvent( getFinalizationEvent(
fromHome,
bridgeContract, bridgeContract,
contractEvent, contractEvent,
providedWeb3, providedWeb3,
@ -293,7 +300,7 @@ export const useMessageConfirmations = ({
message, message,
interval, interval,
subscriptions, subscriptions,
timestamp, startBlock,
collectedSignaturesEvent, collectedSignaturesEvent,
getExecutionFailedTransactionForMessage, getExecutionFailedTransactionForMessage,
setFailedExecution, setFailedExecution,
@ -315,8 +322,9 @@ export const useMessageConfirmations = ({
home.web3, home.web3,
waitingBlocksResolved, waitingBlocksResolved,
waitingBlocksForExecutionResolved, waitingBlocksForExecutionResolved,
timestamp, collectedSignaturesEvent,
collectedSignaturesEvent foreignStartBlock,
homeStartBlock
] ]
) )
@ -328,6 +336,9 @@ export const useMessageConfirmations = ({
? CONFIRMATIONS_STATUS.SUCCESS ? CONFIRMATIONS_STATUS.SUCCESS
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
setStatus(newStatus) setStatus(newStatus)
foreignBlockNumberProvider.stop()
homeBlockNumberProvider.stop()
} else if (signatureCollected) { } else if (signatureCollected) {
if (fromHome) { if (fromHome) {
if (waitingBlocksForExecution) { if (waitingBlocksForExecution) {

@ -1,6 +1,6 @@
import Web3 from 'web3' import Web3 from 'web3'
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds' import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'
import { HOME_RPC_POLLING_INTERVAL } from '../config/constants' import { FOREIGN_RPC_POLLING_INTERVAL, HOME_RPC_POLLING_INTERVAL } from '../config/constants'
export class BlockNumberProvider { export class BlockNumberProvider {
private running: number private running: number
@ -61,4 +61,4 @@ export class BlockNumberProvider {
} }
export const homeBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL) export const homeBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
export const foreignBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL) export const foreignBlockNumberProvider = new BlockNumberProvider(FOREIGN_RPC_POLLING_INTERVAL)

@ -17,19 +17,33 @@ const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
describe('getFailedTransactions', () => { describe('getFailedTransactions', () => {
test('should only return failed transactions', async () => { test('should only return failed transactions', async () => {
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }] const to = otherAddress
const transactions = [
{ isError: '0', to },
{ isError: '1', to },
{ isError: '0', to },
{ isError: '1', to },
{ isError: '1', to }
]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getFailedTransactions('', '', 0, 1, '', fetchAccountTransactions) const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(3) expect(result.length).toEqual(3)
}) })
}) })
describe('getSuccessTransactions', () => { describe('getSuccessTransactions', () => {
test('should only return success transactions', async () => { test('should only return success transactions', async () => {
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }] const to = otherAddress
const transactions = [
{ isError: '0', to },
{ isError: '1', to },
{ isError: '0', to },
{ isError: '1', to },
{ isError: '1', to }
]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getSuccessTransactions('', '', 0, 1, '', fetchAccountTransactions) const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(2) expect(result.length).toEqual(2)
}) })
}) })
@ -74,8 +88,8 @@ describe('getExecutionFailedTransactionForMessage', () => {
account: '', account: '',
to: '', to: '',
messageData, messageData,
startTimestamp: 0, startBlock: 0,
endTimestamp: 1 endBlock: 1
}, },
fetchAccountTransactions fetchAccountTransactions
) )

@ -64,6 +64,7 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn() const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName, eventName,
web3, web3,
@ -115,6 +116,7 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn() const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName, eventName,
web3, web3,
@ -166,6 +168,7 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn() const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName, eventName,
web3, web3,
@ -217,6 +220,7 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn() const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName, eventName,
web3, web3,
@ -275,6 +279,7 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn() const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent( await getFinalizationEvent(
true,
contract, contract,
eventName, eventName,
web3, web3,

@ -30,7 +30,6 @@ export const checkWaitingBlocksForExecution = async (
) )
setWaitingBlocksForExecutionResolved(true) setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false) setWaitingBlocksForExecution(false)
blockProvider.stop()
} else { } else {
let nextInterval = interval let nextInterval = interval
if (!currentBlock) { if (!currentBlock) {

@ -1,8 +1,10 @@
import { import {
BLOCK_RANGE,
EXECUTE_AFFIRMATION_HASH, EXECUTE_AFFIRMATION_HASH,
EXECUTE_SIGNATURES_HASH, EXECUTE_SIGNATURES_HASH,
FOREIGN_EXPLORER_API, FOREIGN_EXPLORER_API,
HOME_EXPLORER_API, HOME_EXPLORER_API,
MAX_TX_SEARCH_BLOCK_RANGE,
SUBMIT_SIGNATURE_HASH SUBMIT_SIGNATURE_HASH
} from '../config/constants' } from '../config/constants'
@ -12,6 +14,7 @@ export interface APITransaction {
input: string input: string
to: string to: string
hash: string hash: string
blockNumber: string
} }
export interface APIPendingTransaction { export interface APIPendingTransaction {
@ -27,106 +30,42 @@ export interface PendingTransactionsParams {
export interface AccountTransactionsParams { export interface AccountTransactionsParams {
account: string account: string
to: string startBlock: number
startTimestamp: number endBlock: number
endTimestamp: number
api: string api: string
} }
export interface GetFailedTransactionParams {
account: string
to: string
messageData: string
startTimestamp: number
endTimestamp: number
}
export interface GetPendingTransactionParams { export interface GetPendingTransactionParams {
account: string account: string
to: string to: string
messageData: string messageData: string
} }
export const fetchAccountTransactionsFromBlockscout = async ({ export interface GetTransactionParams extends GetPendingTransactionParams {
account, startBlock: number
to, endBlock: number
startTimestamp, }
endTimestamp,
api export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => {
}: AccountTransactionsParams): Promise<APITransaction[]> => { const params = `module=account&action=txlist&address=${account}&filterby=from&startblock=${startBlock}&endblock=${endBlock}`
const url = `${api}?module=account&action=txlist&address=${account}&filterby=from=${account}&to=${to}&starttimestamp=${startTimestamp}&endtimestamp=${endTimestamp}` const url = api.includes('blockscout') ? `${api}?${params}` : `${api}&${params}`
try {
const result = await fetch(url).then(res => res.json()) const result = await fetch(url).then(res => res.json())
if (result.status === '0') {
if (result.message === 'No transactions found') {
return [] return []
} }
return result.result return result.result
} catch (e) {
console.log(e)
return []
}
}
export const getBlockByTimestampUrl = (api: string, timestamp: number) =>
`${api}&module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before`
export const fetchAccountTransactionsFromEtherscan = async ({
account,
to,
startTimestamp,
endTimestamp,
api
}: AccountTransactionsParams): Promise<APITransaction[]> => {
const startBlockUrl = getBlockByTimestampUrl(api, startTimestamp)
const endBlockUrl = getBlockByTimestampUrl(api, endTimestamp)
let fromBlock = 0
let toBlock = 9999999999999
try {
const [fromBlockResult, toBlockResult] = await Promise.all([
fetch(startBlockUrl).then(res => res.json()),
fetch(endBlockUrl).then(res => res.json())
])
if (fromBlockResult.status !== '0') {
fromBlock = parseInt(fromBlockResult.result)
}
if (toBlockResult.status !== '0') {
toBlock = parseInt(toBlockResult.result)
}
} catch (e) {
console.log(e)
return []
}
const url = `${api}&module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}`
try {
const result = await fetch(url).then(res => res.json())
if (result.status === '0') {
return []
}
const toAddressLowerCase = to.toLowerCase()
const transactions: APITransaction[] = result.result
return transactions.filter(t => t.to.toLowerCase() === toAddressLowerCase)
} catch (e) {
console.log(e)
return []
}
}
export const fetchAccountTransactions = (api: string) => {
return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan
} }
export const fetchPendingTransactions = async ({ export const fetchPendingTransactions = async ({
account, account,
api api
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => { }: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
if (!api.includes('blockscout')) {
return []
}
const url = `${api}?module=account&action=pendingtxlist&address=${account}` const url = `${api}?module=account&action=pendingtxlist&address=${account}`
try { try {
@ -141,30 +80,96 @@ export const fetchPendingTransactions = async ({
} }
} }
export const getClosestBlockByTimestamp = async (api: string, timestamp: number): Promise<number> => {
if (api.includes('blockscout')) {
throw new Error('Blockscout does not support getblocknobytime')
}
const url = `${api}&module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before`
const blockNumber = await fetch(url).then(res => res.json())
return parseInt(blockNumber.result)
}
// fast version of fetchAccountTransactions
// sequentially fetches transactions in small batches
// caches the result
const transactionsCache: { [key: string]: { lastBlock: number; transactions: APITransaction[] } } = {}
export const getAccountTransactions = async ({
account,
startBlock,
endBlock,
api
}: AccountTransactionsParams): Promise<APITransaction[]> => {
const key = `${account}-${startBlock}-${api}`
// initialize empty cache if it doesn't exist yet
if (!transactionsCache[key]) {
transactionsCache[key] = { lastBlock: startBlock - 1, transactions: [] }
}
// if cache contains events up to block X,
// new batch is fetched for range [X + 1, X + 1 + BLOCK_RANGE]
const newStartBlock = transactionsCache[key].lastBlock + 1
const newEndBlock = newStartBlock + BLOCK_RANGE
// search for new transactions only if max allowed block range is not yet exceeded
if (newEndBlock <= startBlock + MAX_TX_SEARCH_BLOCK_RANGE) {
const newTransactions = await fetchAccountTransactions({
account,
startBlock: newStartBlock,
endBlock: newEndBlock,
api
})
const transactions = transactionsCache[key].transactions.concat(...newTransactions)
// cache updated transactions list
transactionsCache[key].transactions = transactions
// enbBlock is assumed to be the current block number of the chain
// if the whole range is finalized, last block can be safely updated to the end of the range
// this works even if there are no transactions in the list
if (newEndBlock < endBlock) {
transactionsCache[key].lastBlock = newEndBlock
} else if (transactions.length > 0) {
transactionsCache[key].lastBlock = parseInt(transactions[transactions.length - 1].blockNumber, 10)
}
return transactions
}
console.warn(`Reached max transaction searching range, returning previously cached transactions for ${account}`)
return transactionsCache[key].transactions
}
const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase()
export const getFailedTransactions = async ( export const getFailedTransactions = async (
account: string, account: string,
to: string, to: string,
startTimestamp: number, startBlock: number,
endTimestamp: number, endBlock: number,
api: string, api: string,
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]> getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api }) const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError !== '0') return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to))
} }
export const getSuccessTransactions = async ( export const getSuccessTransactions = async (
account: string, account: string,
to: string, to: string,
startTimestamp: number, startBlock: number,
endTimestamp: number, endBlock: number,
api: string, api: string,
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]> getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api }) const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError === '0') return transactions.filter(t => t.isError === '0').filter(filterReceiver(to))
} }
export const filterValidatorSignatureTransaction = ( export const filterValidatorSignatureTransaction = (
@ -183,17 +188,10 @@ export const getValidatorFailedTransactionsForMessage = async ({
account, account,
to, to,
messageData, messageData,
startTimestamp, startBlock,
endTimestamp endBlock
}: GetFailedTransactionParams): Promise<APITransaction[]> => { }: GetTransactionParams): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactions( const failedTransactions = await getFailedTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
account,
to,
startTimestamp,
endTimestamp,
HOME_EXPLORER_API,
fetchAccountTransactionsFromBlockscout
)
return filterValidatorSignatureTransaction(failedTransactions, messageData) return filterValidatorSignatureTransaction(failedTransactions, messageData)
} }
@ -202,33 +200,19 @@ export const getValidatorSuccessTransactionsForMessage = async ({
account, account,
to, to,
messageData, messageData,
startTimestamp, startBlock,
endTimestamp endBlock
}: GetFailedTransactionParams): Promise<APITransaction[]> => { }: GetTransactionParams): Promise<APITransaction[]> => {
const transactions = await getSuccessTransactions( const transactions = await getSuccessTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
account,
to,
startTimestamp,
endTimestamp,
HOME_EXPLORER_API,
fetchAccountTransactionsFromBlockscout
)
return filterValidatorSignatureTransaction(transactions, messageData) return filterValidatorSignatureTransaction(transactions, messageData)
} }
export const getExecutionFailedTransactionForMessage = async ( export const getExecutionFailedTransactionForMessage = async (
{ account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams, { account, to, messageData, startBlock, endBlock }: GetTransactionParams,
getFailedTransactionsMethod = getFailedTransactions getFailedTransactionsMethod = getFailedTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactionsMethod( const failedTransactions = await getFailedTransactionsMethod(account, to, startBlock, endBlock, FOREIGN_EXPLORER_API)
account,
to,
startTimestamp,
endTimestamp,
FOREIGN_EXPLORER_API,
fetchAccountTransactions(FOREIGN_EXPLORER_API)
)
const messageDataValue = messageData.replace('0x', '') const messageDataValue = messageData.replace('0x', '')
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue)) return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))

@ -31,7 +31,6 @@ export const getCollectedSignaturesEvent = async (
if (filteredEvents.length) { if (filteredEvents.length) {
const event = filteredEvents[0] const event = filteredEvents[0]
setCollectedSignaturesEvent(event) setCollectedSignaturesEvent(event)
homeBlockNumberProvider.stop()
} else { } else {
const newFromBlock = currentBlock ? securedToBlock : fromBlock const newFromBlock = currentBlock ? securedToBlock : fromBlock
const newToBlock = currentBlock ? toBlock + BLOCK_RANGE : toBlock const newToBlock = currentBlock ? toBlock + BLOCK_RANGE : toBlock

@ -1,12 +1,7 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
GetFailedTransactionParams,
APITransaction,
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { getAffirmationsSigned, getMessagesSigned } from './contract' import { getAffirmationsSigned, getMessagesSigned } from './contract'
import { import {
getValidatorConfirmation, getValidatorConfirmation,
@ -43,12 +38,12 @@ export const getConfirmationsForTx = async (
setSignatureCollected: Function, setSignatureCollected: Function,
waitingBlocksResolved: boolean, waitingBlocksResolved: boolean,
subscriptions: number[], subscriptions: number[],
timestamp: number, startBlock: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>, getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function, setFailedConfirmations: Function,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>, getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingConfirmations: Function, setPendingConfirmations: Function,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => { ) => {
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
@ -102,7 +97,7 @@ export const getConfirmationsForTx = async (
// Check if confirmation failed // Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all( const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map( undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
) )
) )
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
@ -136,7 +131,7 @@ export const getConfirmationsForTx = async (
// get transactions from success signatures // get transactions from success signatures
const successConfirmationWithData = await Promise.all( const successConfirmationWithData = await Promise.all(
successConfirmations.map( successConfirmations.map(
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions) getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
) )
) )
@ -162,7 +157,7 @@ export const getConfirmationsForTx = async (
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, waitingBlocksResolved,
subscriptions, subscriptions,
timestamp, startBlock,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
getPendingTransactions, getPendingTransactions,

@ -1,15 +1,11 @@
import { Contract, EventData } from 'web3-eth-contract' import { Contract, EventData } from 'web3-eth-contract'
import Web3 from 'web3' import Web3 from 'web3'
import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { CACHE_KEY_EXECUTION_FAILED, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ExecutionData } from '../hooks/useMessageConfirmations'
import { import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
APIPendingTransaction,
APITransaction,
GetFailedTransactionParams,
GetPendingTransactionParams
} from './explorer'
import { getBlock, MessageObject } from './web3' import { getBlock, MessageObject } from './web3'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider'
export const getSuccessExecutionData = async (contract: Contract, eventName: string, web3: Web3, messageId: string) => { export const getSuccessExecutionData = async (contract: Contract, eventName: string, web3: Web3, messageId: string) => {
// Since it filters by the message id, only one event will be fetched // Since it filters by the message id, only one event will be fetched
@ -43,6 +39,7 @@ export const getSuccessExecutionData = async (contract: Contract, eventName: str
} }
export const getFinalizationEvent = async ( export const getFinalizationEvent = async (
fromHome: boolean,
contract: Maybe<Contract>, contract: Maybe<Contract>,
eventName: string, eventName: string,
web3: Maybe<Web3>, web3: Maybe<Web3>,
@ -51,9 +48,9 @@ export const getFinalizationEvent = async (
message: MessageObject, message: MessageObject,
interval: number, interval: number,
subscriptions: number[], subscriptions: number[],
timestamp: number, startBlock: number,
collectedSignaturesEvent: Maybe<EventData>, collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>, getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function, setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>, getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function, setPendingExecution: Function,
@ -92,14 +89,15 @@ export const getFinalizationEvent = async (
} else { } else {
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}` const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
const blockProvider = fromHome ? foreignBlockNumberProvider : homeBlockNumberProvider
if (!failedFromCache) { if (!failedFromCache) {
const failedTransactions = await getFailedExecution({ const failedTransactions = await getFailedExecution({
account: validator, account: validator,
to: contract.options.address, to: contract.options.address,
messageData: message.data, messageData: message.data,
startTimestamp: timestamp, startBlock,
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP endBlock: blockProvider.get() || 0
}) })
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
@ -125,6 +123,7 @@ export const getFinalizationEvent = async (
const timeoutId = setTimeout( const timeoutId = setTimeout(
() => () =>
getFinalizationEvent( getFinalizationEvent(
fromHome,
contract, contract,
eventName, eventName,
web3, web3,
@ -133,7 +132,7 @@ export const getFinalizationEvent = async (
message, message,
interval, interval,
subscriptions, subscriptions,
timestamp, startBlock,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution, setFailedExecution,

@ -16,7 +16,6 @@ export const checkSignaturesWaitingForBLocks = async (
if (currentBlock && currentBlock >= targetBlock) { if (currentBlock && currentBlock >= targetBlock) {
setWaitingBlocksResolved(true) setWaitingBlocksResolved(true)
setWaitingStatus(false) setWaitingStatus(false)
blockProvider.stop()
} else { } else {
let nextInterval = interval let nextInterval = interval
if (!currentBlock) { if (!currentBlock) {

@ -2,18 +2,9 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
import { import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
CACHE_KEY_FAILED, import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
CACHE_KEY_SUCCESS, import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
ONE_DAY_TIMESTAMP,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import {
APIPendingTransaction,
APITransaction,
GetFailedTransactionParams,
GetPendingTransactionParams
} from './explorer'
export const getValidatorConfirmation = ( export const getValidatorConfirmation = (
web3: Web3, web3: Web3,
@ -50,8 +41,8 @@ export const getSuccessExecutionTransaction = (
bridgeContract: Contract, bridgeContract: Contract,
fromHome: boolean, fromHome: boolean,
messageData: string, messageData: string,
timestamp: number, startBlock: number,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
@ -65,8 +56,8 @@ export const getSuccessExecutionTransaction = (
account: validatorData.validator, account: validatorData.validator,
to: bridgeContract.options.address, to: bridgeContract.options.address,
messageData, messageData,
startTimestamp: timestamp, startBlock,
endTimestamp: timestamp + ONE_DAY_TIMESTAMP endBlock: homeBlockNumberProvider.get() || 0
}) })
let txHashTimestamp = 0 let txHashTimestamp = 0
@ -98,8 +89,8 @@ export const getSuccessExecutionTransaction = (
export const getValidatorFailedTransaction = ( export const getValidatorFailedTransaction = (
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
timestamp: number, startBlock: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]> getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
const failedFromCache = validatorsCache.getData(validatorCacheKey) const failedFromCache = validatorsCache.getData(validatorCacheKey)
@ -112,8 +103,8 @@ export const getValidatorFailedTransaction = (
account: validatorData.validator, account: validatorData.validator,
to: bridgeContract.options.address, to: bridgeContract.options.address,
messageData, messageData,
startTimestamp: timestamp, startBlock,
endTimestamp: timestamp + ONE_DAY_TIMESTAMP endBlock: homeBlockNumberProvider.get() || 0
}) })
const newStatus = const newStatus =
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED