Add ALM Pending validator transactions detection (#363)

This commit is contained in:
Gerardo Nardelli 2020-06-23 11:24:10 -03:00 committed by GitHub
parent d228bb7ea9
commit 9e9e891db8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 212 additions and 36 deletions

@ -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
)