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> return <SuccessLabel>{validatorStatus}</SuccessLabel>
case VALIDATOR_CONFIRMATION_STATUS.FAILED: case VALIDATOR_CONFIRMATION_STATUS.FAILED:
return <RedLabel>{validatorStatus}</RedLabel> return <RedLabel>{validatorStatus}</RedLabel>
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
case VALIDATOR_CONFIRMATION_STATUS.WAITING: case VALIDATOR_CONFIRMATION_STATUS.WAITING:
return <GreyLabel>{validatorStatus}</GreyLabel> return <GreyLabel>{validatorStatus}</GreyLabel>
default: default:

@ -32,6 +32,7 @@ export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmatio
return <SuccessLabel>{validatorStatus}</SuccessLabel> return <SuccessLabel>{validatorStatus}</SuccessLabel>
case VALIDATOR_CONFIRMATION_STATUS.FAILED: case VALIDATOR_CONFIRMATION_STATUS.FAILED:
return <RedLabel>{validatorStatus}</RedLabel> return <RedLabel>{validatorStatus}</RedLabel>
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
case VALIDATOR_CONFIRMATION_STATUS.WAITING: case VALIDATOR_CONFIRMATION_STATUS.WAITING:
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
return <GreyLabel>{validatorStatus}</GreyLabel> 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 FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
export const HOME_RPC_POLLING_INTERVAL: number = 5000 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 BLOCK_RANGE: number = 50
export const ONE_DAY_TIMESTAMP: number = 86400 export const ONE_DAY_TIMESTAMP: number = 86400
export const THREE_DAYS_TIMESTAMP: number = 259200 export const THREE_DAYS_TIMESTAMP: number = 259200

@ -17,7 +17,12 @@ import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEven
import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks' import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks'
import { getConfirmationsForTx } from '../utils/getConfirmationsForTx' import { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
import { getFinalizationEvent } from '../utils/getFinalizationEvent' import { getFinalizationEvent } from '../utils/getFinalizationEvent'
import { getValidatorFailedTransactionsForMessage, getExecutionFailedTransactionForMessage } from '../utils/explorer' import {
getValidatorFailedTransactionsForMessage,
getExecutionFailedTransactionForMessage,
getValidatorPendingTransactionsForMessage,
getExecutionPendingTransactionsForMessage
} from '../utils/explorer'
export interface useMessageConfirmationsParams { export interface useMessageConfirmationsParams {
message: MessageObject message: MessageObject
@ -58,6 +63,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false) const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
const [failedConfirmations, setFailedConfirmations] = useState(false) const [failedConfirmations, setFailedConfirmations] = useState(false)
const [failedExecution, setFailedExecution] = 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 // Check if the validators are waiting for block confirmations to verify the message
useEffect( useEffect(
@ -211,7 +218,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
subscriptions, subscriptions,
timestamp, timestamp,
getValidatorFailedTransactionsForMessage, getValidatorFailedTransactionsForMessage,
setFailedConfirmations setFailedConfirmations,
getValidatorPendingTransactionsForMessage,
setPendingConfirmations
) )
return () => { return () => {
@ -262,7 +271,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getExecutionFailedTransactionForMessage, getExecutionFailedTransactionForMessage,
setFailedExecution setFailedExecution,
getExecutionPendingTransactionsForMessage,
setPendingExecution
) )
return () => { return () => {
@ -297,6 +308,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING) setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
} else if (failedExecution) { } else if (failedExecution) {
setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED) setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED)
} else if (pendingExecution) {
setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING)
} else { } else {
setStatus(CONFIRMATIONS_STATUS.UNDEFINED) setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
} }
@ -307,6 +320,8 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
setStatus(CONFIRMATIONS_STATUS.WAITING) setStatus(CONFIRMATIONS_STATUS.WAITING)
} else if (failedConfirmations) { } else if (failedConfirmations) {
setStatus(CONFIRMATIONS_STATUS.FAILED) setStatus(CONFIRMATIONS_STATUS.FAILED)
} else if (pendingConfirmations) {
setStatus(CONFIRMATIONS_STATUS.PENDING)
} else { } else {
setStatus(CONFIRMATIONS_STATUS.UNDEFINED) setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
} }
@ -318,7 +333,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
waitingBlocks, waitingBlocks,
waitingBlocksForExecution, waitingBlocksForExecution,
failedConfirmations, failedConfirmations,
failedExecution failedExecution,
pendingConfirmations,
pendingExecution
] ]
) )

@ -14,6 +14,17 @@ export interface APITransaction {
hash: string hash: string
} }
export interface APIPendingTransaction {
input: string
to: string
hash: string
}
export interface PendingTransactionsParams {
account: string
api: string
}
export interface AccountTransactionsParams { export interface AccountTransactionsParams {
account: string account: string
to: string to: string
@ -30,6 +41,12 @@ export interface GetFailedTransactionParams {
endTimestamp: number endTimestamp: number
} }
export interface GetPendingTransactionParams {
account: string
to: string
messageData: string
}
export const fetchAccountTransactionsFromBlockscout = async ({ export const fetchAccountTransactionsFromBlockscout = async ({
account, account,
to, to,
@ -106,6 +123,24 @@ export const fetchAccountTransactions = (api: string) => {
return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan 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 ( export const getFailedTransactions = async (
account: string, account: string,
to: string, to: string,
@ -162,3 +197,45 @@ export const getExecutionFailedTransactionForMessage = async ({
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))
} }
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, ONE_DAY_TIMESTAMP,
VALIDATOR_CONFIRMATION_STATUS VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants' } from '../config/constants'
import { GetFailedTransactionParams, APITransaction } from './explorer' import {
GetFailedTransactionParams,
APITransaction,
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
export const getValidatorConfirmation = ( 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 ( export const getConfirmationsForTx = async (
messageData: string, messageData: string,
web3: Maybe<Web3>, web3: Maybe<Web3>,
@ -90,7 +115,9 @@ export const getConfirmationsForTx = async (
subscriptions: number[], subscriptions: number[],
timestamp: number, timestamp: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>, getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function setFailedConfirmations: Function,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingConfirmations: Function
) => { ) => {
if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return
const hashMsg = web3.utils.soliditySha3Raw(messageData) 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 signatures not collected, it needs to retry in the next blocks
if (successConfirmations.length !== requiredSignatures) { 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 // Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all( const validatorFailedConfirmationsChecks = await Promise.all(
notSuccessConfirmations.map( undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions) getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
) )
) )
@ -123,7 +170,7 @@ export const getConfirmationsForTx = async (
} }
const missingConfirmations = validatorConfirmations.filter( 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) { if (missingConfirmations.length > 0) {
@ -142,7 +189,9 @@ export const getConfirmationsForTx = async (
subscriptions, subscriptions,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations
), ),
HOME_RPC_POLLING_INTERVAL HOME_RPC_POLLING_INTERVAL
) )

@ -2,7 +2,12 @@ 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, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ExecutionData } from '../hooks/useMessageConfirmations'
import { APITransaction, GetFailedTransactionParams } from './explorer' import {
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'
@ -18,7 +23,9 @@ export const getFinalizationEvent = async (
timestamp: number, timestamp: number,
collectedSignaturesEvent: Maybe<EventData>, collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>, getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function
) => { ) => {
if (!contract || !web3 || !waitingBlocksResolved) return if (!contract || !web3 || !waitingBlocksResolved) return
// 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
@ -52,33 +59,55 @@ export const getFinalizationEvent = async (
if (collectedSignaturesEvent) { if (collectedSignaturesEvent) {
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}` const pendingTransactions = await getPendingExecution({
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey) account: validator,
messageData: message.data,
to: contract.options.address
})
if (!failedFromCache) { // If the transaction is pending it sets the status and avoid making the request for failed transactions
const failedTransactions = await getFailedExecution({ if (pendingTransactions.length > 0) {
account: validator, const pendingTx = pendingTransactions[0]
to: contract.options.address,
messageData: message.data, const nowTimestamp = Math.floor(new Date().getTime() / 1000.0)
startTimestamp: timestamp,
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP 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) { if (!failedFromCache) {
const failedTx = failedTransactions[0] const failedTransactions = await getFailedExecution({
account: validator,
// If validator execution failed, we cache the result to avoid doing future requests for a result that won't change to: contract.options.address,
validatorsCache.set(validatorExecutionCacheKey, true) messageData: message.data,
startTimestamp: timestamp,
const timestamp = parseInt(failedTx.timeStamp) endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
setResult({
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validator: validator,
txHash: failedTx.hash,
timestamp,
executionResult: false
}) })
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, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
setFailedExecution setFailedExecution,
getPendingExecution,
setPendingExecution
), ),
interval interval
) )