Display Age field for validator signatures in ALM (#371)

This commit is contained in:
Gerardo Nardelli 2020-06-25 18:18:54 -03:00 committed by GitHub
parent 0eb7c41278
commit 2ca07e998a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 244 additions and 48 deletions

@ -14,6 +14,7 @@
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3",
"customize-cra": "^1.0.0",
"date-fns": "^2.14.0",
"fast-memoize": "^2.5.2",

@ -57,7 +57,7 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td>
<td className="text-center">{getExecutionStatusElement(executionData.status)}</td>
<td className="text-center">
<ExplorerTxLink href={txExplorerLink} target="blank">
<ExplorerTxLink href={txExplorerLink} target="_blank">
{executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''}
</ExplorerTxLink>
</td>

@ -1,11 +1,12 @@
import React from 'react'
import { formatTxHashExtended } from '../utils/networks'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components'
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
const Thead = styled.thead`
border-bottom: 2px solid #9e9e9e;
@ -49,7 +50,8 @@ export const ValidatorsConfirmations = ({
<Thead>
<tr>
<th>Validator</th>
<th className="is-center">Confirmations</th>
<th className="text-center">Status</th>
<th className="text-center">Age</th>
</tr>
</Thead>
<tbody>
@ -57,10 +59,25 @@ export const ValidatorsConfirmations = ({
const filteredConfirmation = confirmations.filter(c => c.validator === validator)
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
const elementIfNoTimestamp =
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
<SimpleLoading />
) : (
''
)
return (
<tr key={i}>
<td>{windowWidth < 850 ? formatTxHashExtended(validator) : validator}</td>
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td>
<td className="text-center">{getValidatorStatusElement(displayedStatus)}</td>
<td className="text-center">
<ExplorerTxLink href={explorerLink} target="_blank">
{confirmation && confirmation.timestamp > 0
? formatTimestamp(confirmation.timestamp)
: elementIfNoTimestamp}
</ExplorerTxLink>
</td>
</tr>
)
})}

@ -23,6 +23,7 @@ export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
export const SUBMIT_SIGNATURE_HASH = '630cea8e'
export const EXECUTE_SIGNATURES_HASH = '3f7658fd'
export const CACHE_KEY_SUCCESS = 'success-confirmation-validator-'
export const CACHE_KEY_FAILED = 'failed-confirmation-validator-'
export const CACHE_KEY_EXECUTION_FAILED = 'failed-execution-validator-'

@ -21,7 +21,8 @@ import {
getValidatorFailedTransactionsForMessage,
getExecutionFailedTransactionForMessage,
getValidatorPendingTransactionsForMessage,
getExecutionPendingTransactionsForMessage
getExecutionPendingTransactionsForMessage,
getValidatorSuccessTransactionsForMessage
} from '../utils/explorer'
export interface useMessageConfirmationsParams {
@ -33,11 +34,16 @@ export interface useMessageConfirmationsParams {
validatorList: string[]
}
export interface ConfirmationParam {
export interface BasicConfirmationParam {
validator: string
status: string
}
export interface ConfirmationParam extends BasicConfirmationParam {
txHash: string
timestamp: number
}
export interface ExecutionData {
status: string
validator: string
@ -221,7 +227,8 @@ export const useMessageConfirmations = ({
getValidatorFailedTransactionsForMessage,
setFailedConfirmations,
getValidatorPendingTransactionsForMessage,
setPendingConfirmations
setPendingConfirmations,
getValidatorSuccessTransactionsForMessage
)
return () => {

@ -4,6 +4,7 @@ import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constan
import { getTransactionStatusDescription } from '../utils/networks'
import { useStateProvider } from '../state/StateProvider'
import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3'
import useInterval from '@use-it/interval'
export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => {
const { home, foreign } = useStateProvider()
@ -14,6 +15,12 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai
const [timestamp, setTimestamp] = useState(0)
const [loading, setLoading] = useState(true)
// Update description so the time displayed is accurate
useInterval(() => {
if (!status || !timestamp || !description) return
setDescription(getTransactionStatusDescription(status, timestamp))
}, 30000)
useEffect(
() => {
const subscriptions: Array<number> = []

@ -1,8 +1,12 @@
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
class ValidatorsCache {
private readonly store: { [key: string]: boolean }
private readonly dataStore: { [key: string]: ConfirmationParam }
constructor() {
this.store = {}
this.dataStore = {}
}
get(key: string) {
@ -12,6 +16,14 @@ class ValidatorsCache {
set(key: string, value: boolean) {
this.store[key] = value
}
getData(key: string) {
return this.dataStore[key]
}
setData(key: string, value: ConfirmationParam) {
this.dataStore[key] = value
}
}
export default new ValidatorsCache()

@ -154,6 +154,31 @@ export const getFailedTransactions = async (
return transactions.filter(t => t.isError !== '0')
}
export const getSuccessTransactions = async (
account: string,
to: string,
startTimestamp: number,
endTimestamp: number,
api: string,
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
): Promise<APITransaction[]> => {
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
return transactions.filter(t => t.isError === '0')
}
export const filterValidatorSignatureTransaction = (
transactions: APITransaction[],
messageData: string
): APITransaction[] => {
const messageDataValue = messageData.replace('0x', '')
return transactions.filter(
t =>
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
t.input.includes(messageDataValue)
)
}
export const getValidatorFailedTransactionsForMessage = async ({
account,
to,
@ -170,12 +195,26 @@ export const getValidatorFailedTransactionsForMessage = async ({
fetchAccountTransactionsFromBlockscout
)
const messageDataValue = messageData.replace('0x', '')
return failedTransactions.filter(
t =>
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
t.input.includes(messageDataValue)
return filterValidatorSignatureTransaction(failedTransactions, messageData)
}
export const getValidatorSuccessTransactionsForMessage = async ({
account,
to,
messageData,
startTimestamp,
endTimestamp
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
const transactions = await getSuccessTransactions(
account,
to,
startTimestamp,
endTimestamp,
HOME_EXPLORER_API,
fetchAccountTransactionsFromBlockscout
)
return filterValidatorSignatureTransaction(transactions, messageData)
}
export const getExecutionFailedTransactionForMessage = async ({

@ -3,6 +3,7 @@ import { Contract } from 'web3-eth-contract'
import validatorsCache from '../services/ValidatorsCache'
import {
CACHE_KEY_FAILED,
CACHE_KEY_SUCCESS,
HOME_RPC_POLLING_INTERVAL,
ONE_DAY_TIMESTAMP,
VALIDATOR_CONFIRMATION_STATUS
@ -13,14 +14,14 @@ import {
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
export const getValidatorConfirmation = (
web3: Web3,
hashMsg: string,
bridgeContract: Contract,
confirmationContractMethod: Function
) => async (validator: string): Promise<ConfirmationParam> => {
) => async (validator: string): Promise<BasicConfirmationParam> => {
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
const signatureFromCache = validatorsCache.get(hashSenderMsg)
@ -45,20 +46,65 @@ export const getValidatorConfirmation = (
}
}
export const getValidatorSuccessTransaction = (
bridgeContract: Contract,
messageData: string,
timestamp: number,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}`
const fromCache = validatorsCache.getData(validatorCacheKey)
if (fromCache && fromCache.txHash) {
return fromCache
}
const transactions = await getSuccessTransactions({
account: validatorData.validator,
to: bridgeContract.options.address,
messageData,
startTimestamp: timestamp,
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
})
let txHashTimestamp = 0
let txHash = ''
const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS
if (transactions.length > 0) {
const tx = transactions[0]
txHashTimestamp = parseInt(tx.timeStamp)
txHash = tx.hash
// cache the result
validatorsCache.setData(validatorCacheKey, {
validator,
status,
txHash,
timestamp: txHashTimestamp
})
}
return {
validator,
status,
txHash,
timestamp: txHashTimestamp
}
}
export const getValidatorFailedTransaction = (
bridgeContract: Contract,
messageData: string,
timestamp: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}`
const failedFromCache = validatorsCache.get(validatorCacheKey)
const failedFromCache = validatorsCache.getData(validatorCacheKey)
if (failedFromCache) {
return {
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.FAILED
}
if (failedFromCache && failedFromCache.txHash) {
return failedFromCache
}
const failedTransactions = await getFailedTransactions({
@ -71,14 +117,27 @@ export const getValidatorFailedTransaction = (
const newStatus =
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
let txHashTimestamp = 0
let txHash = ''
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
if (failedTransactions.length > 0) {
validatorsCache.set(validatorCacheKey, true)
const failedTx = failedTransactions[0]
txHashTimestamp = parseInt(failedTx.timeStamp)
txHash = failedTx.hash
validatorsCache.setData(validatorCacheKey, {
validator: validatorData.validator,
status: newStatus,
txHash,
timestamp: txHashTimestamp
})
}
return {
validator: validatorData.validator,
status: newStatus
status: newStatus,
txHash,
timestamp: txHashTimestamp
}
}
@ -86,7 +145,7 @@ export const getValidatorPendingTransaction = (
bridgeContract: Contract,
messageData: string,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const failedTransactions = await getPendingTransactions({
account: validatorData.validator,
to: bridgeContract.options.address,
@ -96,9 +155,20 @@ export const getValidatorPendingTransaction = (
const newStatus =
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
let timestamp = 0
let txHash = ''
if (failedTransactions.length > 0) {
const failedTx = failedTransactions[0]
timestamp = Math.floor(new Date().getTime() / 1000.0)
txHash = failedTx.hash
}
return {
validator: validatorData.validator,
status: newStatus
status: newStatus,
txHash,
timestamp
}
}
@ -117,9 +187,14 @@ export const getConfirmationsForTx = async (
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingConfirmations: Function
setPendingConfirmations: Function,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
) => {
if (!web3 || !validatorList || !bridgeContract || !waitingBlocksResolved) return
// If all the information was not collected, then it should retry
let shouldRetry = false
const hashMsg = web3.utils.soliditySha3Raw(messageData)
let validatorConfirmations = await Promise.all(
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
@ -174,6 +249,48 @@ export const getConfirmationsForTx = async (
)
if (missingConfirmations.length > 0) {
shouldRetry = true
}
} else {
// If signatures collected, it should set other signatures as not required
const notRequiredConfirmations = notSuccessConfirmations.map(c => ({
validator: c.validator,
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
}))
validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations]
setSignatureCollected(true)
}
// Set confirmations to update UI and continue requesting the transactions for the signatures
setResult(validatorConfirmations)
// get transactions from success signatures
const successConfirmationWithData = await Promise.all(
validatorConfirmations
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
.map(getValidatorSuccessTransaction(bridgeContract, messageData, timestamp, getSuccessTransactions))
)
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
const updatedValidatorConfirmations = [...validatorConfirmations]
if (successConfirmationWithTxFound.length > 0) {
successConfirmationWithTxFound.forEach(validatorData => {
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator)
updatedValidatorConfirmations[index] = validatorData
})
}
setResult(updatedValidatorConfirmations)
// Retry if not all transaction were found for validator confirmations
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) {
shouldRetry = true
}
if (shouldRetry) {
const timeoutId = setTimeout(
() =>
getConfirmationsForTx(
@ -191,21 +308,11 @@ export const getConfirmationsForTx = async (
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations
setPendingConfirmations,
getSuccessTransactions
),
HOME_RPC_POLLING_INTERVAL
)
subscriptions.push(timeoutId)
}
} else {
// If signatures collected, it should set other signatures as not required
const notRequiredConfirmations = notSuccessConfirmations.map(c => ({
validator: c.validator,
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
}))
validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations]
setSignatureCollected(true)
}
setResult(validatorConfirmations)
}

@ -3220,6 +3220,11 @@
lodash.unescape "4.0.1"
semver "5.5.0"
"@use-it/interval@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@use-it/interval/-/interval-0.1.3.tgz#5d1096b2295d7a5dda8e8022f3abb5f9d9ef27f8"
integrity sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ==
"@web3-js/scrypt-shim@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc"