diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx index e7e2befa..4f999257 100644 --- a/alm/src/components/ConfirmationsContainer.tsx +++ b/alm/src/components/ConfirmationsContainer.tsx @@ -1,9 +1,9 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { TransactionReceipt } from 'web3-eth' import { useMessageConfirmations } from '../hooks/useMessageConfirmations' import { MessageObject } from '../utils/web3' import styled from 'styled-components' -import { CONFIRMATIONS_STATUS } from '../config/constants' +import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions' import { SimpleLoading } from './commons/Loading' import { ValidatorsConfirmations } from './ValidatorsConfirmations' @@ -54,7 +54,9 @@ export const ConfirmationsContainer = ({ home: { name: homeName }, foreign: { name: foreignName } } = useStateProvider() - const { requiredSignatures, validatorList } = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0) + const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0) + const [executionBlockNumber, setExecutionBlockNumber] = useState(0) + const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest') const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt }) const { confirmations, @@ -71,11 +73,21 @@ export const ConfirmationsContainer = ({ fromHome, homeStartBlock, foreignStartBlock, - requiredSignatures, - validatorList, + requiredSignatures: src.requiredSignatures, + validatorList: src.validatorList, + targetValidatorList: dst.validatorList, blockConfirmations }) + useEffect( + () => { + if (executionBlockNumber || executionData.status !== VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS) return + + setExecutionBlockNumber(executionData.blockNumber) + }, + [executionData.status, executionBlockNumber, executionData.blockNumber] + ) + const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL const parseDescription = () => { @@ -114,9 +126,9 @@ export const ConfirmationsContainer = ({ dst.validatorList.includes(c.validator)) : confirmations} + requiredSignatures={dst.requiredSignatures} + validatorList={dst.validatorList} waitingBlocksResolved={waitingBlocksResolved} /> {signatureCollected && ( @@ -124,10 +136,12 @@ export const ConfirmationsContainer = ({ message={message} executionData={executionData} isHome={!fromHome} - signatureCollected={signatureCollected} + confirmations={confirmations} setExecutionData={setExecutionData} executionEventsFetched={executionEventsFetched} setPendingExecution={setPendingExecution} + dstRequiredSignatures={dst.requiredSignatures} + dstValidatorList={dst.validatorList} /> )} diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx index 38cde65a..18008370 100644 --- a/alm/src/components/ExecutionConfirmation.tsx +++ b/alm/src/components/ExecutionConfirmation.tsx @@ -4,7 +4,7 @@ import { useWindowWidth } from '@react-hook/window-size' import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' -import { ExecutionData } from '../hooks/useMessageConfirmations' +import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations' import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { ExplorerTxLink } from './commons/ExplorerTxLink' import { Thead, AgeTd, StatusTd } from './commons/Table' @@ -22,20 +22,24 @@ export interface ExecutionConfirmationParams { message: MessageObject executionData: ExecutionData setExecutionData: Function - signatureCollected: boolean | string[] + confirmations: ConfirmationParam[] isHome: boolean executionEventsFetched: boolean setPendingExecution: Function + dstRequiredSignatures: number + dstValidatorList: string[] } export const ExecutionConfirmation = ({ message, executionData, setExecutionData, - signatureCollected, + confirmations, isHome, executionEventsFetched, - setPendingExecution + setPendingExecution, + dstRequiredSignatures, + dstValidatorList }: ExecutionConfirmationParams) => { const { foreign } = useStateProvider() const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false) @@ -152,9 +156,11 @@ export const ExecutionConfirmation = ({ safeExecutionAvailable={safeExecutionAvailable} messageData={message.data} setExecutionData={setExecutionData} - signatureCollected={signatureCollected as string[]} + confirmations={confirmations} setPendingExecution={setPendingExecution} setError={setError} + requiredSignatures={dstRequiredSignatures} + validatorList={dstValidatorList} /> )} diff --git a/alm/src/components/ManualExecutionButton.tsx b/alm/src/components/ManualExecutionButton.tsx index a84bca27..74abe4ab 100644 --- a/alm/src/components/ManualExecutionButton.tsx +++ b/alm/src/components/ManualExecutionButton.tsx @@ -14,7 +14,7 @@ import { useStateProvider } from '../state/StateProvider' import { signatureToVRS, packSignatures } from '../utils/signatures' import { getSuccessExecutionData } from '../utils/getFinalizationEvent' import { TransactionReceipt } from 'web3-eth' -import { useValidatorContract } from '../hooks/useValidatorContract' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' const ActionButton = styled.button` color: var(--button-color); @@ -31,18 +31,22 @@ interface ManualExecutionButtonParams { safeExecutionAvailable: boolean messageData: string setExecutionData: Function - signatureCollected: string[] + confirmations: ConfirmationParam[] setPendingExecution: Function setError: Function + requiredSignatures: number + validatorList: string[] } export const ManualExecutionButton = ({ safeExecutionAvailable, messageData, setExecutionData, - signatureCollected, + confirmations, setPendingExecution, - setError + setError, + requiredSignatures, + validatorList }: ManualExecutionButtonParams) => { const { foreign } = useStateProvider() const { library, activate, account, active } = useWeb3React() @@ -52,15 +56,13 @@ export const ManualExecutionButton = ({ const [title, setTitle] = useState('Loading') const [validSignatures, setValidSignatures] = useState([]) - const { requiredSignatures, validatorList } = useValidatorContract(false, 'latest') - useEffect( () => { if ( !foreign.bridgeContract || !foreign.web3 || - !signatureCollected || - !signatureCollected.length || + !confirmations || + !confirmations.length || !requiredSignatures || !validatorList || !validatorList.length @@ -68,39 +70,20 @@ export const ManualExecutionButton = ({ return const signatures = [] - const remainingValidators = Object.fromEntries(validatorList.map(validator => [validator, true])) - for (let i = 0; i < signatureCollected.length && signatures.length < requiredSignatures; i++) { - const { v, r, s } = signatureToVRS(signatureCollected[i]) + for (let i = 0; i < confirmations.length && signatures.length < requiredSignatures; i++) { + const sig = confirmations[i].signature + if (!sig) { + continue + } + const { v, r, s } = signatureToVRS(sig) const signer = foreign.web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`) if (validatorList.includes(signer)) { - delete remainingValidators[signer] - signatures.push(signatureCollected[i]) - } - } - - if (signatures.length < requiredSignatures) { - console.log('On-chain collected signatures are not enough for message execution') - const manualValidators = Object.keys(remainingValidators) - const msgHash = foreign.web3.utils.sha3(messageData)! - for (let i = 0; i < manualValidators.length && signatures.length < requiredSignatures; i++) { - try { - const overrideSignatures: { - [key: string]: string - } = require(`../snapshots/signatures_${manualValidators[i]}.json`) - if (overrideSignatures[msgHash]) { - console.log(`Adding manual signature from ${manualValidators[i]}`) - signatures.push(overrideSignatures[msgHash]) - } else { - console.log(`No manual signature from ${manualValidators[i]} was found`) - } - } catch (e) { - console.log(`Signatures overrides are not present for ${manualValidators[i]}`) - } + signatures.push(sig) } } if (signatures.length >= requiredSignatures) { - setValidSignatures(signatures) + setValidSignatures(signatures.slice(0, requiredSignatures)) setTitle('Execute') setReady(true) } else { @@ -110,11 +93,11 @@ export const ManualExecutionButton = ({ [ foreign.bridgeContract, foreign.web3, - signatureCollected, validatorList, requiredSignatures, messageData, - setValidSignatures + setValidSignatures, + confirmations ] ) diff --git a/alm/src/components/ValidatorsConfirmations.tsx b/alm/src/components/ValidatorsConfirmations.tsx index 3b7e058e..d132be77 100644 --- a/alm/src/components/ValidatorsConfirmations.tsx +++ b/alm/src/components/ValidatorsConfirmations.tsx @@ -1,7 +1,7 @@ import React from 'react' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { useWindowWidth } from '@react-hook/window-size' -import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' +import { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { SimpleLoading } from './commons/Loading' import styled from 'styled-components' import { ConfirmationParam } from '../hooks/useMessageConfirmations' @@ -31,7 +31,9 @@ export const ValidatorsConfirmations = ({ const getValidatorStatusElement = (validatorStatus = '') => { switch (validatorStatus) { case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: - return {validatorStatus} + case VALIDATOR_CONFIRMATION_STATUS.MANUAL: + case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID: + return {VALIDATOR_CONFIRMATION_STATUS.SUCCESS} case VALIDATOR_CONFIRMATION_STATUS.FAILED: return {validatorStatus} case VALIDATOR_CONFIRMATION_STATUS.PENDING: @@ -58,26 +60,28 @@ export const ValidatorsConfirmations = ({ - {validatorList.map((validator, i) => { - 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 ? ( - (displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') && - waitingBlocksResolved ? ( - SEARCHING_TX - ) : ( - - ) - ) : ( - '' - ) + {confirmations.map((confirmation, i) => { + const displayedStatus = confirmation.status + const explorerLink = getExplorerTxUrl(confirmation.txHash, true) + let elementIfNoTimestamp: any = + switch (displayedStatus) { + case '': + case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED: + if (waitingBlocksResolved) { + elementIfNoTimestamp = SEARCHING_TX + } + break + case VALIDATOR_CONFIRMATION_STATUS.WAITING: + case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: + elementIfNoTimestamp = '' + break + case VALIDATOR_CONFIRMATION_STATUS.MANUAL: + elementIfNoTimestamp = RECENT_AGE + break + } return ( - {windowWidth < 850 ? formatTxHash(validator) : validator} + {windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator} {getValidatorStatusElement(displayedStatus)} {confirmation && confirmation.timestamp > 0 ? ( diff --git a/alm/src/config/constants.ts b/alm/src/config/constants.ts index 38199350..6c537bae 100644 --- a/alm/src/config/constants.ts +++ b/alm/src/config/constants.ts @@ -55,14 +55,18 @@ export const CONFIRMATIONS_STATUS = { export const VALIDATOR_CONFIRMATION_STATUS = { SUCCESS: 'Confirmed', + MANUAL: 'Manual', EXECUTION_SUCCESS: 'Executed', FAILED: 'Failed', + FAILED_VALID: 'Failed valid', PENDING: 'Pending', WAITING: 'Waiting', NOT_REQUIRED: 'Not required', UNDEFINED: 'UNDEFINED' } +export const RECENT_AGE = 'Recent' + export const SEARCHING_TX = 'Searching Transaction...' export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.` diff --git a/alm/src/hooks/useMessageConfirmations.ts b/alm/src/hooks/useMessageConfirmations.ts index abfa3bfb..4a1dc470 100644 --- a/alm/src/hooks/useMessageConfirmations.ts +++ b/alm/src/hooks/useMessageConfirmations.ts @@ -29,17 +29,16 @@ export interface useMessageConfirmationsParams { foreignStartBlock: Maybe requiredSignatures: number validatorList: string[] + targetValidatorList: string[] blockConfirmations: number } -export interface BasicConfirmationParam { +export interface ConfirmationParam { validator: string status: string -} - -export interface ConfirmationParam extends BasicConfirmationParam { txHash: string timestamp: number + signature?: string } export interface ExecutionData { @@ -48,6 +47,7 @@ export interface ExecutionData { txHash: string timestamp: number executionResult: boolean + blockNumber: number } export const useMessageConfirmations = ({ @@ -58,6 +58,7 @@ export const useMessageConfirmations = ({ foreignStartBlock, requiredSignatures, validatorList, + targetValidatorList, blockConfirmations }: useMessageConfirmationsParams) => { const { home, foreign } = useStateProvider() @@ -65,7 +66,7 @@ export const useMessageConfirmations = ({ const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) const [waitingBlocks, setWaitingBlocks] = useState(false) const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false) - const [signatureCollected, setSignatureCollected] = useState(false) + const [signatureCollected, setSignatureCollected] = useState(false) const [executionEventsFetched, setExecutionEventsFetched] = useState(false) const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState>(null) const [executionData, setExecutionData] = useState({ @@ -73,7 +74,8 @@ export const useMessageConfirmations = ({ validator: '', txHash: '', timestamp: 0, - executionResult: false + executionResult: false, + blockNumber: 0 }) const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false) const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false) @@ -140,10 +142,9 @@ export const useMessageConfirmations = ({ // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if // the execution tx on the foreign network is waiting for block confirmations // This is executed if the message is in Home to Foreign direction only - const hasCollectedSignatures = !!signatureCollected // true or string[] useEffect( () => { - if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return + if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return let timeoutId: number let isCancelled = false @@ -179,7 +180,7 @@ export const useMessageConfirmations = ({ isCancelled = true } }, - [fromHome, home.bridgeContract, home.web3, message.data, receipt, hasCollectedSignatures] + [fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected] ) // Check if the responsible validator is waiting for block confirmations to execute the message on foreign network @@ -252,6 +253,35 @@ export const useMessageConfirmations = ({ let timeoutId: number let isCancelled = false + if (fromHome) { + if (!targetValidatorList || !targetValidatorList.length) return + const msgHash = home.web3.utils.sha3(message.data)! + const allValidators = [...validatorList, ...targetValidatorList].filter((v, i, s) => s.indexOf(v) === i) + const manualConfirmations = [] + for (let i = 0; i < allValidators.length; i++) { + try { + const overrideSignatures: { + [key: string]: string + } = require(`../snapshots/signatures_${allValidators[i]}.json`) + if (overrideSignatures[msgHash]) { + console.log(`Adding manual signature from ${allValidators[i]}`) + manualConfirmations.push({ + status: VALIDATOR_CONFIRMATION_STATUS.MANUAL, + validator: allValidators[i], + timestamp: 0, + txHash: '', + signature: overrideSignatures[msgHash] + }) + } else { + console.log(`No manual signature from ${allValidators[i]} was found`) + } + } catch (e) { + console.log(`Signatures overrides are not present for ${allValidators[i]}`) + } + } + setConfirmations(manualConfirmations) + } + getConfirmationsForTx( message.data, home.web3, @@ -284,7 +314,8 @@ export const useMessageConfirmations = ({ home.bridgeContract, requiredSignatures, waitingBlocksResolved, - homeStartBlock + homeStartBlock, + targetValidatorList ] ) diff --git a/alm/src/utils/__tests__/explorer.test.ts b/alm/src/utils/__tests__/explorer.test.ts index 7cdf8417..36041691 100644 --- a/alm/src/utils/__tests__/explorer.test.ts +++ b/alm/src/utils/__tests__/explorer.test.ts @@ -14,37 +14,40 @@ const messageData = '0x123456' const OTHER_HASH = 'aabbccdd' const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1' +const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' +const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' +const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' describe('getFailedTransactions', () => { test('should only return failed transactions', async () => { const to = otherAddress const transactions = [ - { isError: '0', to }, - { isError: '1', to }, - { isError: '0', to }, - { isError: '1', to }, - { isError: '1', to } + { isError: '0', to, from: validator1 }, + { isError: '1', to, from: validator1 }, + { isError: '0', to, from: validator2 }, + { isError: '1', to, from: validator2 }, + { isError: '1', to, from: validator3 } ] const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) - const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions) - expect(result.length).toEqual(3) + const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions) + expect(result.length).toEqual(1) }) }) describe('getSuccessTransactions', () => { test('should only return success transactions', async () => { const to = otherAddress const transactions = [ - { isError: '0', to }, - { isError: '1', to }, - { isError: '0', to }, - { isError: '1', to }, - { isError: '1', to } + { isError: '0', to, from: validator1 }, + { isError: '1', to, from: validator1 }, + { isError: '0', to, from: validator2 }, + { isError: '1', to, from: validator2 }, + { isError: '1', to, from: validator3 } ] const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) - const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions) - expect(result.length).toEqual(2) + const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions) + expect(result.length).toEqual(1) }) }) describe('filterValidatorSignatureTransaction', () => { diff --git a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts index 088a2d08..028cab8b 100644 --- a/alm/src/utils/__tests__/getConfirmationsForTx.test.ts +++ b/alm/src/utils/__tests__/getConfirmationsForTx.test.ts @@ -5,7 +5,7 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' import { APIPendingTransaction, APITransaction } from '../explorer' import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' -import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations' +import { ConfirmationParam } from '../../hooks/useMessageConfirmations' jest.mock('../validatorConfirmationHelpers') @@ -18,6 +18,9 @@ const messageData = '0x111111111' const web3 = { utils: { soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}` + }, + eth: { + accounts: new Web3().eth.accounts } } as Web3 const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' @@ -25,7 +28,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f' const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' const validatorList = [validator1, validator2, validator3] const signature = - '0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4' + '0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b' const bridgeContract = { methods: { signature: () => ({ @@ -61,19 +64,19 @@ describe('getConfirmationsForTx', () => { validator, status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '', timestamp: 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', timestamp: 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -110,9 +113,8 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(2) + expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) - expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature]) expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1) @@ -135,7 +137,7 @@ describe('getConfirmationsForTx', () => { expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, - { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 } ]) ) }) @@ -144,19 +146,19 @@ describe('getConfirmationsForTx', () => { validator, status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '', timestamp: 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', timestamp: 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -208,19 +210,19 @@ describe('getConfirmationsForTx', () => { validator, status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: validatorData.validator !== validator3 ? '0x123' : '', timestamp: validatorData.validator !== validator3 ? 123 : 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', timestamp: 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -257,9 +259,8 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(3) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(2) + expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) - expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature]) expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1) @@ -290,7 +291,7 @@ describe('getConfirmationsForTx', () => { expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, - { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 } ]) ) }) @@ -304,22 +305,22 @@ describe('getConfirmationsForTx', () => { ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '', timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator3 - ? VALIDATOR_CONFIRMATION_STATUS.FAILED + ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: validatorData.validator === validator3 ? '0x123' : '', timestamp: validatorData.validator === validator3 ? 123 : 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -356,9 +357,8 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(4) expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1) - expect(setSignatureCollected).toBeCalledTimes(2) + expect(setSignatureCollected).toBeCalledTimes(1) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) - expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature]) expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1) @@ -392,7 +392,7 @@ describe('getConfirmationsForTx', () => { expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, - { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 }, { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } ]) ) @@ -400,8 +400,8 @@ describe('getConfirmationsForTx', () => { expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, - { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, - { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 }, + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 } ]) ) }) @@ -414,22 +414,22 @@ describe('getConfirmationsForTx', () => { validator, status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: validatorData.validator === validator1 ? '0x123' : '', timestamp: validatorData.validator === validator1 ? 123 : 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator2 - ? VALIDATOR_CONFIRMATION_STATUS.FAILED + ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: validatorData.validator === validator2 ? '0x123' : '', timestamp: validatorData.validator === validator2 ? 123 : 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator3 @@ -507,7 +507,7 @@ describe('getConfirmationsForTx', () => { expect(res4).toEqual( expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, - { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, + { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } ]) ) @@ -521,13 +521,13 @@ describe('getConfirmationsForTx', () => { validator, status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) - getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: validatorData.validator === validator1 ? '0x123' : '', timestamp: validatorData.validator === validator1 ? 123 : 0 })) - getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator !== validator1 @@ -536,7 +536,7 @@ describe('getConfirmationsForTx', () => { txHash: validatorData.validator !== validator1 ? '0x123' : '', timestamp: validatorData.validator !== validator1 ? 123 : 0 })) - getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -632,13 +632,13 @@ describe('getConfirmationsForTx', () => { : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED })) getSuccessExecutionTransaction - .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: validatorData.validator === validator1 ? '0x100' : '', timestamp: validatorData.validator === validator1 ? 100 : 0 })) - .mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: @@ -646,7 +646,7 @@ describe('getConfirmationsForTx', () => { timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : '' })) getValidatorFailedTransaction - .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator2 @@ -655,18 +655,20 @@ describe('getConfirmationsForTx', () => { txHash: validatorData.validator === validator2 ? '0x200' : '', timestamp: validatorData.validator === validator2 ? 200 : 0 })) - .mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementation(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator2 || validatorData.validator === validator4 - ? VALIDATOR_CONFIRMATION_STATUS.FAILED + ? validatorData.validator === validator2 + ? VALIDATOR_CONFIRMATION_STATUS.FAILED + : VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: validatorData.validator === validator2 ? '0x200' : validatorData.validator === validator4 ? '0x400' : '', timestamp: validatorData.validator === validator2 ? 200 : validatorData.validator === validator4 ? 400 : '' })) getValidatorPendingTransaction - .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: validatorData.validator === validator3 @@ -675,7 +677,7 @@ describe('getConfirmationsForTx', () => { txHash: validatorData.validator === validator3 ? '0x300' : '', timestamp: validatorData.validator === validator3 ? 300 : 0 })) - .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ + .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ validator: validatorData.validator, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, txHash: '', @@ -716,7 +718,7 @@ describe('getConfirmationsForTx', () => { expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1) - expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true) expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1) @@ -783,14 +785,13 @@ describe('getConfirmationsForTx', () => { expect(setResult).toBeCalledTimes(7) expect(getValidatorConfirmation).toBeCalledTimes(2) expect(getSuccessExecutionTransaction).toBeCalledTimes(2) - expect(setSignatureCollected).toBeCalledTimes(3) + expect(setSignatureCollected).toBeCalledTimes(2) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[1][0]).toEqual(true) - expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature]) expect(getValidatorFailedTransaction).toBeCalledTimes(2) expect(setFailedConfirmations).toBeCalledTimes(2) - expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) + expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true) expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false) expect(getValidatorPendingTransaction).toBeCalledTimes(1) @@ -805,7 +806,7 @@ describe('getConfirmationsForTx', () => { expect.arrayContaining([ { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, - { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, + { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 }, { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } ]) ) @@ -822,7 +823,7 @@ describe('getConfirmationsForTx', () => { { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 }, - { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x400', timestamp: 400 } + { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 } ]) ) }) diff --git a/alm/src/utils/__tests__/getFinalizationEvent.test.ts b/alm/src/utils/__tests__/getFinalizationEvent.test.ts index 98c4a4d7..c671ffd6 100644 --- a/alm/src/utils/__tests__/getFinalizationEvent.test.ts +++ b/alm/src/utils/__tests__/getFinalizationEvent.test.ts @@ -87,7 +87,8 @@ describe('getFinalizationEvent', () => { status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS, txHash, timestamp, - executionResult: true + executionResult: true, + blockNumber: 5523145 }) expect(getFailedExecution).toBeCalledTimes(0) @@ -237,7 +238,8 @@ describe('getFinalizationEvent', () => { status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash, timestamp: expect.any(Number), - executionResult: false + executionResult: false, + blockNumber: 0 }) expect(getFailedExecution).toBeCalledTimes(0) @@ -294,7 +296,8 @@ describe('getFinalizationEvent', () => { status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash, timestamp: expect.any(Number), - executionResult: false + executionResult: false, + blockNumber: expect.any(Number) }) expect(getFailedExecution).toBeCalledTimes(1) diff --git a/alm/src/utils/explorer.ts b/alm/src/utils/explorer.ts index 6b1f53bf..4537fd45 100644 --- a/alm/src/utils/explorer.ts +++ b/alm/src/utils/explorer.ts @@ -12,6 +12,7 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' export interface APITransaction { + from: string timeStamp: string isError: string input: string @@ -54,7 +55,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock, url.searchParams.append('module', 'account') url.searchParams.append('action', 'txlist') url.searchParams.append('address', account) - url.searchParams.append('filterby', 'from') + url.searchParams.append('filterby', 'to') url.searchParams.append('startblock', startBlock.toString()) url.searchParams.append('endblock', endBlock.toString()) @@ -64,7 +65,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock, return [] } - return result.result + return result.result || [] } export const fetchPendingTransactions = async ({ @@ -180,7 +181,9 @@ export const getLogs = async ( if (topics[i] !== null) { url.searchParams.append(`topic${i}`, topics[i] as string) for (let j = 0; j < i; j++) { - url.searchParams.append(`topic${j}_${i}_opr`, 'and') + if (topics[j] !== null) { + url.searchParams.append(`topic${j}_${i}_opr`, 'and') + } } } } @@ -194,7 +197,7 @@ export const getLogs = async ( })) } -const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase() +const filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase() export const getFailedTransactions = async ( account: string, @@ -204,9 +207,9 @@ export const getFailedTransactions = async ( api: string, getAccountTransactionsMethod = getAccountTransactions ): Promise => { - const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api }) + const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api }) - return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to)) + return transactions.filter(t => t.isError !== '0').filter(filterSender(account)) } export const getSuccessTransactions = async ( @@ -217,9 +220,9 @@ export const getSuccessTransactions = async ( api: string, getAccountTransactionsMethod = getAccountTransactions ): Promise => { - const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api }) + const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api }) - return transactions.filter(t => t.isError === '0').filter(filterReceiver(to)) + return transactions.filter(t => t.isError === '0').filter(filterSender(account)) } export const filterValidatorSignatureTransaction = ( diff --git a/alm/src/utils/getConfirmationsForTx.ts b/alm/src/utils/getConfirmationsForTx.ts index bc725cc5..c9cf0634 100644 --- a/alm/src/utils/getConfirmationsForTx.ts +++ b/alm/src/utils/getConfirmationsForTx.ts @@ -2,26 +2,37 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer' -import { getAffirmationsSigned, getMessagesSigned } from './contract' import { getValidatorConfirmation, getValidatorFailedTransaction, getValidatorPendingTransaction, getSuccessExecutionTransaction } from './validatorConfirmationHelpers' -import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' +import { signatureToVRS } from './signatures' -const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => { +const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => { const confirmations = [...oldConfirmations] newConfirmations.forEach(validatorData => { const index = confirmations.findIndex(e => e.validator === validatorData.validator) + if (index === -1) { + confirmations.push(validatorData) + return + } const currentStatus = confirmations[index].status const newStatus = validatorData.status if ( - (validatorData as ConfirmationParam).txHash || + validatorData.txHash || + !!validatorData.signature || (newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED) ) { - confirmations[index] = validatorData + confirmations[index] = { + status: validatorData.status, + validator: validatorData.validator, + timestamp: confirmations[index].timestamp || validatorData.timestamp, + txHash: confirmations[index].txHash || validatorData.txHash, + signature: confirmations[index].signature || validatorData.signature + } } }) return confirmations @@ -45,19 +56,17 @@ export const getConfirmationsForTx = async ( setPendingConfirmations: Function, getSuccessTransactions: (args: GetTransactionParams) => Promise ) => { - const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned - const hashMsg = web3.utils.soliditySha3Raw(messageData) let validatorConfirmations = await Promise.all( - validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod)) + validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, fromHome)) ) - const updateConfirmations = (confirmations: BasicConfirmationParam[]) => { + const updateConfirmations = (confirmations: ConfirmationParam[]) => { if (confirmations.length === 0) { return } validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations) - setResult((currentConfirmations: BasicConfirmationParam[]) => { + setResult((currentConfirmations: ConfirmationParam[]) => { if (currentConfirmations && currentConfirmations.length) { return mergeConfirmations(currentConfirmations, confirmations) } @@ -67,7 +76,7 @@ export const getConfirmationsForTx = async ( const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS) - const hasEnoughSignatures = successConfirmations.length === requiredSignatures + const hasEnoughSignatures = successConfirmations.length >= requiredSignatures updateConfirmations(validatorConfirmations) setSignatureCollected(hasEnoughSignatures) @@ -76,11 +85,15 @@ export const getConfirmationsForTx = async ( setPendingConfirmations(false) if (fromHome) { // fetch collected signatures for possible manual processing - setSignatureCollected( - await Promise.all( - Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call()) - ) + const signatures = await Promise.all( + Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call()) ) + const confirmations = signatures.flatMap(sig => { + const { v, r, s } = signatureToVRS(sig) + const address = web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`) + return successConfirmations.filter(c => c.validator === address).map(c => ({ ...c, signature: sig })) + }) + updateConfirmations(confirmations) } } @@ -115,13 +128,13 @@ export const getConfirmationsForTx = async ( // Check if confirmation failed const validatorFailedConfirmationsChecks = await Promise.all( undefinedConfirmations.map( - getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions) + getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions) ) ) let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( - c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED + c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID ) - if (hasEnoughSignatures) { + if (hasEnoughSignatures && !fromHome) { const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0)) validatorFailedConfirmations = validatorFailedConfirmations.map( c => @@ -129,11 +142,13 @@ export const getConfirmationsForTx = async ( ? c : { ...c, - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS + status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID } ) } - setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures) + setFailedConfirmations( + !hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED) + ) updateConfirmations(validatorFailedConfirmations) const missingConfirmations = validatorConfirmations.filter( @@ -144,7 +159,9 @@ export const getConfirmationsForTx = async ( // If signatures collected, it should set other signatures not found as not required const notRequiredConfirmations = missingConfirmations.map(c => ({ validator: c.validator, - status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED + status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, + timestamp: 0, + txHash: '' })) updateConfirmations(notRequiredConfirmations) } diff --git a/alm/src/utils/getFinalizationEvent.ts b/alm/src/utils/getFinalizationEvent.ts index 4489271a..4e849ccb 100644 --- a/alm/src/utils/getFinalizationEvent.ts +++ b/alm/src/utils/getFinalizationEvent.ts @@ -63,7 +63,8 @@ export const getSuccessExecutionData = async ( validator: validatorAddress, txHash: event.transactionHash, timestamp: blockTimestamp, - executionResult: event.returnValues.status + executionResult: event.returnValues.status, + blockNumber: event.blockNumber } } return null @@ -115,7 +116,8 @@ export const getFinalizationEvent = async ( validator: validator, txHash: pendingTx.hash, timestamp: nowTimestamp, - executionResult: false + executionResult: false, + blockNumber: 0 }) setPendingExecution(true) } else { @@ -144,7 +146,8 @@ export const getFinalizationEvent = async ( validator: validator, txHash: failedTx.hash, timestamp, - executionResult: false + executionResult: false, + blockNumber: parseInt(failedTx.blockNumber) }) setFailedExecution(true) } diff --git a/alm/src/utils/validatorConfirmationHelpers.ts b/alm/src/utils/validatorConfirmationHelpers.ts index 24f364a9..988cbb6b 100644 --- a/alm/src/utils/validatorConfirmationHelpers.ts +++ b/alm/src/utils/validatorConfirmationHelpers.ts @@ -1,38 +1,45 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' -import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' +import { ConfirmationParam } from '../hooks/useMessageConfirmations' import validatorsCache from '../services/ValidatorsCache' import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer' import { homeBlockNumberProvider } from '../services/BlockNumberProvider' +import { getAffirmationsSigned, getMessagesSigned } from './contract' export const getValidatorConfirmation = ( web3: Web3, hashMsg: string, bridgeContract: Contract, - confirmationContractMethod: Function -) => async (validator: string): Promise => { + fromHome: boolean +) => async (validator: string): Promise => { const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) - const signatureFromCache = validatorsCache.get(hashSenderMsg) - if (signatureFromCache) { - return { - validator, - status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS - } + const fromCache = validatorsCache.getData(hashSenderMsg) + if (fromCache) { + return fromCache } + const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) - const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED // If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change if (confirmed) { - validatorsCache.set(hashSenderMsg, confirmed) + const confirmation: ConfirmationParam = { + status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, + validator, + timestamp: 0, + txHash: '' + } + validatorsCache.setData(hashSenderMsg, confirmation) + return confirmation } return { + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, validator, - status + timestamp: 0, + txHash: '' } } @@ -43,7 +50,7 @@ export const getSuccessExecutionTransaction = ( messageData: string, startBlock: number, getSuccessTransactions: (args: GetTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { +) => async (validatorData: ConfirmationParam): Promise => { const { validator } = validatorData const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` const fromCache = validatorsCache.getData(validatorCacheKey) @@ -87,11 +94,12 @@ export const getSuccessExecutionTransaction = ( } export const getValidatorFailedTransaction = ( + web3: Web3, bridgeContract: Contract, messageData: string, startBlock: number, getFailedTransactions: (args: GetTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { +) => async (validatorData: ConfirmationParam): Promise => { const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` const failedFromCache = validatorsCache.getData(validatorCacheKey) @@ -106,30 +114,33 @@ export const getValidatorFailedTransaction = ( startBlock, endBlock: homeBlockNumberProvider.get() || 0 }) - 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) { const failedTx = failedTransactions[0] - txHashTimestamp = parseInt(failedTx.timeStamp) - txHash = failedTx.hash - - validatorsCache.setData(validatorCacheKey, { + const confirmation: ConfirmationParam = { + status: VALIDATOR_CONFIRMATION_STATUS.FAILED, validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp - }) + txHash: failedTx.hash, + timestamp: parseInt(failedTx.timeStamp) + } + + if (failedTx.input && failedTx.input.length > 10) { + try { + const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`) + confirmation.signature = res[0] + confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID + console.log(`Adding manual signature from failed message from ${validatorData.validator}`) + } catch {} + } + validatorsCache.setData(validatorCacheKey, confirmation) + return confirmation } return { + status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, validator: validatorData.validator, - status: newStatus, - txHash, - timestamp: txHashTimestamp + txHash: '', + timestamp: 0 } } @@ -137,7 +148,7 @@ export const getValidatorPendingTransaction = ( bridgeContract: Contract, messageData: string, getPendingTransactions: (args: GetPendingTransactionParams) => Promise -) => async (validatorData: BasicConfirmationParam): Promise => { +) => async (validatorData: ConfirmationParam): Promise => { const failedTransactions = await getPendingTransactions({ account: validatorData.validator, to: bridgeContract.options.address, diff --git a/oracle/src/utils/utils.js b/oracle/src/utils/utils.js index a2a7901c..716520ce 100644 --- a/oracle/src/utils/utils.js +++ b/oracle/src/utils/utils.js @@ -169,7 +169,8 @@ function isNonceError(e) { message.includes('transaction nonce is too low') || message.includes('nonce too low') || message.includes('transaction with same nonce in the queue') || - message.includes('oldnonce') + message.includes('oldnonce') || + message.includes(`the tx doesn't have the correct nonce`) ) }