Add pretty error messages for manual execution transaction reverts (#511)

This commit is contained in:
Kirill Fedoseev 2021-02-19 04:25:01 +03:00 committed by GitHub
parent 0451d6e373
commit e1536755f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 42 deletions

@ -36,10 +36,12 @@ export const ExecutionConfirmation = ({
const availableManualExecution = const availableManualExecution =
!isHome && !isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING || (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED ||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && (executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
executionEventsFetched && executionEventsFetched &&
!!executionData.validator)) !!executionData.validator))
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const showAgeColumn = !requiredManualExecution || executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
const windowWidth = useWindowWidth() const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome) const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@ -71,7 +73,7 @@ export const ExecutionConfirmation = ({
<tr> <tr>
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th> <th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
<th className="text-center">Status</th> <th className="text-center">Status</th>
{!requiredManualExecution && <th className="text-center">Age</th>} {showAgeColumn && <th className="text-center">Age</th>}
{availableManualExecution && <th className="text-center">Actions</th>} {availableManualExecution && <th className="text-center">Actions</th>}
</tr> </tr>
</Thead> </Thead>
@ -87,7 +89,7 @@ export const ExecutionConfirmation = ({
)} )}
</td> </td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd> <StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
{!requiredManualExecution && ( {showAgeColumn && (
<AgeTd className="text-center"> <AgeTd className="text-center">
{executionData.timestamp > 0 ? ( {executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank"> <ExplorerTxLink href={txExplorerLink} target="_blank">

@ -2,9 +2,17 @@ import React, { useState, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { InjectedConnector } from '@web3-react/injected-connector' import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import {
DOUBLE_EXECUTION_ATTEMPT_ERROR,
EXECUTION_FAILED_ERROR,
EXECUTION_OUT_OF_GAS_ERROR,
INCORRECT_CHAIN_ERROR,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures' import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth'
const StyledButton = styled.button` const StyledButton = styled.button`
color: var(--button-color); color: var(--button-color);
@ -61,7 +69,9 @@ export const ManualExecutionButton = ({
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS)) const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI() const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false) setManualExecution(false)
library.eth library.eth
@ -80,7 +90,27 @@ export const ManualExecutionButton = ({
}) })
setPendingExecution(true) setPendingExecution(true)
}) })
.on('error', (e: Error) => setError(e.message)) .on('error', async (e: Error, receipt: TransactionReceipt) => {
if (e.message.includes('Transaction has been reverted by the EVM')) {
const successExecutionData = await getSuccessExecutionData(bridge, 'RelayedMessage', library, messageId)
if (successExecutionData) {
setExecutionData(successExecutionData)
setError(DOUBLE_EXECUTION_ATTEMPT_ERROR)
} else {
const { gas } = await library.eth.getTransaction(receipt.transactionHash)
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validator: account,
txHash: receipt.transactionHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
setError(gas === receipt.gasUsed ? EXECUTION_OUT_OF_GAS_ERROR : EXECUTION_FAILED_ERROR)
}
} else {
setError(e.message)
}
})
}, },
[ [
manualExecution, manualExecution,

@ -2,6 +2,7 @@ import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { InfoIcon } from './InfoIcon' import { InfoIcon } from './InfoIcon'
import { CloseIcon } from './CloseIcon' import { CloseIcon } from './CloseIcon'
import { ExplorerTxLink } from './ExplorerTxLink'
const StyledErrorAlert = styled.div` const StyledErrorAlert = styled.div`
border: 1px solid var(--failed-color); border: 1px solid var(--failed-color);
@ -15,17 +16,33 @@ const CloseIconContainer = styled.div`
` `
const TextContainer = styled.div` const TextContainer = styled.div`
white-space: pre-wrap;
flex-direction: column; flex-direction: column;
` `
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => ( export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
<div className="row is-center"> const errorArray = error.split('%link')
<StyledErrorAlert className="col-10 is-vertical-align row"> const text = errorArray[0]
<InfoIcon color="var(--failed-color)" /> let link
<TextContainer className="col-10">{error}</TextContainer> if (errorArray.length > 1) {
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}> link = (
<CloseIcon color="var(--failed-color)" /> <ExplorerTxLink href={errorArray[1]} target="_blank" rel="noopener noreferrer">
</CloseIconContainer> {errorArray[1]}
</StyledErrorAlert> </ExplorerTxLink>
</div> )
) }
return (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">
{text}
{link}
</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
}

@ -66,3 +66,12 @@ export const VALIDATOR_CONFIRMATION_STATUS = {
export const SEARCHING_TX = 'Searching Transaction...' export const SEARCHING_TX = 'Searching Transaction...'
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.` export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
export const DOUBLE_EXECUTION_ATTEMPT_ERROR = `Your execution transaction has been reverted.
However, the execution completed successfully in the transaction sent by a different party.`
export const EXECUTION_FAILED_ERROR = `Your execution transaction has been reverted.
Please, contact the support by messaging on %linkhttps://forum.poa.network/c/support`
export const EXECUTION_OUT_OF_GAS_ERROR = `Your execution transaction has been reverted due to Out-of-Gas error.
Please, resend the transaction and provide more gas to it.`

@ -11,6 +11,37 @@ import {
import { getBlock, MessageObject } from './web3' import { getBlock, MessageObject } from './web3'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
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
// so there is no need to limit the range of the block to reduce the network traffic
const events: EventData[] = await contract.getPastEvents(eventName, {
fromBlock: 0,
toBlock: 'latest',
filter: {
messageId
}
})
if (events.length > 0) {
const event = events[0]
const [txReceipt, block] = await Promise.all([
web3.eth.getTransactionReceipt(event.transactionHash),
getBlock(web3, event.blockNumber)
])
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
return {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator: validatorAddress,
txHash: event.transactionHash,
timestamp: blockTimestamp,
executionResult: event.returnValues.status
}
}
return null
}
export const getFinalizationEvent = async ( export const getFinalizationEvent = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
eventName: string, eventName: string,
@ -29,32 +60,9 @@ export const getFinalizationEvent = async (
setExecutionEventsFetched: Function setExecutionEventsFetched: Function
) => { ) => {
if (!contract || !web3 || !waitingBlocksResolved) return if (!contract || !web3 || !waitingBlocksResolved) return
// Since it filters by the message id, only one event will be fetched const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id)
// so there is no need to limit the range of the block to reduce the network traffic if (successExecutionData) {
const events: EventData[] = await contract.getPastEvents(eventName, { setResult(successExecutionData)
fromBlock: 0,
toBlock: 'latest',
filter: {
messageId: message.id
}
})
if (events.length > 0) {
const event = events[0]
const [txReceipt, block] = await Promise.all([
web3.eth.getTransactionReceipt(event.transactionHash),
getBlock(web3, event.blockNumber)
])
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
setResult({
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator: validatorAddress,
txHash: event.transactionHash,
timestamp: blockTimestamp,
executionResult: event.returnValues.status
})
} else { } else {
setExecutionEventsFetched(true) setExecutionEventsFetched(true)
// If event is defined, it means it is a message from Home to Foreign // If event is defined, it means it is a message from Home to Foreign