Compare commits

..

5 Commits

Author SHA1 Message Date
Alexander Kolotov
af6848f39c Merge branch 'mev-executor' into fake-mev-executor 2022-02-07 23:21:53 +03:00
Kirill Fedoseev
72e1242544 Refine configuration 2022-02-07 20:22:20 +04:00
Kirill Fedoseev
13be1a4b6a Update log level 2022-02-07 18:51:51 +04:00
Kirill Fedoseev
9586b78e10 Fake MEV execution for BSC 2022-02-02 22:46:51 +03:00
Kirill Fedoseev
3e84b01f3b Helper watcher process for MEV bundling 2022-01-31 23:13:36 +03:00
39 changed files with 398 additions and 806 deletions

View File

@ -56,11 +56,9 @@ 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) | Responsible for listening to bridge related events and authorizing asset transfers. | | [Oracle](oracle/README.md) | Oracle 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, { useEffect, useState } from 'react' import React 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, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { CONFIRMATIONS_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,9 +54,7 @@ export const ConfirmationsContainer = ({
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
} = useStateProvider() } = useStateProvider()
const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0) const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
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,
@ -73,21 +71,11 @@ export const ConfirmationsContainer = ({
fromHome, fromHome,
homeStartBlock, homeStartBlock,
foreignStartBlock, foreignStartBlock,
requiredSignatures: src.requiredSignatures, requiredSignatures,
validatorList: src.validatorList, 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 = () => {
@ -126,22 +114,20 @@ export const ConfirmationsContainer = ({
</MultiLine> </MultiLine>
</StatusDescription> </StatusDescription>
<ValidatorsConfirmations <ValidatorsConfirmations
confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations} confirmations={confirmations}
requiredSignatures={dst.requiredSignatures} requiredSignatures={requiredSignatures}
validatorList={dst.validatorList} validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved} waitingBlocksResolved={waitingBlocksResolved}
/> />
{signatureCollected && ( {signatureCollected && (
<ExecutionConfirmation <ExecutionConfirmation
message={message} messageData={message.data}
executionData={executionData} executionData={executionData}
isHome={!fromHome} isHome={!fromHome}
confirmations={confirmations} signatureCollected={signatureCollected}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched} executionEventsFetched={executionEventsFetched}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
dstRequiredSignatures={dst.requiredSignatures}
dstValidatorList={dst.validatorList}
/> />
)} )}
</StyledConfirmationContainer> </StyledConfirmationContainer>

View File

@ -4,47 +4,38 @@ 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 { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations' import { 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 {
message: MessageObject messageData: string
executionData: ExecutionData executionData: ExecutionData
setExecutionData: Function setExecutionData: Function
confirmations: ConfirmationParam[] signatureCollected: boolean | string[]
isHome: boolean isHome: boolean
executionEventsFetched: boolean executionEventsFetched: boolean
setPendingExecution: Function setPendingExecution: Function
dstRequiredSignatures: number
dstValidatorList: string[]
} }
export const ExecutionConfirmation = ({ export const ExecutionConfirmation = ({
message, messageData,
executionData, executionData,
setExecutionData, setExecutionData,
confirmations, signatureCollected,
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 ||
@ -76,27 +67,9 @@ 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.EXECUTION_SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.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>
@ -114,8 +87,6 @@ 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>
@ -154,13 +125,10 @@ export const ExecutionConfirmation = ({
<td> <td>
<ManualExecutionButton <ManualExecutionButton
safeExecutionAvailable={safeExecutionAvailable} safeExecutionAvailable={safeExecutionAvailable}
messageData={message.data} messageData={messageData}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
confirmations={confirmations} signatureCollected={signatureCollected as string[]}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
setError={setError}
requiredSignatures={dstRequiredSignatures}
validatorList={dstValidatorList}
/> />
</td> </td>
)} )}

View File

@ -8,6 +8,7 @@ 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;
@ -51,7 +52,7 @@ export interface FormSubmitParams {
export const MainPage = () => { export const MainPage = () => {
const history = useHistory() const history = useHistory()
const { home, foreign } = useStateProvider() const { home, foreign, error, setError } = 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)
@ -131,6 +132,7 @@ 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,7 +14,6 @@ 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);
@ -31,75 +30,22 @@ interface ManualExecutionButtonParams {
safeExecutionAvailable: boolean safeExecutionAvailable: boolean
messageData: string messageData: string
setExecutionData: Function setExecutionData: Function
confirmations: ConfirmationParam[] signatureCollected: string[]
setPendingExecution: Function setPendingExecution: Function
setError: Function
requiredSignatures: number
validatorList: string[]
} }
export const ManualExecutionButton = ({ export const ManualExecutionButton = ({
safeExecutionAvailable, safeExecutionAvailable,
messageData, messageData,
setExecutionData, setExecutionData,
confirmations, signatureCollected,
setPendingExecution, setPendingExecution
setError,
requiredSignatures,
validatorList
}: ManualExecutionButtonParams) => { }: ManualExecutionButtonParams) => {
const { foreign } = useStateProvider() const { foreign, setError } = 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 [ready, setReady] = useState(false) const notReady = !foreign.bridgeContract || !signatureCollected || !signatureCollected.length
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(
() => { () => {
@ -127,9 +73,9 @@ export const ManualExecutionButton = ({
return return
} }
if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
const signatures = packSignatures(validSignatures.map(signatureToVRS)) const signatures = packSignatures(signatureCollected.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 =
@ -194,20 +140,19 @@ 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={!ready} className="button outline" onClick={() => setManualExecution(true)}> <ActionButton disabled={notReady} className="button outline" onClick={() => setManualExecution(true)}>
{title} Execute
</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 { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { 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,9 +31,7 @@ export const ValidatorsConfirmations = ({
const getValidatorStatusElement = (validatorStatus = '') => { const getValidatorStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
case VALIDATOR_CONFIRMATION_STATUS.MANUAL: return <SuccessLabel>{validatorStatus}</SuccessLabel>
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:
@ -60,28 +58,26 @@ export const ValidatorsConfirmations = ({
</tr> </tr>
</Thead> </Thead>
<tbody> <tbody>
{confirmations.map((confirmation, i) => { {validatorList.map((validator, i) => {
const displayedStatus = confirmation.status const filteredConfirmation = confirmations.filter(c => c.validator === validator)
const explorerLink = getExplorerTxUrl(confirmation.txHash, true) const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
let elementIfNoTimestamp: any = <SimpleLoading /> const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
switch (displayedStatus) { const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
case '': const elementIfNoTimestamp =
case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED: displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
if (waitingBlocksResolved) { displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
elementIfNoTimestamp = SEARCHING_TX (displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') &&
} waitingBlocksResolved ? (
break SEARCHING_TX
case VALIDATOR_CONFIRMATION_STATUS.WAITING: ) : (
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED: <SimpleLoading />
elementIfNoTimestamp = '' )
break ) : (
case VALIDATOR_CONFIRMATION_STATUS.MANUAL: ''
elementIfNoTimestamp = RECENT_AGE )
break
}
return ( return (
<tr key={i}> <tr key={i}>
<td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td> <td>{windowWidth < 850 ? formatTxHash(validator) : 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 ? (
@ -98,7 +94,7 @@ export const ValidatorsConfirmations = ({
</tbody> </tbody>
</table> </table>
<RequiredConfirmations> <RequiredConfirmations>
At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required {requiredSignatures} of {validatorList.length} 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-12 is-vertical-align row"> <StyledErrorAlert className="col-10 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

@ -1,34 +0,0 @@
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,19 +54,14 @@ export const CONFIRMATIONS_STATUS = {
} }
export const VALIDATOR_CONFIRMATION_STATUS = { export const VALIDATOR_CONFIRMATION_STATUS = {
SUCCESS: 'Confirmed', SUCCESS: 'Success',
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,16 +29,17 @@ 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 ConfirmationParam { export interface BasicConfirmationParam {
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 {
@ -47,7 +48,6 @@ export interface ExecutionData {
txHash: string txHash: string
timestamp: number timestamp: number
executionResult: boolean executionResult: boolean
blockNumber: number
} }
export const useMessageConfirmations = ({ export const useMessageConfirmations = ({
@ -58,7 +58,6 @@ export const useMessageConfirmations = ({
foreignStartBlock, foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
targetValidatorList,
blockConfirmations blockConfirmations
}: useMessageConfirmationsParams) => { }: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
@ -66,7 +65,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(false) const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(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>({
@ -74,8 +73,7 @@ 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)
@ -142,9 +140,10 @@ 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 || !signatureCollected) return if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return
let timeoutId: number let timeoutId: number
let isCancelled = false let isCancelled = false
@ -180,7 +179,7 @@ export const useMessageConfirmations = ({
isCancelled = true isCancelled = true
} }
}, },
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected] [fromHome, home.bridgeContract, home.web3, message.data, receipt, hasCollectedSignatures]
) )
// 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
@ -253,35 +252,6 @@ 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,
@ -314,8 +284,7 @@ export const useMessageConfirmations = ({
home.bridgeContract, home.bridgeContract,
requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
homeStartBlock, homeStartBlock
targetValidatorList
] ]
) )
@ -374,10 +343,7 @@ export const useMessageConfirmations = ({
// Sets the message status based in the collected information // Sets the message status based in the collected information
useEffect( useEffect(
() => { () => {
if ( if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) {
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,13 +4,19 @@ 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 const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => { export interface useValidatorContractParams {
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<string[]>([]) const [validatorList, setValidatorList] = useState([])
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
@ -23,34 +29,34 @@ export const useValidatorContract = (isHome: boolean, blockNumber: number | 'lat
const callRequiredSignatures = async ( const callRequiredSignatures = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
blockNumber: number | 'latest', receipt: TransactionReceipt,
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, blockNumber, snapshotProvider, web3, api) const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api)
setResult(result) setResult(result)
} }
const callValidatorList = async ( const callValidatorList = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
blockNumber: number | 'latest', receipt: TransactionReceipt,
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, blockNumber, snapshotProvider, web3, api) const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api)
setResult(result) setResult(result)
} }
const web3 = isHome ? home.web3 : foreign.web3 const web3 = fromHome ? home.web3 : foreign.web3
const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
const snapshotProvider = isHome ? homeSnapshotProvider : foreignSnapshotProvider const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
useEffect( useEffect(
() => { () => {
@ -62,11 +68,11 @@ export const useValidatorContract = (isHome: boolean, blockNumber: number | 'lat
useEffect( useEffect(
() => { () => {
if (!web3 || !blockNumber) return if (!web3 || !receipt) return
callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api) callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api)
callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api) callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api)
}, },
[validatorContract, blockNumber, web3, snapshotProvider, api] [validatorContract, receipt, web3, snapshotProvider, api]
) )
return { return {

View File

@ -1,4 +1,4 @@
import React, { createContext, ReactNode } from 'react' import React, { createContext, ReactNode, useState } from 'react'
import { useNetwork } from '../hooks/useNetwork' import { useNetwork } from '../hooks/useNetwork'
import { import {
HOME_RPC_URL, HOME_RPC_URL,
@ -25,6 +25,8 @@ export interface StateContext {
home: BaseNetworkParams home: BaseNetworkParams
foreign: BaseNetworkParams foreign: BaseNetworkParams
loading: boolean loading: boolean
error: string
setError: Function
} }
const initialState = { const initialState = {
@ -42,7 +44,9 @@ 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)
@ -54,6 +58,7 @@ 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: {
@ -68,7 +73,9 @@ 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,7 +28,5 @@ 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,10 +17,6 @@ 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,40 +14,37 @@ 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, from: validator1 }, { isError: '0', to },
{ isError: '1', to, from: validator1 }, { isError: '1', to },
{ isError: '0', to, from: validator2 }, { isError: '0', to },
{ isError: '1', to, from: validator2 }, { isError: '1', to },
{ isError: '1', to, from: validator3 } { isError: '1', to }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions) const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(1) expect(result.length).toEqual(3)
}) })
}) })
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, from: validator1 }, { isError: '0', to },
{ isError: '1', to, from: validator1 }, { isError: '1', to },
{ isError: '0', to, from: validator2 }, { isError: '0', to },
{ isError: '1', to, from: validator2 }, { isError: '1', to },
{ isError: '1', to, from: validator3 } { isError: '1', to }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions) const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(1) expect(result.length).toEqual(2)
}) })
}) })
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 { ConfirmationParam } from '../../hooks/useMessageConfirmations' import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
jest.mock('../validatorConfirmationHelpers') jest.mock('../validatorConfirmationHelpers')
@ -18,9 +18,6 @@ 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'
@ -28,7 +25,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3] const validatorList = [validator1, validator2, validator3]
const signature = const signature =
'0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b' '0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
const bridgeContract = { const bridgeContract = {
methods: { methods: {
signature: () => ({ signature: () => ({
@ -64,19 +61,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: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -113,8 +110,9 @@ 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(1) expect(setSignatureCollected).toBeCalledTimes(2)
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)
@ -137,7 +135,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, txHash: '', timestamp: 0 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
}) })
@ -146,19 +144,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: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -210,19 +208,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: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -259,8 +257,9 @@ 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(1) expect(setSignatureCollected).toBeCalledTimes(2)
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)
@ -282,16 +281,16 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
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, txHash: '', timestamp: 0 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
}) })
@ -305,22 +304,22 @@ describe('getConfirmationsForTx', () => {
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID ? VALIDATOR_CONFIRMATION_STATUS.FAILED
: 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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -357,8 +356,9 @@ 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(1) expect(setSignatureCollected).toBeCalledTimes(2)
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, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ 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, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
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_VALID, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
]) ])
) )
}) })
@ -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: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator2 validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID ? VALIDATOR_CONFIRMATION_STATUS.FAILED
: 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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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, txHash: '0x123', timestamp: 123 }, { 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.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
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 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, 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 }
]) ])
) )
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_VALID, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, 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: ConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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: ConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
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, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
@ -610,13 +610,9 @@ describe('getConfirmationsForTx', () => {
) )
}) })
test('should remove pending state after transaction mined', async () => { test('should remove pending state after transaction mined', async () => {
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3' // Validator1 success
const validatorList = [validator1, validator2, validator3, validator4] // Validator2 failed
// 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) => ({
@ -627,57 +623,41 @@ describe('getConfirmationsForTx', () => {
.mockImplementation(() => async (validator: string) => ({ .mockImplementation(() => async (validator: string) => ({
validator, validator,
status: status:
validator === validator1 || validator === validator3 validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x100' : '', txHash: validatorData.validator === validator1 ? '0x123' : '',
timestamp: validatorData.validator === validator1 ? 100 : 0 timestamp: validatorData.validator === validator1 ? 123 : 0
})) }))
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({ .mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: txHash: validatorData.validator !== validator2 ? '0x123' : '',
validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '', timestamp: validatorData.validator !== validator2 ? 123 : 0
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: ConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
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 ? '0x300' : '', txHash: validatorData.validator === validator3 ? '0x123' : '',
timestamp: validatorData.validator === validator3 ? 300 : 0 timestamp: validatorData.validator === validator3 ? 123 : 0
})) }))
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -718,7 +698,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(true) expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(getValidatorPendingTransaction).toBeCalledTimes(1)
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
@ -732,32 +712,28 @@ 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, txHash: '0x100', timestamp: 100 }, { 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.PENDING, txHash: '0x123', timestamp: 123 }
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
{ 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: '0x100', timestamp: 100 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
@ -785,13 +761,14 @@ 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(2) expect(setSignatureCollected).toBeCalledTimes(3)
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(true) expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false) expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(getValidatorPendingTransaction).toBeCalledTimes(1)
@ -804,26 +781,23 @@ 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: '0x100', timestamp: 100 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
{ 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: '0x100', timestamp: 100 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
{ 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: '0x100', timestamp: 100 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
]) ])
) )
}) })

View File

@ -84,11 +84,10 @@ 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.EXECUTION_SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash, txHash,
timestamp, timestamp,
executionResult: true, executionResult: true
blockNumber: 5523145
}) })
expect(getFailedExecution).toBeCalledTimes(0) expect(getFailedExecution).toBeCalledTimes(0)
@ -238,8 +237,7 @@ 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)
@ -296,8 +294,7 @@ 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,16 +22,6 @@ 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()
@ -45,10 +35,16 @@ export const getRequiredBlockConfirmations = async (
const events = [...eventsFromSnapshot, ...contractEvents] const events = [...eventsFromSnapshot, ...contractEvents]
// Use the value from last event before the transaction let blockConfirmations
const event = events[events.length - 1] if (events.length > 0) {
blockConfirmations = event.returnValues.requiredBlockConfirmations // Use the value from last event before the transaction
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)
} }
@ -56,25 +52,11 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
export const getRequiredSignatures = async ( export const getRequiredSignatures = async (
contract: Contract, contract: Contract,
blockNumber: number | 'latest', blockNumber: number,
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()
@ -90,25 +72,17 @@ 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]
;({ requiredSignatures } = event.returnValues) const { requiredSignatures } = event.returnValues
return parseInt(requiredSignatures) return parseInt(requiredSignatures)
} }
export const getValidatorList = async ( export const getValidatorList = async (
contract: Contract, contract: Contract,
blockNumber: number | 'latest', blockNumber: number,
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,7 +12,6 @@ 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
@ -55,7 +54,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', 'to') url.searchParams.append('filterby', 'from')
url.searchParams.append('startblock', startBlock.toString()) url.searchParams.append('startblock', startBlock.toString())
url.searchParams.append('endblock', endBlock.toString()) url.searchParams.append('endblock', endBlock.toString())
@ -65,7 +64,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
return [] return []
} }
return result.result || [] return result.result
} }
export const fetchPendingTransactions = async ({ export const fetchPendingTransactions = async ({
@ -181,9 +180,7 @@ 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++) {
if (topics[j] !== null) { url.searchParams.append(`topic${j}_${i}_opr`, 'and')
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
}
} }
} }
} }
@ -197,7 +194,7 @@ export const getLogs = async (
})) }))
} }
const filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase() const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase()
export const getFailedTransactions = async ( export const getFailedTransactions = async (
account: string, account: string,
@ -207,9 +204,9 @@ export const getFailedTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api }) const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError !== '0').filter(filterSender(account)) return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to))
} }
export const getSuccessTransactions = async ( export const getSuccessTransactions = async (
@ -220,9 +217,9 @@ export const getSuccessTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api }) const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError === '0').filter(filterSender(account)) return transactions.filter(t => t.isError === '0').filter(filterReceiver(to))
} }
export const filterValidatorSignatureTransaction = ( export const filterValidatorSignatureTransaction = (

View File

@ -2,37 +2,26 @@ 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 { ConfirmationParam } from '../hooks/useMessageConfirmations' import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
import { signatureToVRS } from './signatures'
const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => { const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
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.txHash || (validatorData as ConfirmationParam).txHash ||
!!validatorData.signature ||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED) (newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
) { ) {
confirmations[index] = { confirmations[index] = validatorData
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
@ -56,17 +45,19 @@ 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, fromHome)) validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
) )
const updateConfirmations = (confirmations: ConfirmationParam[]) => { const updateConfirmations = (confirmations: BasicConfirmationParam[]) => {
if (confirmations.length === 0) { if (confirmations.length === 0) {
return return
} }
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations) validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
setResult((currentConfirmations: ConfirmationParam[]) => { setResult((currentConfirmations: BasicConfirmationParam[]) => {
if (currentConfirmations && currentConfirmations.length) { if (currentConfirmations && currentConfirmations.length) {
return mergeConfirmations(currentConfirmations, confirmations) return mergeConfirmations(currentConfirmations, confirmations)
} }
@ -76,37 +67,11 @@ 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
@ -119,6 +84,16 @@ 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(
@ -128,27 +103,13 @@ 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(web3, bridgeContract, messageData, startBlock, getFailedTransactions) getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
) )
) )
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter( const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
)
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(
@ -159,13 +120,21 @@ 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,12 +59,11 @@ export const getSuccessExecutionData = async (
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from) const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.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
@ -116,8 +115,7 @@ 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 {
@ -146,8 +144,7 @@ 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,45 +1,38 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { BasicConfirmationParam, 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,
fromHome: boolean confirmationContractMethod: Function
) => async (validator: string): Promise<ConfirmationParam> => { ) => async (validator: string): Promise<BasicConfirmationParam> => {
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
const fromCache = validatorsCache.getData(hashSenderMsg) const signatureFromCache = validatorsCache.get(hashSenderMsg)
if (fromCache) { if (signatureFromCache) {
return fromCache return {
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) {
const confirmation: ConfirmationParam = { validatorsCache.set(hashSenderMsg, confirmed)
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator,
timestamp: 0,
txHash: ''
}
validatorsCache.setData(hashSenderMsg, confirmation)
return confirmation
} }
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator, validator,
timestamp: 0, status
txHash: ''
} }
} }
@ -50,7 +43,7 @@ export const getSuccessExecutionTransaction = (
messageData: string, messageData: string,
startBlock: number, startBlock: number,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: BasicConfirmationParam): 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)
@ -94,12 +87,11 @@ 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: ConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: BasicConfirmationParam): 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)
@ -114,33 +106,30 @@ 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]
const confirmation: ConfirmationParam = { txHashTimestamp = parseInt(failedTx.timeStamp)
status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash = failedTx.hash
validator: validatorData.validator,
txHash: failedTx.hash,
timestamp: parseInt(failedTx.timeStamp)
}
if (failedTx.input && failedTx.input.length > 10) { validatorsCache.setData(validatorCacheKey, {
try { validator: validatorData.validator,
const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`) status: newStatus,
confirmation.signature = res[0] txHash,
confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID timestamp: txHashTimestamp
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,
txHash: '', status: newStatus,
timestamp: 0 txHash,
timestamp: txHashTimestamp
} }
} }
@ -148,7 +137,7 @@ export const getValidatorPendingTransaction = (
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]> getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: BasicConfirmationParam): 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,37 +10,6 @@ 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))
@ -57,33 +26,15 @@ 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 => {
const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]]) let decodedLogs: { [p: string]: string } = {
let sender, executor, obToken, obReceiver messageId: '',
if (encodedData.length >= 160) { encodedData: ''
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)}`
}
} }
return { if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) {
id: messageId || '', decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]])
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
An oracle responsible for listening to bridge related events and authorizing asset transfers. 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,15 +7,7 @@ const {
HOME_AMB_ABI, HOME_AMB_ABI,
FOREIGN_AMB_ABI FOREIGN_AMB_ABI
} = require('../../commons') } = require('../../commons')
const { const { web3Home, web3Foreign } = require('../src/services/web3')
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')
@ -35,11 +27,9 @@ 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
@ -73,12 +63,9 @@ 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: {
@ -94,13 +81,9 @@ 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,14 +1,17 @@
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,14 +1,17 @@
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,9 +1,11 @@
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

@ -57,5 +57,8 @@ services:
ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY: 82db7175932f4e6c8e45283b78b54fd5f195149378ec90d95b8fd0ec8bdadf1d ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY: 82db7175932f4e6c8e45283b78b54fd5f195149378ec90d95b8fd0ec8bdadf1d
ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE: '5' ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE: '5'
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '70000' # time between sending different batches of MEV bundles (~= 5 blocks * 14 seconds) ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '70000' # time between sending different batches of MEV bundles (~= 5 blocks * 14 seconds)
FAKE_MEV_RELAY_TX_SENDING: 'true'
FAKE_MEV_RELAY_NO_EIP1559: 'true'
COMMON_FOREIGN_GAS_PRICE_FALLBACK: 100000000000 # will essentially replace block base fee
restart: unless-stopped restart: unless-stopped
entrypoint: yarn mev:sender:foreign entrypoint: yarn mev:sender:foreign

View File

@ -19,7 +19,6 @@
"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

@ -1,83 +0,0 @@
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,13 +35,11 @@ Object.keys(asyncCalls).forEach(method => {
}) })
function processInformationRequestsBuilder(config) { function processInformationRequestsBuilder(config) {
const { home, foreign } = config const { home, foreign, web3ForeignArchive } = 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 = []
@ -51,15 +49,13 @@ function processInformationRequestsBuilder(config) {
if (blockFinder === null) { if (blockFinder === null) {
rootLogger.debug('Initializing block finder') rootLogger.debug('Initializing block finder')
blockFinder = await makeBlockFinder('foreign', foreign.web3Archive) blockFinder = await makeBlockFinder('foreign', foreign.web3)
} }
// 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.web3Archive)) - (await getRequiredBlockConfirmations(foreign.bridgeContract)) (await getBlockNumber(foreign.web3)) - (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.web3Archive, foreignBlockNumber) const lastForeignBlock = await getBlock(foreign.web3, foreignBlockNumber)
if (homeBlock.timestamp > lastForeignBlock.timestamp) { if (homeBlock.timestamp > lastForeignBlock.timestamp) {
rootLogger.debug( rootLogger.debug(
@ -89,7 +85,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(foreign.web3Archive, data, foreignClosestBlock).catch(e => { let [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => {
if (e instanceof HttpListProviderError) { if (e instanceof HttpListProviderError) {
throw e throw e
} }

View File

@ -15,9 +15,13 @@ if (process.argv.length < 3) {
} }
const config = require(path.join('../config/', process.argv[2])) const config = require(path.join('../config/', process.argv[2]))
const GasPrice = require('./services/gasPrice')
const { web3, mevForeign, validatorAddress } = config const { web3, mevForeign, validatorAddress } = config
const FAKE_MEV_RELAY_TX_SENDING = process.env.FAKE_MEV_RELAY_TX_SENDING === 'true'
const FAKE_MEV_RELAY_NO_EIP1559 = process.env.FAKE_MEV_RELAY_NO_EIP1559 === 'true'
let chainId = 0 let chainId = 0
let flashbotsProvider let flashbotsProvider
@ -28,7 +32,9 @@ async function initialize() {
web3.currentProvider.urls.forEach(checkHttps(config.id)) web3.currentProvider.urls.forEach(checkHttps(config.id))
chainId = await getChainId(web3) chainId = await getChainId(web3)
flashbotsProvider = await mevForeign.getFlashbotsProvider(chainId) if (!FAKE_MEV_RELAY_TX_SENDING) {
flashbotsProvider = await mevForeign.getFlashbotsProvider(chainId)
}
return runMain() return runMain()
} catch (e) { } catch (e) {
logger.error(e.message) logger.error(e.message)
@ -65,7 +71,13 @@ async function main() {
return return
} }
const { baseFeePerGas: pendingBaseFee, number: pendingBlockNumber } = await getBlock(web3, 'pending') const { baseFeePerGas, number: pendingBlockNumber } = await getBlock(web3, 'pending')
let pendingBaseFee = baseFeePerGas
if (FAKE_MEV_RELAY_TX_SENDING && FAKE_MEV_RELAY_NO_EIP1559) {
// emulate baseFee with the current gasPrice
await GasPrice.start('foreign', web3, true)
pendingBaseFee = GasPrice.gasPriceOptions().gasPrice
}
const bestJob = pickBestJob(jobs, pendingBaseFee) const bestJob = pickBestJob(jobs, pendingBaseFee)
if (!bestJob) { if (!bestJob) {
@ -115,7 +127,7 @@ async function main() {
{ nonce, fromBlock: pendingBlockNumber, toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1 }, { nonce, fromBlock: pendingBlockNumber, toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1 },
'Sending MEV bundles' 'Sending MEV bundles'
) )
const txHash = await sendTx({ const opts = {
data: bestJob.data, data: bestJob.data,
nonce, nonce,
value: bestJob.value, value: bestJob.value,
@ -134,7 +146,14 @@ async function main() {
toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1, toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1,
logger logger
} }
}) }
if (FAKE_MEV_RELAY_TX_SENDING) {
if (FAKE_MEV_RELAY_NO_EIP1559) {
opts.gasPriceOptions = { gasPrice: pendingBaseFee }
}
delete opts.mevOptions
}
const txHash = await sendTx(opts)
jobLogger.info({ txHash }, `Tx generated ${txHash} for event Tx ${bestJob.transactionReference}`) jobLogger.info({ txHash }, `Tx generated ${txHash} for event Tx ${bestJob.transactionReference}`)

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, syncCheckInterval } = config.main const { web3, web3Fallback } = config
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.main.web3Redundant : web3 const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : web3
const nonceKey = `${config.id}:nonce` const nonceKey = `${config.id}:nonce`
let chainId = 0 let chainId = 0
@ -43,7 +43,6 @@ 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,6 +1,5 @@
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')
@ -40,54 +39,19 @@ 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.startSyncStateChecker = function(syncCheckInterval) { HttpListProvider.prototype.switchToFallbackRPC = function() {
if (this.urls.length > 1 && syncCheckInterval > 0 && this.syncStateCheckerIntervalId === 0) { if (this.urls.length < 2) {
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'
@ -116,7 +80,11 @@ 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.switchToFallbackRPC(index) this.logger.info(
{ 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,6 +27,7 @@ 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,8 +169,7 @@ 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,7 +6,11 @@ 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 { EXIT_CODES, MAX_HISTORY_BLOCK_TO_REPROCESS } = require('./utils/constants') const {
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')
@ -34,21 +38,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()
@ -221,6 +225,28 @@ 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
} }