Add ALM Pending validator transactions detection (#363)
This commit is contained in:
parent
d228bb7ea9
commit
9e9e891db8
@ -34,6 +34,7 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
||||
default:
|
||||
|
@ -32,6 +32,7 @@ export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmatio
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
||||
|
@ -14,7 +14,7 @@ export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER
|
||||
export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
|
||||
|
||||
export const HOME_RPC_POLLING_INTERVAL: number = 5000
|
||||
export const FOREIGN_RPC_POLLING_INTERVAL: number = 15000
|
||||
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
|
||||
export const BLOCK_RANGE: number = 50
|
||||
export const ONE_DAY_TIMESTAMP: number = 86400
|
||||
export const THREE_DAYS_TIMESTAMP: number = 259200
|
||||
|
@ -17,7 +17,12 @@ import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEven
|
||||
import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks'
|
||||
import { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
|
||||
import { getFinalizationEvent } from '../utils/getFinalizationEvent'
|
||||
import { getValidatorFailedTransactionsForMessage, getExecutionFailedTransactionForMessage } from '../utils/explorer'
|
||||
import {
|
||||
getValidatorFailedTransactionsForMessage,
|
||||
getExecutionFailedTransactionForMessage,
|
||||
getValidatorPendingTransactionsForMessage,
|
||||
getExecutionPendingTransactionsForMessage
|
||||
} from '../utils/explorer'
|
||||
|
||||
export interface useMessageConfirmationsParams {
|
||||
message: MessageObject
|
||||
@ -58,6 +63,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
|
||||
const [failedConfirmations, setFailedConfirmations] = useState(false)
|
||||
const [failedExecution, setFailedExecution] = useState(false)
|
||||
const [pendingConfirmations, setPendingConfirmations] = useState(false)
|
||||
const [pendingExecution, setPendingExecution] = useState(false)
|
||||
|
||||
// Check if the validators are waiting for block confirmations to verify the message
|
||||
useEffect(
|
||||
@ -211,7 +218,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getValidatorFailedTransactionsForMessage,
|
||||
setFailedConfirmations
|
||||
setFailedConfirmations,
|
||||
getValidatorPendingTransactionsForMessage,
|
||||
setPendingConfirmations
|
||||
)
|
||||
|
||||
return () => {
|
||||
@ -262,7 +271,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getExecutionFailedTransactionForMessage,
|
||||
setFailedExecution
|
||||
setFailedExecution,
|
||||
getExecutionPendingTransactionsForMessage,
|
||||
setPendingExecution
|
||||
)
|
||||
|
||||
return () => {
|
||||
@ -297,6 +308,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
|
||||
} else if (failedExecution) {
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED)
|
||||
} else if (pendingExecution) {
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING)
|
||||
} else {
|
||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
}
|
||||
@ -307,6 +320,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
setStatus(CONFIRMATIONS_STATUS.WAITING)
|
||||
} else if (failedConfirmations) {
|
||||
setStatus(CONFIRMATIONS_STATUS.FAILED)
|
||||
} else if (pendingConfirmations) {
|
||||
setStatus(CONFIRMATIONS_STATUS.PENDING)
|
||||
} else {
|
||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
}
|
||||
@ -318,7 +333,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
|
||||
waitingBlocks,
|
||||
waitingBlocksForExecution,
|
||||
failedConfirmations,
|
||||
failedExecution
|
||||
failedExecution,
|
||||
pendingConfirmations,
|
||||
pendingExecution
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -14,6 +14,17 @@ export interface APITransaction {
|
||||
hash: string
|
||||
}
|
||||
|
||||
export interface APIPendingTransaction {
|
||||
input: string
|
||||
to: string
|
||||
hash: string
|
||||
}
|
||||
|
||||
export interface PendingTransactionsParams {
|
||||
account: string
|
||||
api: string
|
||||
}
|
||||
|
||||
export interface AccountTransactionsParams {
|
||||
account: string
|
||||
to: string
|
||||
@ -30,6 +41,12 @@ export interface GetFailedTransactionParams {
|
||||
endTimestamp: number
|
||||
}
|
||||
|
||||
export interface GetPendingTransactionParams {
|
||||
account: string
|
||||
to: string
|
||||
messageData: string
|
||||
}
|
||||
|
||||
export const fetchAccountTransactionsFromBlockscout = async ({
|
||||
account,
|
||||
to,
|
||||
@ -106,6 +123,24 @@ export const fetchAccountTransactions = (api: string) => {
|
||||
return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan
|
||||
}
|
||||
|
||||
export const fetchPendingTransactions = async ({
|
||||
account,
|
||||
api
|
||||
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
|
||||
const url = `${api}?module=account&action=pendingtxlist&address=${account}`
|
||||
|
||||
try {
|
||||
const result = await fetch(url).then(res => res.json())
|
||||
if (result.status === '0') {
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const getFailedTransactions = async (
|
||||
account: string,
|
||||
to: string,
|
||||
@ -162,3 +197,45 @@ export const getExecutionFailedTransactionForMessage = async ({
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))
|
||||
}
|
||||
|
||||
export const getValidatorPendingTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData
|
||||
}: GetPendingTransactionParams): Promise<APIPendingTransaction[]> => {
|
||||
const pendingTransactions = await fetchPendingTransactions({
|
||||
account,
|
||||
api: HOME_EXPLORER_API
|
||||
})
|
||||
|
||||
const toAddressLowerCase = to.toLowerCase()
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
|
||||
return pendingTransactions.filter(
|
||||
t =>
|
||||
t.to.toLowerCase() === toAddressLowerCase &&
|
||||
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
|
||||
t.input.includes(messageDataValue)
|
||||
)
|
||||
}
|
||||
|
||||
export const getExecutionPendingTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData
|
||||
}: GetPendingTransactionParams): Promise<APIPendingTransaction[]> => {
|
||||
const pendingTransactions = await fetchPendingTransactions({
|
||||
account,
|
||||
api: FOREIGN_EXPLORER_API
|
||||
})
|
||||
|
||||
const toAddressLowerCase = to.toLowerCase()
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
|
||||
return pendingTransactions.filter(
|
||||
t =>
|
||||
t.to.toLowerCase() === toAddressLowerCase &&
|
||||
t.input.includes(EXECUTE_SIGNATURES_HASH) &&
|
||||
t.input.includes(messageDataValue)
|
||||
)
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ import {
|
||||
ONE_DAY_TIMESTAMP,
|
||||
VALIDATOR_CONFIRMATION_STATUS
|
||||
} from '../config/constants'
|
||||
import { GetFailedTransactionParams, APITransaction } from './explorer'
|
||||
import {
|
||||
GetFailedTransactionParams,
|
||||
APITransaction,
|
||||
APIPendingTransaction,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
export const getValidatorConfirmation = (
|
||||
@ -77,6 +82,26 @@ export const getValidatorFailedTransaction = (
|
||||
}
|
||||
}
|
||||
|
||||
export const getValidatorPendingTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const failedTransactions = await getPendingTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData
|
||||
})
|
||||
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
return {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus
|
||||
}
|
||||
}
|
||||
|
||||
export const getConfirmationsForTx = async (
|
||||
messageData: string,
|
||||
web3: Maybe<Web3>,
|
||||
@ -90,7 +115,9 @@ export const getConfirmationsForTx = async (
|
||||
subscriptions: number[],
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
|
||||
setFailedConfirmations: Function
|
||||
setFailedConfirmations: Function,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
||||
setPendingConfirmations: Function
|
||||
) => {
|
||||
if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
@ -104,9 +131,29 @@ export const getConfirmationsForTx = async (
|
||||
|
||||
// If signatures not collected, it needs to retry in the next blocks
|
||||
if (successConfirmations.length !== requiredSignatures) {
|
||||
// Check if confirmation is pending
|
||||
const validatorPendingConfirmationsChecks = await Promise.all(
|
||||
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
|
||||
)
|
||||
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
validatorPendingConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
|
||||
if (validatorPendingConfirmations.length > 0) {
|
||||
setPendingConfirmations(true)
|
||||
}
|
||||
|
||||
const undefinedConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
)
|
||||
// Check if confirmation failed
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
notSuccessConfirmations.map(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
|
||||
)
|
||||
)
|
||||
@ -123,7 +170,7 @@ export const getConfirmationsForTx = async (
|
||||
}
|
||||
|
||||
const missingConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
if (missingConfirmations.length > 0) {
|
||||
@ -142,7 +189,9 @@ export const getConfirmationsForTx = async (
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations
|
||||
setFailedConfirmations,
|
||||
getPendingTransactions,
|
||||
setPendingConfirmations
|
||||
),
|
||||
HOME_RPC_POLLING_INTERVAL
|
||||
)
|
||||
|
@ -2,7 +2,12 @@ import { Contract, EventData } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { APITransaction, GetFailedTransactionParams } from './explorer'
|
||||
import {
|
||||
APIPendingTransaction,
|
||||
APITransaction,
|
||||
GetFailedTransactionParams,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { getBlock, MessageObject } from './web3'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
|
||||
@ -18,7 +23,9 @@ export const getFinalizationEvent = async (
|
||||
timestamp: number,
|
||||
collectedSignaturesEvent: Maybe<EventData>,
|
||||
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
|
||||
setFailedExecution: Function
|
||||
setFailedExecution: Function,
|
||||
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
||||
setPendingExecution: Function
|
||||
) => {
|
||||
if (!contract || !web3 || !waitingBlocksResolved) return
|
||||
// Since it filters by the message id, only one event will be fetched
|
||||
@ -52,33 +59,55 @@ export const getFinalizationEvent = async (
|
||||
if (collectedSignaturesEvent) {
|
||||
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
|
||||
|
||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}`
|
||||
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
|
||||
const pendingTransactions = await getPendingExecution({
|
||||
account: validator,
|
||||
messageData: message.data,
|
||||
to: contract.options.address
|
||||
})
|
||||
|
||||
if (!failedFromCache) {
|
||||
const failedTransactions = await getFailedExecution({
|
||||
account: validator,
|
||||
to: contract.options.address,
|
||||
messageData: message.data,
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
|
||||
// If the transaction is pending it sets the status and avoid making the request for failed transactions
|
||||
if (pendingTransactions.length > 0) {
|
||||
const pendingTx = pendingTransactions[0]
|
||||
|
||||
const nowTimestamp = Math.floor(new Date().getTime() / 1000.0)
|
||||
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
||||
validator: validator,
|
||||
txHash: pendingTx.hash,
|
||||
timestamp: nowTimestamp,
|
||||
executionResult: false
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} else {
|
||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}`
|
||||
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
|
||||
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
|
||||
// If validator execution failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
validatorsCache.set(validatorExecutionCacheKey, true)
|
||||
|
||||
const timestamp = parseInt(failedTx.timeStamp)
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false
|
||||
if (!failedFromCache) {
|
||||
const failedTransactions = await getFailedExecution({
|
||||
account: validator,
|
||||
to: contract.options.address,
|
||||
messageData: message.data,
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
|
||||
})
|
||||
setFailedExecution(true)
|
||||
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
|
||||
// If validator execution failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
validatorsCache.set(validatorExecutionCacheKey, true)
|
||||
|
||||
const timestamp = parseInt(failedTx.timeStamp)
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false
|
||||
})
|
||||
setFailedExecution(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,7 +126,9 @@ export const getFinalizationEvent = async (
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
setFailedExecution
|
||||
setFailedExecution,
|
||||
getPendingExecution,
|
||||
setPendingExecution
|
||||
),
|
||||
interval
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user