Compare commits

...

13 Commits

Author SHA1 Message Date
ce515a8635
Improve contract fetching
Some checks failed
tokenbridge / initialize (push) Has been cancelled
tokenbridge / build-e2e-images (push) Has been cancelled
tokenbridge / build-molecule-runner (push) Has been cancelled
tokenbridge / validate (build) (push) Has been cancelled
tokenbridge / validate (lint) (push) Has been cancelled
tokenbridge / validate (test) (push) Has been cancelled
tokenbridge / e2e (alm-e2e, true) (push) Has been cancelled
tokenbridge / e2e (monitor-e2e) (push) Has been cancelled
tokenbridge / e2e (oracle-e2e) (push) Has been cancelled
tokenbridge / deployment (monitor) (push) Has been cancelled
tokenbridge / deployment (multiple) (push) Has been cancelled
tokenbridge / deployment (oracle) (push) Has been cancelled
tokenbridge / deployment (repo) (push) Has been cancelled
tokenbridge / ultimate (amb) (push) Has been cancelled
tokenbridge / ultimate (erc-to-native) (push) Has been cancelled
2024-05-08 23:45:31 +00:00
Alexander Kolotov
961b12b9f3
Naming convention for oracle (#661) 2022-10-28 14:07:02 +03:00
Alexander Kolotov
ff9f3fb7d6
Merge the develop branch to the master branch, preparation to v3.7.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Periodic check for RPC sync state (#656)
2022-05-25 14:09:36 +03:00
Kirill Fedoseev
297cb67895
Periodic check for RPC sync state (#656) 2022-05-25 13:54:47 +03:00
Alexander Kolotov
bcf16144c1
Merge the develop branch to the master branch, preparation to v3.6.0
This merge contains the following set of changes:
  * [Oracle, Fix] Add missing Ganache nonce error message (#651)
  * [ALM, Improvement] ALM: show manually added signatures (#653)
2022-04-29 19:13:41 +03:00
Kirill Fedoseev
16f3e9add6
ALM: show manually added signatures (#653) 2022-04-29 16:38:08 +03:00
Ho Xuan Cuong
4af882b0ff
Add missing Ganache nonce error message (#651) 2022-04-08 13:26:55 +04:00
Alexander Kolotov
dbaf7feca7
Merge the develop branch to the master branch, preparation to v3.5.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Helpers for overriding AMB signatures (#640)
  * [Oracle, Improvement] Support manual signatures in erc to native mode (#646)
  * [ALM, Improvement] ALM: Do not show unneeded failed confirmations (#641)
  * [ALM, Improvement] ALM: warning for auto-relayed messages (#644)
  * [ALM, Fix] ALM: reorder warnings
2022-03-18 17:07:42 +03:00
Kirill Fedoseev
735aa75f81 ALM: reorder warnings 2022-03-17 17:41:33 +04:00
Kirill Fedoseev
910c3759c1
ALM: warning for auto-relayed messages (#644) 2022-03-17 14:11:14 +03:00
Kirill Fedoseev
9c2d2f404c
ALM: Do not show unneeded failed confirmations (#641) 2022-03-13 13:54:33 +03:00
Kirill Fedoseev
2b51d4c209
Support manual signatures in erc to native mode (#646) 2022-03-10 14:18:19 +03:00
Kirill Fedoseev
981231fb47
Helpers for overriding AMB signatures (#640) 2022-02-18 17:35:12 +03:00
37 changed files with 802 additions and 372 deletions

View File

@ -56,9 +56,11 @@ ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trig
ORACLE_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool` ORACLE_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer` ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer`
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer` ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer`
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool` ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer` ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer`
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer` ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer`
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
## Monitor configuration ## Monitor configuration

View File

@ -19,7 +19,7 @@ Sub-repositories maintained within this monorepo are listed below.
| Sub-repository | Description | | Sub-repository | Description |
| --- | --- | | --- | --- |
| [Oracle](oracle/README.md) | Oracle responsible for listening to bridge related events and authorizing asset transfers. | | [Oracle](oracle/README.md) | Responsible for listening to bridge related events and authorizing asset transfers. |
| [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. | | [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. |
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. | | [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle | | [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |

View File

@ -1,9 +1,9 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { useMessageConfirmations } from '../hooks/useMessageConfirmations' import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
import { MessageObject } from '../utils/web3' import { MessageObject } from '../utils/web3'
import styled from 'styled-components' 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 { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
import { ValidatorsConfirmations } from './ValidatorsConfirmations' import { ValidatorsConfirmations } from './ValidatorsConfirmations'
@ -54,7 +54,9 @@ export const ConfirmationsContainer = ({
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
} = useStateProvider() } = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) 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 { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
const { const {
confirmations, confirmations,
@ -71,11 +73,21 @@ export const ConfirmationsContainer = ({
fromHome, fromHome,
homeStartBlock, homeStartBlock,
foreignStartBlock, foreignStartBlock,
requiredSignatures, requiredSignatures: src.requiredSignatures,
validatorList, validatorList: src.validatorList,
targetValidatorList: dst.validatorList,
blockConfirmations 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 statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
const parseDescription = () => { const parseDescription = () => {
@ -114,20 +126,22 @@ export const ConfirmationsContainer = ({
</MultiLine> </MultiLine>
</StatusDescription> </StatusDescription>
<ValidatorsConfirmations <ValidatorsConfirmations
confirmations={confirmations} confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
requiredSignatures={requiredSignatures} requiredSignatures={dst.requiredSignatures}
validatorList={validatorList} validatorList={dst.validatorList}
waitingBlocksResolved={waitingBlocksResolved} waitingBlocksResolved={waitingBlocksResolved}
/> />
{signatureCollected && ( {signatureCollected && (
<ExecutionConfirmation <ExecutionConfirmation
messageData={message.data} message={message}
executionData={executionData} executionData={executionData}
isHome={!fromHome} isHome={!fromHome}
signatureCollected={signatureCollected} confirmations={confirmations}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched} executionEventsFetched={executionEventsFetched}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
dstRequiredSignatures={dst.requiredSignatures}
dstValidatorList={dst.validatorList}
/> />
)} )}
</StyledConfirmationContainer> </StyledConfirmationContainer>

View File

@ -4,38 +4,47 @@ import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants' import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components' import styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table' import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton' import { ManualExecutionButton } from './ManualExecutionButton'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
import { WarningAlert } from './commons/WarningAlert'
import { ErrorAlert } from './commons/ErrorAlert'
const StyledExecutionConfirmation = styled.div` const StyledExecutionConfirmation = styled.div`
margin-top: 30px; margin-top: 30px;
` `
export interface ExecutionConfirmationParams { export interface ExecutionConfirmationParams {
messageData: string message: MessageObject
executionData: ExecutionData executionData: ExecutionData
setExecutionData: Function setExecutionData: Function
signatureCollected: boolean | string[] confirmations: ConfirmationParam[]
isHome: boolean isHome: boolean
executionEventsFetched: boolean executionEventsFetched: boolean
setPendingExecution: Function setPendingExecution: Function
dstRequiredSignatures: number
dstValidatorList: string[]
} }
export const ExecutionConfirmation = ({ export const ExecutionConfirmation = ({
messageData, message,
executionData, executionData,
setExecutionData, setExecutionData,
signatureCollected, confirmations,
isHome, isHome,
executionEventsFetched, executionEventsFetched,
setPendingExecution setPendingExecution,
dstRequiredSignatures,
dstValidatorList
}: ExecutionConfirmationParams) => { }: ExecutionConfirmationParams) => {
const { foreign } = useStateProvider() const { foreign } = useStateProvider()
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false) const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
const [error, setError] = useState('')
const [warning, setWarning] = useState('')
const availableManualExecution = const availableManualExecution =
!isHome && !isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING || (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
@ -67,9 +76,27 @@ export const ExecutionConfirmation = ({
[availableManualExecution, foreign.bridgeContract] [availableManualExecution, foreign.bridgeContract]
) )
useEffect(
() => {
if (!message.data || !executionData || !availableManualExecution) return
try {
const fileName = 'warnRules'
const rules: WarnRule[] = require(`../snapshots/${fileName}.json`)
for (let rule of rules) {
if (matchesRule(rule, message)) {
setWarning(rule.message)
return
}
}
} catch (e) {}
},
[availableManualExecution, executionData, message, message.data, setWarning]
)
const getExecutionStatusElement = (validatorStatus = '') => { const getExecutionStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
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>
@ -87,6 +114,8 @@ export const ExecutionConfirmation = ({
return ( return (
<StyledExecutionConfirmation> <StyledExecutionConfirmation>
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
<table> <table>
<Thead> <Thead>
<tr> <tr>
@ -125,10 +154,13 @@ export const ExecutionConfirmation = ({
<td> <td>
<ManualExecutionButton <ManualExecutionButton
safeExecutionAvailable={safeExecutionAvailable} safeExecutionAvailable={safeExecutionAvailable}
messageData={messageData} messageData={message.data}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]} confirmations={confirmations}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
setError={setError}
requiredSignatures={dstRequiredSignatures}
validatorList={dstValidatorList}
/> />
</td> </td>
)} )}

View File

@ -8,7 +8,6 @@ import { TransactionReceipt } from 'web3-eth'
import { InfoAlert } from './commons/InfoAlert' import { InfoAlert } from './commons/InfoAlert'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants' import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
import { ErrorAlert } from './commons/ErrorAlert'
const StyledMainPage = styled.div` const StyledMainPage = styled.div`
text-align: center; text-align: center;
@ -52,7 +51,7 @@ export interface FormSubmitParams {
export const MainPage = () => { export const MainPage = () => {
const history = useHistory() const history = useHistory()
const { home, foreign, error, setError } = useStateProvider() const { home, foreign } = useStateProvider()
const [networkName, setNetworkName] = useState('') const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null) const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false) const [showInfoAlert, setShowInfoAlert] = useState(false)
@ -132,7 +131,6 @@ export const MainPage = () => {
</AlertP> </AlertP>
</InfoAlert> </InfoAlert>
)} )}
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} /> <Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
<Route <Route
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']} path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}

View File

@ -14,6 +14,7 @@ import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures' import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent' import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
const ActionButton = styled.button` const ActionButton = styled.button`
color: var(--button-color); color: var(--button-color);
@ -30,22 +31,75 @@ interface ManualExecutionButtonParams {
safeExecutionAvailable: boolean safeExecutionAvailable: boolean
messageData: string messageData: string
setExecutionData: Function setExecutionData: Function
signatureCollected: string[] confirmations: ConfirmationParam[]
setPendingExecution: Function setPendingExecution: Function
setError: Function
requiredSignatures: number
validatorList: string[]
} }
export const ManualExecutionButton = ({ export const ManualExecutionButton = ({
safeExecutionAvailable, safeExecutionAvailable,
messageData, messageData,
setExecutionData, setExecutionData,
signatureCollected, confirmations,
setPendingExecution setPendingExecution,
setError,
requiredSignatures,
validatorList
}: ManualExecutionButtonParams) => { }: ManualExecutionButtonParams) => {
const { foreign, setError } = useStateProvider() const { foreign } = useStateProvider()
const { library, activate, account, active } = useWeb3React() const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false) const [manualExecution, setManualExecution] = useState(false)
const [allowFailures, setAllowFailures] = useState(false) const [allowFailures, setAllowFailures] = useState(false)
const notReady = !foreign.bridgeContract || !signatureCollected || !signatureCollected.length const [ready, setReady] = useState(false)
const [title, setTitle] = useState('Loading')
const [validSignatures, setValidSignatures] = useState<string[]>([])
useEffect(
() => {
if (
!foreign.bridgeContract ||
!foreign.web3 ||
!confirmations ||
!confirmations.length ||
!requiredSignatures ||
!validatorList ||
!validatorList.length
)
return
const signatures = []
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)) {
signatures.push(sig)
}
}
if (signatures.length >= requiredSignatures) {
setValidSignatures(signatures.slice(0, requiredSignatures))
setTitle('Execute')
setReady(true)
} else {
setTitle('Unavailable')
}
},
[
foreign.bridgeContract,
foreign.web3,
validatorList,
requiredSignatures,
messageData,
setValidSignatures,
confirmations
]
)
useEffect( useEffect(
() => { () => {
@ -73,9 +127,9 @@ export const ManualExecutionButton = ({
return return
} }
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS)) const signatures = packSignatures(validSignatures.map(signatureToVRS))
const messageId = messageData.slice(0, 66) const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract const bridge = foreign.bridgeContract
const executeMethod = const executeMethod =
@ -140,19 +194,20 @@ export const ManualExecutionButton = ({
foreign.bridgeContract, foreign.bridgeContract,
setError, setError,
messageData, messageData,
signatureCollected,
setExecutionData, setExecutionData,
setPendingExecution, setPendingExecution,
safeExecutionAvailable, safeExecutionAvailable,
allowFailures allowFailures,
foreign.web3,
validSignatures
] ]
) )
return ( return (
<div> <div>
<div className="is-center"> <div className="is-center">
<ActionButton disabled={notReady} className="button outline" onClick={() => setManualExecution(true)}> <ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
Execute {title}
</ActionButton> </ActionButton>
</div> </div>
{safeExecutionAvailable && ( {safeExecutionAvailable && (

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size' 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 { SimpleLoading } from './commons/Loading'
import styled from 'styled-components' import styled from 'styled-components'
import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
@ -31,7 +31,9 @@ export const ValidatorsConfirmations = ({
const getValidatorStatusElement = (validatorStatus = '') => { const getValidatorStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
return <SuccessLabel>{validatorStatus}</SuccessLabel> case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</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.PENDING:
@ -58,26 +60,28 @@ export const ValidatorsConfirmations = ({
</tr> </tr>
</Thead> </Thead>
<tbody> <tbody>
{validatorList.map((validator, i) => { {confirmations.map((confirmation, i) => {
const filteredConfirmation = confirmations.filter(c => c.validator === validator) const displayedStatus = confirmation.status
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
const displayedStatus = confirmation && confirmation.status ? confirmation.status : '' let elementIfNoTimestamp: any = <SimpleLoading />
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : '' switch (displayedStatus) {
const elementIfNoTimestamp = case '':
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING && case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? ( if (waitingBlocksResolved) {
(displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') && elementIfNoTimestamp = SEARCHING_TX
waitingBlocksResolved ? ( }
SEARCHING_TX break
) : ( case VALIDATOR_CONFIRMATION_STATUS.WAITING:
<SimpleLoading /> case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
) elementIfNoTimestamp = ''
) : ( break
'' case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
) elementIfNoTimestamp = RECENT_AGE
break
}
return ( return (
<tr key={i}> <tr key={i}>
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td> <td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td>
<StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd> <StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd>
<AgeTd className="text-center"> <AgeTd className="text-center">
{confirmation && confirmation.timestamp > 0 ? ( {confirmation && confirmation.timestamp > 0 ? (
@ -94,7 +98,7 @@ export const ValidatorsConfirmations = ({
</tbody> </tbody>
</table> </table>
<RequiredConfirmations> <RequiredConfirmations>
{requiredSignatures} of {validatorList.length} confirmations required At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
</RequiredConfirmations> </RequiredConfirmations>
</div> </div>
) )

View File

@ -33,7 +33,7 @@ export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: str
} }
return ( return (
<div className="row is-center"> <div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row"> <StyledErrorAlert className="col-12 is-vertical-align row">
<InfoIcon color="var(--failed-color)" /> <InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10"> <TextContainer className="col-10">
{text} {text}

View File

@ -0,0 +1,34 @@
import React from 'react'
import styled from 'styled-components'
import { InfoIcon } from './InfoIcon'
import { CloseIcon } from './CloseIcon'
const StyledErrorAlert = styled.div`
border: 1px solid var(--warning-color);
border-radius: 4px;
margin-bottom: 20px;
padding-top: 10px;
`
const CloseIconContainer = styled.div`
cursor: pointer;
`
const TextContainer = styled.div`
white-space: pre-wrap;
flex-direction: column;
`
export const WarningAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
return (
<div className="row is-center">
<StyledErrorAlert className="col-12 is-vertical-align row">
<InfoIcon color="var(--warning-color)" />
<TextContainer className="col-10">{error}</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--warning-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
}

View File

@ -54,14 +54,19 @@ export const CONFIRMATIONS_STATUS = {
} }
export const VALIDATOR_CONFIRMATION_STATUS = { export const VALIDATOR_CONFIRMATION_STATUS = {
SUCCESS: 'Success', SUCCESS: 'Confirmed',
MANUAL: 'Manual',
EXECUTION_SUCCESS: 'Executed',
FAILED: 'Failed', FAILED: 'Failed',
FAILED_VALID: 'Failed valid',
PENDING: 'Pending', PENDING: 'Pending',
WAITING: 'Waiting', WAITING: 'Waiting',
NOT_REQUIRED: 'Not required', NOT_REQUIRED: 'Not required',
UNDEFINED: 'UNDEFINED' UNDEFINED: 'UNDEFINED'
} }
export const RECENT_AGE = 'Recent'
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.`

View File

@ -29,17 +29,16 @@ export interface useMessageConfirmationsParams {
foreignStartBlock: Maybe<number> foreignStartBlock: Maybe<number>
requiredSignatures: number requiredSignatures: number
validatorList: string[] validatorList: string[]
targetValidatorList: string[]
blockConfirmations: number blockConfirmations: number
} }
export interface BasicConfirmationParam { export interface ConfirmationParam {
validator: string validator: string
status: string status: string
}
export interface ConfirmationParam extends BasicConfirmationParam {
txHash: string txHash: string
timestamp: number timestamp: number
signature?: string
} }
export interface ExecutionData { export interface ExecutionData {
@ -48,6 +47,7 @@ export interface ExecutionData {
txHash: string txHash: string
timestamp: number timestamp: number
executionResult: boolean executionResult: boolean
blockNumber: number
} }
export const useMessageConfirmations = ({ export const useMessageConfirmations = ({
@ -58,6 +58,7 @@ export const useMessageConfirmations = ({
foreignStartBlock, foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
targetValidatorList,
blockConfirmations blockConfirmations
}: useMessageConfirmationsParams) => { }: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
@ -65,7 +66,7 @@ export const useMessageConfirmations = ({
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
const [waitingBlocks, setWaitingBlocks] = useState(false) const [waitingBlocks, setWaitingBlocks] = useState(false)
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false) const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(false) const [signatureCollected, setSignatureCollected] = useState(false)
const [executionEventsFetched, setExecutionEventsFetched] = useState(false) const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null) const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
const [executionData, setExecutionData] = useState<ExecutionData>({ const [executionData, setExecutionData] = useState<ExecutionData>({
@ -73,7 +74,8 @@ export const useMessageConfirmations = ({
validator: '', validator: '',
txHash: '', txHash: '',
timestamp: 0, timestamp: 0,
executionResult: false executionResult: false,
blockNumber: 0
}) })
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false) const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = 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 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 // 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 // This is executed if the message is in Home to Foreign direction only
const hasCollectedSignatures = !!signatureCollected // true or string[]
useEffect( useEffect(
() => { () => {
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
let timeoutId: number let timeoutId: number
let isCancelled = false let isCancelled = false
@ -179,7 +180,7 @@ export const useMessageConfirmations = ({
isCancelled = true 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 // 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 timeoutId: number
let isCancelled = false 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( getConfirmationsForTx(
message.data, message.data,
home.web3, home.web3,
@ -284,7 +314,8 @@ export const useMessageConfirmations = ({
home.bridgeContract, home.bridgeContract,
requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
homeStartBlock homeStartBlock,
targetValidatorList
] ]
) )
@ -343,7 +374,10 @@ export const useMessageConfirmations = ({
// Sets the message status based in the collected information // Sets the message status based in the collected information
useEffect( useEffect(
() => { () => {
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) { if (
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
existsConfirmation(confirmations)
) {
const newStatus = executionData.executionResult const newStatus = executionData.executionResult
? CONFIRMATIONS_STATUS.SUCCESS ? CONFIRMATIONS_STATUS.SUCCESS
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED

View File

@ -4,19 +4,13 @@ import Web3 from 'web3'
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract' import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
import { BRIDGE_VALIDATORS_ABI } from '../abis' import { BRIDGE_VALIDATORS_ABI } from '../abis'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { TransactionReceipt } from 'web3-eth'
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants' import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
export interface useValidatorContractParams { export const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => {
fromHome: boolean
receipt: Maybe<TransactionReceipt>
}
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null) const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
const [requiredSignatures, setRequiredSignatures] = useState(0) const [requiredSignatures, setRequiredSignatures] = useState(0)
const [validatorList, setValidatorList] = useState([]) const [validatorList, setValidatorList] = useState<string[]>([])
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
@ -29,34 +23,34 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
const callRequiredSignatures = async ( const callRequiredSignatures = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
receipt: TransactionReceipt, blockNumber: number | 'latest',
setResult: Function, setResult: Function,
snapshotProvider: SnapshotProvider, snapshotProvider: SnapshotProvider,
web3: Web3, web3: Web3,
api: string api: string
) => { ) => {
if (!contract) return if (!contract) return
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api) const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
setResult(result) setResult(result)
} }
const callValidatorList = async ( const callValidatorList = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
receipt: TransactionReceipt, blockNumber: number | 'latest',
setResult: Function, setResult: Function,
snapshotProvider: SnapshotProvider, snapshotProvider: SnapshotProvider,
web3: Web3, web3: Web3,
api: string api: string
) => { ) => {
if (!contract) return if (!contract) return
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api) const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
setResult(result) setResult(result)
} }
const web3 = fromHome ? home.web3 : foreign.web3 const web3 = isHome ? home.web3 : foreign.web3
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider const snapshotProvider = isHome ? homeSnapshotProvider : foreignSnapshotProvider
useEffect( useEffect(
() => { () => {
@ -68,11 +62,11 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
useEffect( useEffect(
() => { () => {
if (!web3 || !receipt) return if (!web3 || !blockNumber) return
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api) callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api) callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
}, },
[validatorContract, receipt, web3, snapshotProvider, api] [validatorContract, blockNumber, web3, snapshotProvider, api]
) )
return { return {

View File

@ -1,4 +1,4 @@
import React, { createContext, ReactNode, useState } from 'react' import React, { createContext, ReactNode } from 'react'
import { useNetwork } from '../hooks/useNetwork' import { useNetwork } from '../hooks/useNetwork'
import { import {
HOME_RPC_URL, HOME_RPC_URL,
@ -25,8 +25,6 @@ export interface StateContext {
home: BaseNetworkParams home: BaseNetworkParams
foreign: BaseNetworkParams foreign: BaseNetworkParams
loading: boolean loading: boolean
error: string
setError: Function
} }
const initialState = { const initialState = {
@ -44,9 +42,7 @@ const initialState = {
bridgeAddress: FOREIGN_BRIDGE_ADDRESS, bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
bridgeContract: null bridgeContract: null
}, },
loading: true, loading: true
error: '',
setError: () => {}
} }
const StateContext = createContext<StateContext>(initialState) const StateContext = createContext<StateContext>(initialState)
@ -58,7 +54,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
homeWeb3: homeNetwork.web3, homeWeb3: homeNetwork.web3,
foreignWeb3: foreignNetwork.web3 foreignWeb3: foreignNetwork.web3
}) })
const [error, setError] = useState('')
const value = { const value = {
home: { home: {
@ -73,9 +68,7 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
bridgeContract: foreignBridge, bridgeContract: foreignBridge,
...foreignNetwork ...foreignNetwork
}, },
loading: homeNetwork.loading || foreignNetwork.loading, loading: homeNetwork.loading || foreignNetwork.loading
error,
setError
} }
return <StateContext.Provider value={value}>{children}</StateContext.Provider> return <StateContext.Provider value={value}>{children}</StateContext.Provider>

View File

@ -28,5 +28,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor}; --not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
--failed-color: ${props => props.theme.failed.textColor}; --failed-color: ${props => props.theme.failed.textColor};
--failed-bg-color: ${props => props.theme.failed.backgroundColor}; --failed-bg-color: ${props => props.theme.failed.backgroundColor};
--warning-color: ${props => props.theme.warning.textColor};
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
} }
` `

View File

@ -17,6 +17,10 @@ const theme = {
failed: { failed: {
textColor: '#de4437', textColor: '#de4437',
backgroundColor: 'rgba(222,68,55,.1)' backgroundColor: 'rgba(222,68,55,.1)'
},
warning: {
textColor: '#ffa758',
backgroundColor: 'rgba(222,68,55,.1)'
} }
} }
export default theme export default theme

View File

@ -14,37 +14,40 @@ const messageData = '0x123456'
const OTHER_HASH = 'aabbccdd' const OTHER_HASH = 'aabbccdd'
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1' const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
describe('getFailedTransactions', () => { describe('getFailedTransactions', () => {
test('should only return failed transactions', async () => { test('should only return failed transactions', async () => {
const to = otherAddress const to = otherAddress
const transactions = [ const transactions = [
{ isError: '0', to }, { isError: '0', to, from: validator1 },
{ isError: '1', to }, { isError: '1', to, from: validator1 },
{ isError: '0', to }, { isError: '0', to, from: validator2 },
{ isError: '1', to }, { isError: '1', to, from: validator2 },
{ isError: '1', to } { isError: '1', to, from: validator3 }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions) const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(3) expect(result.length).toEqual(1)
}) })
}) })
describe('getSuccessTransactions', () => { describe('getSuccessTransactions', () => {
test('should only return success transactions', async () => { test('should only return success transactions', async () => {
const to = otherAddress const to = otherAddress
const transactions = [ const transactions = [
{ isError: '0', to }, { isError: '0', to, from: validator1 },
{ isError: '1', to }, { isError: '1', to, from: validator1 },
{ isError: '0', to }, { isError: '0', to, from: validator2 },
{ isError: '1', to }, { isError: '1', to, from: validator2 },
{ isError: '1', to } { isError: '1', to, from: validator3 }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions) const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(2) expect(result.length).toEqual(1)
}) })
}) })
describe('filterValidatorSignatureTransaction', () => { describe('filterValidatorSignatureTransaction', () => {

View File

@ -5,7 +5,7 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { APIPendingTransaction, APITransaction } from '../explorer' import { APIPendingTransaction, APITransaction } from '../explorer'
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations' import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
jest.mock('../validatorConfirmationHelpers') jest.mock('../validatorConfirmationHelpers')
@ -18,6 +18,9 @@ const messageData = '0x111111111'
const web3 = { const web3 = {
utils: { utils: {
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}` soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
},
eth: {
accounts: new Web3().eth.accounts
} }
} as Web3 } as Web3
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
@ -25,7 +28,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3] const validatorList = [validator1, validator2, validator3]
const signature = const signature =
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4' '0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
const bridgeContract = { const bridgeContract = {
methods: { methods: {
signature: () => ({ signature: () => ({
@ -61,19 +64,19 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -110,9 +113,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -135,7 +137,7 @@ describe('getConfirmationsForTx', () => {
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, 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, validator,
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -208,19 +210,19 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 ? '0x123' : '', txHash: validatorData.validator !== validator3 ? '0x123' : '',
timestamp: validatorData.validator !== validator3 ? 123 : 0 timestamp: validatorData.validator !== validator3 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -257,9 +259,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(3) expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -281,16 +282,16 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { 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.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, 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.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '', txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0 timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.FAILED ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator3 ? '0x123' : '', txHash: validatorData.validator === validator3 ? '0x123' : '',
timestamp: validatorData.validator === validator3 ? 123 : 0 timestamp: validatorData.validator === validator3 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -356,9 +357,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(4) expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -382,26 +382,26 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, 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.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
]) ])
) )
}) })
@ -414,22 +414,22 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x123' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator2 validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x123' : '', txHash: validatorData.validator === validator2 ? '0x123' : '',
timestamp: validatorData.validator === validator2 ? 123 : 0 timestamp: validatorData.validator === validator2 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
@ -492,22 +492,22 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { 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.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { 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 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
@ -521,13 +521,13 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x123' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator !== validator1 validatorData.validator !== validator1
@ -536,7 +536,7 @@ describe('getConfirmationsForTx', () => {
txHash: validatorData.validator !== validator1 ? '0x123' : '', txHash: validatorData.validator !== validator1 ? '0x123' : '',
timestamp: validatorData.validator !== validator1 ? 123 : 0 timestamp: validatorData.validator !== validator1 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -596,9 +596,9 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { 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.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
@ -610,9 +610,13 @@ describe('getConfirmationsForTx', () => {
) )
}) })
test('should remove pending state after transaction mined', async () => { test('should remove pending state after transaction mined', async () => {
// Validator1 success const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
// Validator2 failed const validatorList = [validator1, validator2, validator3, validator4]
// Validator3 Pending
// Validator1 success (ts=100)
// Validator2 failed (ts=200)
// Validator3 Pending (ts=300)
// Validator4 Excess confirmation (Failed) (ts=400)
getValidatorConfirmation getValidatorConfirmation
.mockImplementationOnce(() => async (validator: string) => ({ .mockImplementationOnce(() => async (validator: string) => ({
@ -623,41 +627,57 @@ describe('getConfirmationsForTx', () => {
.mockImplementation(() => async (validator: string) => ({ .mockImplementation(() => async (validator: string) => ({
validator, validator,
status: status:
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED validator === validator1 || validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x100' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 100 : 0
})) }))
.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator2 ? '0x123' : '', txHash:
timestamp: validatorData.validator !== validator2 ? 123 : 0 validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '',
timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : ''
}))
getValidatorFailedTransaction
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x200' : '',
timestamp: validatorData.validator === validator2 ? 200 : 0
}))
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2 || validatorData.validator === validator4
? 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 : ''
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x123' : '',
timestamp: validatorData.validator === validator2 ? 123 : 0
}))
getValidatorPendingTransaction getValidatorPendingTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.PENDING ? VALIDATOR_CONFIRMATION_STATUS.PENDING
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator3 ? '0x123' : '', txHash: validatorData.validator === validator3 ? '0x300' : '',
timestamp: validatorData.validator === validator3 ? 123 : 0 timestamp: validatorData.validator === validator3 ? 300 : 0
})) }))
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -698,7 +718,7 @@ describe('getConfirmationsForTx', () => {
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).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(getValidatorPendingTransaction).toBeCalledTimes(1)
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
@ -712,28 +732,32 @@ describe('getConfirmationsForTx', () => {
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
@ -761,14 +785,13 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(7) expect(setResult).toBeCalledTimes(7)
expect(getValidatorConfirmation).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(2)
expect(getSuccessExecutionTransaction).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[0][0]).toEqual(false)
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true) expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(2) expect(getValidatorFailedTransaction).toBeCalledTimes(2)
expect(setFailedConfirmations).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(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(getValidatorPendingTransaction).toBeCalledTimes(1)
@ -781,23 +804,26 @@ describe('getConfirmationsForTx', () => {
const res7 = setResult.mock.calls[6][0](res6) const res7 = setResult.mock.calls[6][0](res6)
expect(res5).toEqual( expect(res5).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { 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 }
]) ])
) )
expect(res6).toEqual( expect(res6).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { 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 }
]) ])
) )
expect(res7).toEqual( expect(res7).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
]) ])
) )
}) })

View File

@ -84,10 +84,11 @@ describe('getFinalizationEvent', () => {
expect(setResult).toBeCalledTimes(1) expect(setResult).toBeCalledTimes(1)
expect(setResult.mock.calls[0][0]).toEqual({ expect(setResult.mock.calls[0][0]).toEqual({
validator: validator1, validator: validator1,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
txHash, txHash,
timestamp, timestamp,
executionResult: true executionResult: true,
blockNumber: 5523145
}) })
expect(getFailedExecution).toBeCalledTimes(0) expect(getFailedExecution).toBeCalledTimes(0)
@ -237,7 +238,8 @@ describe('getFinalizationEvent', () => {
status: VALIDATOR_CONFIRMATION_STATUS.PENDING, status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
txHash, txHash,
timestamp: expect.any(Number), timestamp: expect.any(Number),
executionResult: false executionResult: false,
blockNumber: 0
}) })
expect(getFailedExecution).toBeCalledTimes(0) expect(getFailedExecution).toBeCalledTimes(0)
@ -294,7 +296,8 @@ describe('getFinalizationEvent', () => {
status: VALIDATOR_CONFIRMATION_STATUS.FAILED, status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
txHash, txHash,
timestamp: expect.any(Number), timestamp: expect.any(Number),
executionResult: false executionResult: false,
blockNumber: expect.any(Number)
}) })
expect(getFailedExecution).toBeCalledTimes(1) expect(getFailedExecution).toBeCalledTimes(1)

View File

@ -22,6 +22,16 @@ export const getRequiredBlockConfirmations = async (
web3: Web3 | null = null, web3: Web3 | null = null,
api: string = '' api: string = ''
) => { ) => {
let blockConfirmations
try {
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
} catch {}
if (blockConfirmations) {
return parseInt(blockConfirmations)
}
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber) const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
@ -35,16 +45,10 @@ export const getRequiredBlockConfirmations = async (
const events = [...eventsFromSnapshot, ...contractEvents] const events = [...eventsFromSnapshot, ...contractEvents]
let blockConfirmations // Use the value from last event before the transaction
if (events.length > 0) { const event = events[events.length - 1]
// Use the value from last event before the transaction blockConfirmations = event.returnValues.requiredBlockConfirmations
const event = events[events.length - 1]
blockConfirmations = event.returnValues.requiredBlockConfirmations
} else {
// This is a special case where RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
// of Sokol - Kovan. In this case the current value is used.
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
}
return parseInt(blockConfirmations) return parseInt(blockConfirmations)
} }
@ -52,11 +56,25 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
export const getRequiredSignatures = async ( export const getRequiredSignatures = async (
contract: Contract, contract: Contract,
blockNumber: number, blockNumber: number | 'latest',
snapshotProvider: SnapshotProvider, snapshotProvider: SnapshotProvider,
web3: Web3 | null = null, web3: Web3 | null = null,
api: string = '' api: string = ''
) => { ) => {
let requiredSignatures
try {
requiredSignatures = await contract.methods.requiredSignatures().call()
} catch {}
if (requiredSignatures) {
return parseInt(requiredSignatures)
}
if (blockNumber === 'latest') {
return contract.methods.requiredSignatures().call()
}
const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber) const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
@ -72,17 +90,25 @@ export const getRequiredSignatures = async (
// Use the value form last event before the transaction // Use the value form last event before the transaction
const event = events[events.length - 1] const event = events[events.length - 1]
const { requiredSignatures } = event.returnValues ;({ requiredSignatures } = event.returnValues)
return parseInt(requiredSignatures) return parseInt(requiredSignatures)
} }
export const getValidatorList = async ( export const getValidatorList = async (
contract: Contract, contract: Contract,
blockNumber: number, blockNumber: number | 'latest',
snapshotProvider: SnapshotProvider, snapshotProvider: SnapshotProvider,
web3: Web3 | null = null, web3: Web3 | null = null,
api: string = '' api: string = ''
) => { ) => {
try {
const currentList = await contract.methods.validatorList().call()
if (currentList) {
return currentList
}
} catch {}
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber) const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber) const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()

View File

@ -12,6 +12,7 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
export interface APITransaction { export interface APITransaction {
from: string
timeStamp: string timeStamp: string
isError: string isError: string
input: string input: string
@ -54,7 +55,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
url.searchParams.append('module', 'account') url.searchParams.append('module', 'account')
url.searchParams.append('action', 'txlist') url.searchParams.append('action', 'txlist')
url.searchParams.append('address', account) url.searchParams.append('address', account)
url.searchParams.append('filterby', 'from') url.searchParams.append('filterby', 'to')
url.searchParams.append('startblock', startBlock.toString()) url.searchParams.append('startblock', startBlock.toString())
url.searchParams.append('endblock', endBlock.toString()) url.searchParams.append('endblock', endBlock.toString())
@ -64,7 +65,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
return [] return []
} }
return result.result return result.result || []
} }
export const fetchPendingTransactions = async ({ export const fetchPendingTransactions = async ({
@ -180,7 +181,9 @@ export const getLogs = async (
if (topics[i] !== null) { if (topics[i] !== null) {
url.searchParams.append(`topic${i}`, topics[i] as string) url.searchParams.append(`topic${i}`, topics[i] as string)
for (let j = 0; j < i; j++) { 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 ( export const getFailedTransactions = async (
account: string, account: string,
@ -204,9 +207,9 @@ export const getFailedTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
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 ( export const getSuccessTransactions = async (
@ -217,9 +220,9 @@ export const getSuccessTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
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 = ( export const filterValidatorSignatureTransaction = (

View File

@ -2,26 +2,37 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer' import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
import { import {
getValidatorConfirmation, getValidatorConfirmation,
getValidatorFailedTransaction, getValidatorFailedTransaction,
getValidatorPendingTransaction, getValidatorPendingTransaction,
getSuccessExecutionTransaction getSuccessExecutionTransaction
} from './validatorConfirmationHelpers' } 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] const confirmations = [...oldConfirmations]
newConfirmations.forEach(validatorData => { newConfirmations.forEach(validatorData => {
const index = confirmations.findIndex(e => e.validator === validatorData.validator) const index = confirmations.findIndex(e => e.validator === validatorData.validator)
if (index === -1) {
confirmations.push(validatorData)
return
}
const currentStatus = confirmations[index].status const currentStatus = confirmations[index].status
const newStatus = validatorData.status const newStatus = validatorData.status
if ( if (
(validatorData as ConfirmationParam).txHash || validatorData.txHash ||
!!validatorData.signature ||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED) (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 return confirmations
@ -45,19 +56,17 @@ export const getConfirmationsForTx = async (
setPendingConfirmations: Function, setPendingConfirmations: Function,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => { ) => {
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const hashMsg = web3.utils.soliditySha3Raw(messageData) const hashMsg = web3.utils.soliditySha3Raw(messageData)
let validatorConfirmations = await Promise.all( 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) { if (confirmations.length === 0) {
return return
} }
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations) validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
setResult((currentConfirmations: BasicConfirmationParam[]) => { setResult((currentConfirmations: ConfirmationParam[]) => {
if (currentConfirmations && currentConfirmations.length) { if (currentConfirmations && currentConfirmations.length) {
return mergeConfirmations(currentConfirmations, confirmations) return mergeConfirmations(currentConfirmations, confirmations)
} }
@ -67,11 +76,37 @@ export const getConfirmationsForTx = async (
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
const notSuccessConfirmations = 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) updateConfirmations(validatorConfirmations)
setSignatureCollected(hasEnoughSignatures) setSignatureCollected(hasEnoughSignatures)
if (hasEnoughSignatures) {
setPendingConfirmations(false)
if (fromHome) {
// fetch collected signatures for possible manual processing
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)
}
}
// get transactions from success signatures
const successConfirmationWithData = await Promise.all(
successConfirmations.map(
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
)
)
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
updateConfirmations(successConfirmationWithTxFound)
// If signatures not collected, look for pending transactions // If signatures not collected, look for pending transactions
if (!hasEnoughSignatures) { if (!hasEnoughSignatures) {
// Check if confirmation is pending // Check if confirmation is pending
@ -84,16 +119,6 @@ export const getConfirmationsForTx = async (
) )
updateConfirmations(validatorPendingConfirmations) updateConfirmations(validatorPendingConfirmations)
setPendingConfirmations(validatorPendingConfirmations.length > 0) setPendingConfirmations(validatorPendingConfirmations.length > 0)
} else {
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 undefinedConfirmations = validatorConfirmations.filter( const undefinedConfirmations = validatorConfirmations.filter(
@ -103,13 +128,27 @@ export const getConfirmationsForTx = async (
// Check if confirmation failed // Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all( const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map( undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions) getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
) )
) )
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( 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 && !fromHome) {
const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0))
validatorFailedConfirmations = validatorFailedConfirmations.map(
c =>
c.timestamp < lastTS
? c
: {
...c,
status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
}
)
}
setFailedConfirmations(
!hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED)
) )
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
updateConfirmations(validatorFailedConfirmations) updateConfirmations(validatorFailedConfirmations)
const missingConfirmations = validatorConfirmations.filter( const missingConfirmations = validatorConfirmations.filter(
@ -120,21 +159,13 @@ export const getConfirmationsForTx = async (
// If signatures collected, it should set other signatures not found as not required // If signatures collected, it should set other signatures not found as not required
const notRequiredConfirmations = missingConfirmations.map(c => ({ const notRequiredConfirmations = missingConfirmations.map(c => ({
validator: c.validator, validator: c.validator,
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED,
timestamp: 0,
txHash: ''
})) }))
updateConfirmations(notRequiredConfirmations) updateConfirmations(notRequiredConfirmations)
} }
// get transactions from success signatures
const successConfirmationWithData = await Promise.all(
successConfirmations.map(
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
)
)
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
updateConfirmations(successConfirmationWithTxFound)
// retry if not all signatures are collected and some confirmations are still missing // retry if not all signatures are collected and some confirmations are still missing
// or some success transactions were not fetched successfully // or some success transactions were not fetched successfully
if ( if (

View File

@ -59,11 +59,12 @@ export const getSuccessExecutionData = async (
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from) const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
validator: validatorAddress, validator: validatorAddress,
txHash: event.transactionHash, txHash: event.transactionHash,
timestamp: blockTimestamp, timestamp: blockTimestamp,
executionResult: event.returnValues.status executionResult: event.returnValues.status,
blockNumber: event.blockNumber
} }
} }
return null return null
@ -115,7 +116,8 @@ export const getFinalizationEvent = async (
validator: validator, validator: validator,
txHash: pendingTx.hash, txHash: pendingTx.hash,
timestamp: nowTimestamp, timestamp: nowTimestamp,
executionResult: false executionResult: false,
blockNumber: 0
}) })
setPendingExecution(true) setPendingExecution(true)
} else { } else {
@ -144,7 +146,8 @@ export const getFinalizationEvent = async (
validator: validator, validator: validator,
txHash: failedTx.hash, txHash: failedTx.hash,
timestamp, timestamp,
executionResult: false executionResult: false,
blockNumber: parseInt(failedTx.blockNumber)
}) })
setFailedExecution(true) setFailedExecution(true)
} }

View File

@ -1,38 +1,45 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer' import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
import { homeBlockNumberProvider } from '../services/BlockNumberProvider' import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
export const getValidatorConfirmation = ( export const getValidatorConfirmation = (
web3: Web3, web3: Web3,
hashMsg: string, hashMsg: string,
bridgeContract: Contract, bridgeContract: Contract,
confirmationContractMethod: Function fromHome: boolean
) => async (validator: string): Promise<BasicConfirmationParam> => { ) => async (validator: string): Promise<ConfirmationParam> => {
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
const signatureFromCache = validatorsCache.get(hashSenderMsg) const fromCache = validatorsCache.getData(hashSenderMsg)
if (signatureFromCache) { if (fromCache) {
return { return fromCache
validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
}
} }
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) 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 validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
if (confirmed) { 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 { return {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator, validator,
status timestamp: 0,
txHash: ''
} }
} }
@ -43,7 +50,7 @@ export const getSuccessExecutionTransaction = (
messageData: string, messageData: string,
startBlock: number, startBlock: number,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
const fromCache = validatorsCache.getData(validatorCacheKey) const fromCache = validatorsCache.getData(validatorCacheKey)
@ -87,11 +94,12 @@ export const getSuccessExecutionTransaction = (
} }
export const getValidatorFailedTransaction = ( export const getValidatorFailedTransaction = (
web3: Web3,
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
startBlock: number, startBlock: number,
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
const failedFromCache = validatorsCache.getData(validatorCacheKey) const failedFromCache = validatorsCache.getData(validatorCacheKey)
@ -106,30 +114,33 @@ export const getValidatorFailedTransaction = (
startBlock, startBlock,
endBlock: homeBlockNumberProvider.get() || 0 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 validator signature failed, we cache the result to avoid doing future requests for a result that won't change
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
const failedTx = failedTransactions[0] const failedTx = failedTransactions[0]
txHashTimestamp = parseInt(failedTx.timeStamp) const confirmation: ConfirmationParam = {
txHash = failedTx.hash status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validatorsCache.setData(validatorCacheKey, {
validator: validatorData.validator, validator: validatorData.validator,
status: newStatus, txHash: failedTx.hash,
txHash, timestamp: parseInt(failedTx.timeStamp)
timestamp: txHashTimestamp }
})
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 { return {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: validatorData.validator, validator: validatorData.validator,
status: newStatus, txHash: '',
txHash, timestamp: 0
timestamp: txHashTimestamp
} }
} }
@ -137,7 +148,7 @@ export const getValidatorPendingTransaction = (
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]> getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const failedTransactions = await getPendingTransactions({ const failedTransactions = await getPendingTransactions({
account: validatorData.validator, account: validatorData.validator,
to: bridgeContract.options.address, to: bridgeContract.options.address,

View File

@ -10,6 +10,37 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
export interface MessageObject { export interface MessageObject {
id: string id: string
data: string data: string
sender?: string
executor?: string
obToken?: string
obReceiver?: string
}
export interface WarnRule {
message: string
sender?: string
executor?: string
obToken?: string
obReceiver?: string
}
export const matchesRule = (rule: WarnRule, msg: MessageObject) => {
if (!msg.executor || !msg.sender) {
return false
}
if (!!rule.executor && rule.executor.toLowerCase() !== msg.executor.toLowerCase()) {
return false
}
if (!!rule.sender && rule.sender.toLowerCase() !== msg.sender.toLowerCase()) {
return false
}
if (!!rule.obToken && (!msg.obToken || rule.obToken.toLowerCase() !== msg.obToken.toLowerCase())) {
return false
}
if (!!rule.obReceiver && (!msg.obReceiver || rule.obReceiver.toLowerCase() !== msg.obReceiver.toLowerCase())) {
return false
}
return true
} }
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url)) const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
@ -26,15 +57,33 @@ export const filterEventsByAbi = (
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi) const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash) const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
if (!eventAbi || !eventAbi.inputs || !eventAbi.inputs.length) {
return []
}
const inputs = eventAbi.inputs
return events.map(e => { return events.map(e => {
let decodedLogs: { [p: string]: string } = { const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
messageId: '', let sender, executor, obToken, obReceiver
encodedData: '' if (encodedData.length >= 160) {
sender = `0x${encodedData.slice(66, 106)}`
executor = `0x${encodedData.slice(106, 146)}`
const dataOffset =
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
if (encodedData.length >= dataOffset + 64) {
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
}
if (encodedData.length >= dataOffset + 128) {
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
}
} }
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) { return {
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]]) id: messageId || '',
data: encodedData || '',
sender,
executor,
obToken,
obReceiver
} }
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
}) })
} }

View File

@ -1,10 +1,10 @@
# POA TokenBridge / Oracle # POA TokenBridge / Oracle
Oracle responsible for listening to bridge related events and authorizing asset transfers. An oracle responsible for listening to bridge related events and authorizing asset transfers.
## Overview ## Overview
Please refer to the [POA TokenBridge](../README.md) overview first of all. Please refer to the [POA TokenBridge](../README.md) overview first of all.
The Oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. It connects to two chains via a Remote Procedure Call (RPC) and is responsible for: The oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. It connects to two chains via a Remote Procedure Call (RPC) and is responsible for:
- listening to events related to bridge contracts - listening to events related to bridge contracts
- sending transactions to authorize asset transfers - sending transactions to authorize asset transfers
@ -69,7 +69,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues
} }
``` ```
## Install and configure the Oracle ## Install and configure the oracle
1. [Initialize](../README.md#initializing-the-monorepository) the monorepository. 1. [Initialize](../README.md#initializing-the-monorepository) the monorepository.

View File

@ -7,7 +7,15 @@ const {
HOME_AMB_ABI, HOME_AMB_ABI,
FOREIGN_AMB_ABI FOREIGN_AMB_ABI
} = require('../../commons') } = require('../../commons')
const { web3Home, web3Foreign } = require('../src/services/web3') const {
web3Home,
web3Foreign,
web3HomeRedundant,
web3HomeFallback,
web3ForeignRedundant,
web3ForeignFallback,
web3ForeignArchive
} = require('../src/services/web3')
const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils') const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants') const { EXIT_CODES } = require('../src/utils/constants')
@ -27,9 +35,11 @@ const {
ORACLE_HOME_EVENTS_REPROCESSING, ORACLE_HOME_EVENTS_REPROCESSING,
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE, ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE,
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY, ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY,
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL,
ORACLE_FOREIGN_EVENTS_REPROCESSING, ORACLE_FOREIGN_EVENTS_REPROCESSING,
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE, ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE,
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY,
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL
} = process.env } = process.env
let homeAbi let homeAbi
@ -63,9 +73,12 @@ const homeConfig = {
bridgeAddress: COMMON_HOME_BRIDGE_ADDRESS, bridgeAddress: COMMON_HOME_BRIDGE_ADDRESS,
bridgeABI: homeAbi, bridgeABI: homeAbi,
pollingInterval: parseInt(ORACLE_HOME_RPC_POLLING_INTERVAL, 10), pollingInterval: parseInt(ORACLE_HOME_RPC_POLLING_INTERVAL, 10),
syncCheckInterval: parseInt(ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL, 10) || 60000,
startBlock: parseInt(ORACLE_HOME_START_BLOCK, 10) || 0, startBlock: parseInt(ORACLE_HOME_START_BLOCK, 10) || 0,
blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10), blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10),
web3: web3Home, web3: web3Home,
web3Redundant: web3HomeRedundant,
web3Fallback: web3HomeFallback,
bridgeContract: homeContract, bridgeContract: homeContract,
eventContract: homeContract, eventContract: homeContract,
reprocessingOptions: { reprocessingOptions: {
@ -81,9 +94,13 @@ const foreignConfig = {
bridgeAddress: COMMON_FOREIGN_BRIDGE_ADDRESS, bridgeAddress: COMMON_FOREIGN_BRIDGE_ADDRESS,
bridgeABI: foreignAbi, bridgeABI: foreignAbi,
pollingInterval: parseInt(ORACLE_FOREIGN_RPC_POLLING_INTERVAL, 10), pollingInterval: parseInt(ORACLE_FOREIGN_RPC_POLLING_INTERVAL, 10),
syncCheckInterval: parseInt(ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL, 10) || 60000,
startBlock: parseInt(ORACLE_FOREIGN_START_BLOCK, 10) || 0, startBlock: parseInt(ORACLE_FOREIGN_START_BLOCK, 10) || 0,
blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10), blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10),
web3: web3Foreign, web3: web3Foreign,
web3Redundant: web3ForeignRedundant,
web3Fallback: web3ForeignFallback,
web3Archive: web3ForeignArchive || web3Foreign,
bridgeContract: foreignContract, bridgeContract: foreignContract,
eventContract: foreignContract, eventContract: foreignContract,
reprocessingOptions: { reprocessingOptions: {

View File

@ -1,17 +1,14 @@
const baseConfig = require('./base.config') const baseConfig = require('./base.config')
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants') const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../src/services/web3')
const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env const { ORACLE_FOREIGN_TX_RESEND_INTERVAL } = process.env
module.exports = { module.exports = {
...baseConfig, ...baseConfig,
main: baseConfig.foreign,
queue: 'foreign-prioritized', queue: 'foreign-prioritized',
id: 'foreign', id: 'foreign',
name: 'sender-foreign', name: 'sender-foreign',
web3: web3Foreign,
web3Redundant: web3ForeignRedundant,
web3Fallback: web3ForeignFallback,
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
} }

View File

@ -1,17 +1,14 @@
const baseConfig = require('./base.config') const baseConfig = require('./base.config')
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants') const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/services/web3')
const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env const { ORACLE_HOME_TX_RESEND_INTERVAL } = process.env
module.exports = { module.exports = {
...baseConfig, ...baseConfig,
main: baseConfig.home,
queue: 'home-prioritized', queue: 'home-prioritized',
id: 'home', id: 'home',
name: 'sender-home', name: 'sender-home',
web3: web3Home,
web3Redundant: web3HomeRedundant,
web3Fallback: web3HomeFallback,
resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
} }

View File

@ -1,11 +1,9 @@
const baseConfig = require('./base.config') const baseConfig = require('./base.config')
const { web3ForeignArchive } = require('../src/services/web3')
const id = `${baseConfig.id}-information-request` const id = `${baseConfig.id}-information-request`
module.exports = { module.exports = {
...baseConfig, ...baseConfig,
web3ForeignArchive: web3ForeignArchive || baseConfig.foreign.web3,
main: baseConfig.home, main: baseConfig.home,
event: 'UserRequestForInformation', event: 'UserRequestForInformation',
sender: 'home', sender: 'home',

View File

@ -19,6 +19,7 @@
"confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher", "confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher",
"manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager", "manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager",
"helper:interestFether": "node ./scripts/interestFetcher.js", "helper:interestFether": "node ./scripts/interestFetcher.js",
"helper:signPendingMessages": "node ./scripts/signPendingMessages.js",
"mev:watcher:collected-signatures": "./scripts/start-worker.sh mevWatcher mev-collected-signatures-watcher", "mev:watcher:collected-signatures": "./scripts/start-worker.sh mevWatcher mev-collected-signatures-watcher",
"mev:sender:foreign": "./scripts/start-worker.sh mevSender foreign-mev-sender", "mev:sender:foreign": "./scripts/start-worker.sh mevSender foreign-mev-sender",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'",

View File

@ -0,0 +1,83 @@
require('dotenv').config()
const {
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS,
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
ORACLE_HOME_START_BLOCK,
ORACLE_HOME_END_BLOCK,
ORACLE_BRIDGE_MODE
} = process.env
const fs = require('fs')
const promiseLimit = require('promise-limit')
const { web3Home, web3Foreign } = require('../src/services/web3')
const { getBridgeABIs, getPastEvents, parseAMBMessage, BRIDGE_MODES } = require('../../commons')
const { setLogger } = require('../src/services/injectedLogger')
const mockLogger = { debug: () => {}, info: () => {}, error: () => {}, child: () => mockLogger }
setLogger(mockLogger)
const limit = promiseLimit(50)
const output = process.argv[2]
async function main() {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(ORACLE_BRIDGE_MODE)
const wallet = web3Home.eth.accounts.wallet.add(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const fromBlock = parseInt(ORACLE_HOME_START_BLOCK, 10) || 0
let toBlock = parseInt(ORACLE_HOME_END_BLOCK, 10)
if (!toBlock) {
toBlock = await web3Home.eth.getBlockNumber()
}
console.log(`Getting CollectedSignatures events from block ${fromBlock} to block ${toBlock}`)
const events = await getPastEvents(homeBridge, { event: 'CollectedSignatures', fromBlock, toBlock })
console.log(`Found ${events.length} CollectedSignatures events`)
console.log('Getting messages')
let messages = await Promise.all(
events.map((event, i) => () => getMessage(ORACLE_BRIDGE_MODE, homeBridge, foreignBridge, event, i)).map(limit)
)
messages = messages.filter(x => x)
console.log(`Filtered ${messages.length} pending messages`)
const result = {}
messages.forEach(msg => {
result[msg.msgHash] = wallet.sign(msg.message).signature
})
console.log('Writing results')
if (output === '-') {
console.log(JSON.stringify(result))
} else {
fs.writeFileSync(output, JSON.stringify(result))
}
}
async function getMessage(bridgeMode, homeBridge, foreignBridge, event, i) {
if (i % 50 === 0) {
console.log(`Processing event #${i}`)
}
const msgHash = event.returnValues.messageHash
const message = await homeBridge.methods.message(msgHash).call()
let msgId
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
msgId = parseAMBMessage(message).messageId
} else {
msgId = `0x${message.slice(106, 170)}`
}
const alreadyProcessed = await foreignBridge.methods.relayedMessages(msgId).call()
if (alreadyProcessed) {
return null
}
return {
msgHash,
message
}
}
main()

View File

@ -35,11 +35,13 @@ Object.keys(asyncCalls).forEach(method => {
}) })
function processInformationRequestsBuilder(config) { function processInformationRequestsBuilder(config) {
const { home, foreign, web3ForeignArchive } = config const { home, foreign } = config
let validatorContract = null let validatorContract = null
let blockFinder = null let blockFinder = null
foreign.web3Archive.currentProvider.startSyncStateChecker(foreign.syncCheckInterval)
return async function processInformationRequests(informationRequests) { return async function processInformationRequests(informationRequests) {
const txToSend = [] const txToSend = []
@ -49,13 +51,15 @@ function processInformationRequestsBuilder(config) {
if (blockFinder === null) { if (blockFinder === null) {
rootLogger.debug('Initializing block finder') rootLogger.debug('Initializing block finder')
blockFinder = await makeBlockFinder('foreign', foreign.web3) blockFinder = await makeBlockFinder('foreign', foreign.web3Archive)
} }
// latest foreign block is requested from an archive RPC, to ensure that it is synced with the network
// block confirmations can be requested from the regular JSON RPC
const foreignBlockNumber = const foreignBlockNumber =
(await getBlockNumber(foreign.web3)) - (await getRequiredBlockConfirmations(foreign.bridgeContract)) (await getBlockNumber(foreign.web3Archive)) - (await getRequiredBlockConfirmations(foreign.bridgeContract))
const homeBlock = await getBlock(home.web3, informationRequests[0].blockNumber) const homeBlock = await getBlock(home.web3, informationRequests[0].blockNumber)
const lastForeignBlock = await getBlock(foreign.web3, foreignBlockNumber) const lastForeignBlock = await getBlock(foreign.web3Archive, foreignBlockNumber)
if (homeBlock.timestamp > lastForeignBlock.timestamp) { if (homeBlock.timestamp > lastForeignBlock.timestamp) {
rootLogger.debug( rootLogger.debug(
@ -85,7 +89,7 @@ function processInformationRequestsBuilder(config) {
logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request') logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request')
const call = asyncCalls[asyncCallMethod] const call = asyncCalls[asyncCallMethod]
let [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => { let [status, result] = await call(foreign.web3Archive, data, foreignClosestBlock).catch(e => {
if (e instanceof HttpListProviderError) { if (e instanceof HttpListProviderError) {
throw e throw e
} }

View File

@ -32,8 +32,8 @@ if (process.argv.length < 3) {
const config = require(path.join('../config/', process.argv[2])) const config = require(path.join('../config/', process.argv[2]))
const { web3, web3Fallback } = config const { web3, web3Fallback, syncCheckInterval } = config.main
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : web3 const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.main.web3Redundant : web3
const nonceKey = `${config.id}:nonce` const nonceKey = `${config.id}:nonce`
let chainId = 0 let chainId = 0
@ -43,6 +43,7 @@ async function initialize() {
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(config.id)) web3.currentProvider.urls.forEach(checkHttps(config.id))
web3.currentProvider.startSyncStateChecker(syncCheckInterval)
GasPrice.start(config.id, web3) GasPrice.start(config.id, web3)

View File

@ -1,5 +1,6 @@
const fetch = require('node-fetch') const fetch = require('node-fetch')
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const { utils } = require('web3')
const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants') const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants')
const { onInjected } = require('./injectedLogger') const { onInjected } = require('./injectedLogger')
@ -39,19 +40,54 @@ function HttpListProvider(urls, options = {}) {
this.options = { ...defaultOptions, ...options } this.options = { ...defaultOptions, ...options }
this.currentIndex = 0 this.currentIndex = 0
this.lastTimeUsedPrimary = 0 this.lastTimeUsedPrimary = 0
this.latestBlock = 0
this.syncStateCheckerIntervalId = 0
onInjected(logger => { onInjected(logger => {
this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` }) this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` })
}) })
} }
HttpListProvider.prototype.switchToFallbackRPC = function() { HttpListProvider.prototype.startSyncStateChecker = function(syncCheckInterval) {
if (this.urls.length < 2) { if (this.urls.length > 1 && syncCheckInterval > 0 && this.syncStateCheckerIntervalId === 0) {
this.syncStateCheckerIntervalId = setInterval(this.checkLatestBlock.bind(this), syncCheckInterval)
}
}
HttpListProvider.prototype.checkLatestBlock = function() {
const payload = { jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }
this.send(payload, (error, result) => {
if (error) {
this.logger.warn({ oldBlock: this.latestBlock }, 'Failed to request latest block from all RPC urls')
} else if (result.error) {
this.logger.warn(
{ oldBlock: this.latestBlock, error: result.error.message },
'Failed to make eth_blockNumber request due to unknown error, switching to fallback RPC'
)
this.switchToFallbackRPC()
} else {
const blockNumber = utils.hexToNumber(result.result)
if (blockNumber > this.latestBlock) {
this.logger.debug({ oldBlock: this.latestBlock, newBlock: blockNumber }, 'Updating latest block number')
this.latestBlock = blockNumber
} else {
this.logger.warn(
{ oldBlock: this.latestBlock, newBlock: blockNumber },
'Latest block on the node was not updated since last request, switching to fallback RPC'
)
this.switchToFallbackRPC()
}
}
})
}
HttpListProvider.prototype.switchToFallbackRPC = function(index) {
const prevIndex = this.currentIndex
const newIndex = index || (prevIndex + 1) % this.urls.length
if (this.urls.length < 2 || prevIndex === newIndex) {
return return
} }
const prevIndex = this.currentIndex
const newIndex = (prevIndex + 1) % this.urls.length
this.logger.info( this.logger.info(
{ index: newIndex, oldURL: this.urls[prevIndex], newURL: this.urls[newIndex] }, { index: newIndex, oldURL: this.urls[prevIndex], newURL: this.urls[newIndex] },
'Switching to fallback JSON-RPC URL' 'Switching to fallback JSON-RPC URL'
@ -80,11 +116,7 @@ HttpListProvider.prototype.send = async function send(payload, callback) {
// if some of URLs failed to respond, current URL index is updated to the first URL that responded // if some of URLs failed to respond, current URL index is updated to the first URL that responded
if (currentIndex !== index) { if (currentIndex !== index) {
this.logger.info( this.switchToFallbackRPC(index)
{ index, oldURL: this.urls[currentIndex], newURL: this.urls[index] },
'Switching to fallback JSON-RPC URL'
)
this.currentIndex = index
} }
callback(null, result) callback(null, result)
} catch (e) { } catch (e) {

View File

@ -27,7 +27,6 @@ module.exports = {
MIN_GAS_PRICE_BUMP_FACTOR: 0.1, MIN_GAS_PRICE_BUMP_FACTOR: 0.1,
DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000, DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000,
FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000, FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000,
BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT: 10,
SENDER_QUEUE_MAX_PRIORITY: 10, SENDER_QUEUE_MAX_PRIORITY: 10,
SENDER_QUEUE_SEND_PRIORITY: 5, SENDER_QUEUE_SEND_PRIORITY: 5,
SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1, SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1,

View File

@ -169,7 +169,8 @@ function isNonceError(e) {
message.includes('transaction nonce is too low') || message.includes('transaction nonce is too low') ||
message.includes('nonce too low') || message.includes('nonce too low') ||
message.includes('transaction with same nonce in the queue') || 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`)
) )
} }

View File

@ -6,11 +6,7 @@ const logger = require('./services/logger')
const { getShutdownFlag } = require('./services/shutdownState') const { getShutdownFlag } = require('./services/shutdownState')
const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3') const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3')
const { checkHTTPS, watchdog } = require('./utils/utils') const { checkHTTPS, watchdog } = require('./utils/utils')
const { const { EXIT_CODES, MAX_HISTORY_BLOCK_TO_REPROCESS } = require('./utils/constants')
EXIT_CODES,
BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT,
MAX_HISTORY_BLOCK_TO_REPROCESS
} = require('./utils/constants')
if (process.argv.length < 3) { if (process.argv.length < 3) {
logger.error('Please check the number of arguments, config file was not provided') logger.error('Please check the number of arguments, config file was not provided')
@ -38,21 +34,21 @@ const {
pollingInterval, pollingInterval,
chain, chain,
reprocessingOptions, reprocessingOptions,
blockPollingLimit blockPollingLimit,
syncCheckInterval
} = config.main } = config.main
const lastBlockRedisKey = `${config.id}:lastProcessedBlock` const lastBlockRedisKey = `${config.id}:lastProcessedBlock`
const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock` const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock`
const seenEventsRedisKey = `${config.id}:seenEvents` const seenEventsRedisKey = `${config.id}:seenEvents`
let lastProcessedBlock = Math.max(startBlock - 1, 0) let lastProcessedBlock = Math.max(startBlock - 1, 0)
let lastReprocessedBlock let lastReprocessedBlock
let lastSeenBlockNumber = 0
let sameBlockNumberCounter = 0
async function initialize() { async function initialize() {
try { try {
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(chain)) web3.currentProvider.urls.forEach(checkHttps(chain))
web3.currentProvider.startSyncStateChecker(syncCheckInterval)
await getLastProcessedBlock() await getLastProcessedBlock()
await getLastReprocessedBlock() await getLastReprocessedBlock()
@ -225,28 +221,6 @@ async function getLastBlockToProcess(web3, bridgeContract) {
getBlockNumber(web3), getBlockNumber(web3),
getRequiredBlockConfirmations(bridgeContract) getRequiredBlockConfirmations(bridgeContract)
]) ])
if (lastBlockNumber < lastSeenBlockNumber) {
sameBlockNumberCounter = 0
logger.warn({ lastBlockNumber, lastSeenBlockNumber }, 'Received block number less than already seen block')
web3.currentProvider.switchToFallbackRPC()
} else if (lastBlockNumber === lastSeenBlockNumber) {
sameBlockNumberCounter++
if (sameBlockNumberCounter > 1) {
logger.info({ lastBlockNumber, sameBlockNumberCounter }, 'Received the same block number more than twice')
if (sameBlockNumberCounter >= BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT) {
sameBlockNumberCounter = 0
logger.warn(
{ lastBlockNumber, n: BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT },
'Received the same block number for too many times. Probably node is not synced anymore'
)
web3.currentProvider.switchToFallbackRPC()
}
}
} else {
sameBlockNumberCounter = 0
lastSeenBlockNumber = lastBlockNumber
}
return lastBlockNumber - requiredBlockConfirmations return lastBlockNumber - requiredBlockConfirmations
} }