Compare commits
5 Commits
master
...
fake-mev-e
Author | SHA1 | Date | |
---|---|---|---|
|
af6848f39c | ||
|
72e1242544 | ||
|
13be1a4b6a | ||
|
9586b78e10 | ||
|
3e84b01f3b |
@ -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_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_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_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_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
|
||||
|
@ -19,7 +19,7 @@ Sub-repositories maintained within this monorepo are listed below.
|
||||
|
||||
| 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. |
|
||||
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
|
||||
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
import styled from 'styled-components'
|
||||
import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import { ValidatorsConfirmations } from './ValidatorsConfirmations'
|
||||
@ -54,9 +54,7 @@ export const ConfirmationsContainer = ({
|
||||
home: { name: homeName },
|
||||
foreign: { name: foreignName }
|
||||
} = useStateProvider()
|
||||
const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0)
|
||||
const [executionBlockNumber, setExecutionBlockNumber] = useState(0)
|
||||
const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest')
|
||||
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
|
||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
||||
const {
|
||||
confirmations,
|
||||
@ -73,21 +71,11 @@ export const ConfirmationsContainer = ({
|
||||
fromHome,
|
||||
homeStartBlock,
|
||||
foreignStartBlock,
|
||||
requiredSignatures: src.requiredSignatures,
|
||||
validatorList: src.validatorList,
|
||||
targetValidatorList: dst.validatorList,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
blockConfirmations
|
||||
})
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (executionBlockNumber || executionData.status !== VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS) return
|
||||
|
||||
setExecutionBlockNumber(executionData.blockNumber)
|
||||
},
|
||||
[executionData.status, executionBlockNumber, executionData.blockNumber]
|
||||
)
|
||||
|
||||
const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
|
||||
|
||||
const parseDescription = () => {
|
||||
@ -126,22 +114,20 @@ export const ConfirmationsContainer = ({
|
||||
</MultiLine>
|
||||
</StatusDescription>
|
||||
<ValidatorsConfirmations
|
||||
confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
|
||||
requiredSignatures={dst.requiredSignatures}
|
||||
validatorList={dst.validatorList}
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
validatorList={validatorList}
|
||||
waitingBlocksResolved={waitingBlocksResolved}
|
||||
/>
|
||||
{signatureCollected && (
|
||||
<ExecutionConfirmation
|
||||
message={message}
|
||||
messageData={message.data}
|
||||
executionData={executionData}
|
||||
isHome={!fromHome}
|
||||
confirmations={confirmations}
|
||||
signatureCollected={signatureCollected}
|
||||
setExecutionData={setExecutionData}
|
||||
executionEventsFetched={executionEventsFetched}
|
||||
setPendingExecution={setPendingExecution}
|
||||
dstRequiredSignatures={dst.requiredSignatures}
|
||||
dstValidatorList={dst.validatorList}
|
||||
/>
|
||||
)}
|
||||
</StyledConfirmationContainer>
|
||||
|
@ -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 { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { Thead, AgeTd, StatusTd } from './commons/Table'
|
||||
import { ManualExecutionButton } from './ManualExecutionButton'
|
||||
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`
|
||||
margin-top: 30px;
|
||||
`
|
||||
|
||||
export interface ExecutionConfirmationParams {
|
||||
message: MessageObject
|
||||
messageData: string
|
||||
executionData: ExecutionData
|
||||
setExecutionData: Function
|
||||
confirmations: ConfirmationParam[]
|
||||
signatureCollected: boolean | string[]
|
||||
isHome: boolean
|
||||
executionEventsFetched: boolean
|
||||
setPendingExecution: Function
|
||||
dstRequiredSignatures: number
|
||||
dstValidatorList: string[]
|
||||
}
|
||||
|
||||
export const ExecutionConfirmation = ({
|
||||
message,
|
||||
messageData,
|
||||
executionData,
|
||||
setExecutionData,
|
||||
confirmations,
|
||||
signatureCollected,
|
||||
isHome,
|
||||
executionEventsFetched,
|
||||
setPendingExecution,
|
||||
dstRequiredSignatures,
|
||||
dstValidatorList
|
||||
setPendingExecution
|
||||
}: ExecutionConfirmationParams) => {
|
||||
const { foreign } = useStateProvider()
|
||||
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [warning, setWarning] = useState('')
|
||||
const availableManualExecution =
|
||||
!isHome &&
|
||||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
|
||||
@ -76,27 +67,9 @@ export const ExecutionConfirmation = ({
|
||||
[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 = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
@ -114,8 +87,6 @@ export const ExecutionConfirmation = ({
|
||||
|
||||
return (
|
||||
<StyledExecutionConfirmation>
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
|
||||
<table>
|
||||
<Thead>
|
||||
<tr>
|
||||
@ -154,13 +125,10 @@ export const ExecutionConfirmation = ({
|
||||
<td>
|
||||
<ManualExecutionButton
|
||||
safeExecutionAvailable={safeExecutionAvailable}
|
||||
messageData={message.data}
|
||||
messageData={messageData}
|
||||
setExecutionData={setExecutionData}
|
||||
confirmations={confirmations}
|
||||
signatureCollected={signatureCollected as string[]}
|
||||
setPendingExecution={setPendingExecution}
|
||||
setError={setError}
|
||||
requiredSignatures={dstRequiredSignatures}
|
||||
validatorList={dstValidatorList}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
|
@ -8,6 +8,7 @@ import { TransactionReceipt } from 'web3-eth'
|
||||
import { InfoAlert } from './commons/InfoAlert'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
|
||||
import { ErrorAlert } from './commons/ErrorAlert'
|
||||
|
||||
const StyledMainPage = styled.div`
|
||||
text-align: center;
|
||||
@ -51,7 +52,7 @@ export interface FormSubmitParams {
|
||||
|
||||
export const MainPage = () => {
|
||||
const history = useHistory()
|
||||
const { home, foreign } = useStateProvider()
|
||||
const { home, foreign, error, setError } = useStateProvider()
|
||||
const [networkName, setNetworkName] = useState('')
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
const [showInfoAlert, setShowInfoAlert] = useState(false)
|
||||
@ -131,6 +132,7 @@ export const MainPage = () => {
|
||||
</AlertP>
|
||||
</InfoAlert>
|
||||
)}
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
||||
<Route
|
||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
||||
|
@ -14,7 +14,6 @@ import { useStateProvider } from '../state/StateProvider'
|
||||
import { signatureToVRS, packSignatures } from '../utils/signatures'
|
||||
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
const ActionButton = styled.button`
|
||||
color: var(--button-color);
|
||||
@ -31,75 +30,22 @@ interface ManualExecutionButtonParams {
|
||||
safeExecutionAvailable: boolean
|
||||
messageData: string
|
||||
setExecutionData: Function
|
||||
confirmations: ConfirmationParam[]
|
||||
signatureCollected: string[]
|
||||
setPendingExecution: Function
|
||||
setError: Function
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
}
|
||||
|
||||
export const ManualExecutionButton = ({
|
||||
safeExecutionAvailable,
|
||||
messageData,
|
||||
setExecutionData,
|
||||
confirmations,
|
||||
setPendingExecution,
|
||||
setError,
|
||||
requiredSignatures,
|
||||
validatorList
|
||||
signatureCollected,
|
||||
setPendingExecution
|
||||
}: ManualExecutionButtonParams) => {
|
||||
const { foreign } = useStateProvider()
|
||||
const { foreign, setError } = useStateProvider()
|
||||
const { library, activate, account, active } = useWeb3React()
|
||||
const [manualExecution, setManualExecution] = useState(false)
|
||||
const [allowFailures, setAllowFailures] = useState(false)
|
||||
const [ready, setReady] = useState(false)
|
||||
const [title, setTitle] = useState('Loading')
|
||||
const [validSignatures, setValidSignatures] = useState<string[]>([])
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
!foreign.bridgeContract ||
|
||||
!foreign.web3 ||
|
||||
!confirmations ||
|
||||
!confirmations.length ||
|
||||
!requiredSignatures ||
|
||||
!validatorList ||
|
||||
!validatorList.length
|
||||
)
|
||||
return
|
||||
|
||||
const signatures = []
|
||||
for (let i = 0; i < confirmations.length && signatures.length < requiredSignatures; i++) {
|
||||
const sig = confirmations[i].signature
|
||||
if (!sig) {
|
||||
continue
|
||||
}
|
||||
const { v, r, s } = signatureToVRS(sig)
|
||||
const signer = foreign.web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
||||
if (validatorList.includes(signer)) {
|
||||
signatures.push(sig)
|
||||
}
|
||||
}
|
||||
|
||||
if (signatures.length >= requiredSignatures) {
|
||||
setValidSignatures(signatures.slice(0, requiredSignatures))
|
||||
setTitle('Execute')
|
||||
setReady(true)
|
||||
} else {
|
||||
setTitle('Unavailable')
|
||||
}
|
||||
},
|
||||
[
|
||||
foreign.bridgeContract,
|
||||
foreign.web3,
|
||||
validatorList,
|
||||
requiredSignatures,
|
||||
messageData,
|
||||
setValidSignatures,
|
||||
confirmations
|
||||
]
|
||||
)
|
||||
const notReady = !foreign.bridgeContract || !signatureCollected || !signatureCollected.length
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -127,9 +73,9 @@ export const ManualExecutionButton = ({
|
||||
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 bridge = foreign.bridgeContract
|
||||
const executeMethod =
|
||||
@ -194,20 +140,19 @@ export const ManualExecutionButton = ({
|
||||
foreign.bridgeContract,
|
||||
setError,
|
||||
messageData,
|
||||
signatureCollected,
|
||||
setExecutionData,
|
||||
setPendingExecution,
|
||||
safeExecutionAvailable,
|
||||
allowFailures,
|
||||
foreign.web3,
|
||||
validSignatures
|
||||
allowFailures
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="is-center">
|
||||
<ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
{title}
|
||||
<ActionButton disabled={notReady} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
Execute
|
||||
</ActionButton>
|
||||
</div>
|
||||
{safeExecutionAvailable && (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
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 styled from 'styled-components'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
@ -31,9 +31,7 @@ export const ValidatorsConfirmations = ({
|
||||
const getValidatorStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
|
||||
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</SuccessLabel>
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
@ -60,28 +58,26 @@ export const ValidatorsConfirmations = ({
|
||||
</tr>
|
||||
</Thead>
|
||||
<tbody>
|
||||
{confirmations.map((confirmation, i) => {
|
||||
const displayedStatus = confirmation.status
|
||||
const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
|
||||
let elementIfNoTimestamp: any = <SimpleLoading />
|
||||
switch (displayedStatus) {
|
||||
case '':
|
||||
case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
|
||||
if (waitingBlocksResolved) {
|
||||
elementIfNoTimestamp = SEARCHING_TX
|
||||
}
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
||||
elementIfNoTimestamp = ''
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
elementIfNoTimestamp = RECENT_AGE
|
||||
break
|
||||
}
|
||||
{validatorList.map((validator, i) => {
|
||||
const filteredConfirmation = confirmations.filter(c => c.validator === validator)
|
||||
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
|
||||
const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
|
||||
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
|
||||
const elementIfNoTimestamp =
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
|
||||
(displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') &&
|
||||
waitingBlocksResolved ? (
|
||||
SEARCHING_TX
|
||||
) : (
|
||||
<SimpleLoading />
|
||||
)
|
||||
) : (
|
||||
''
|
||||
)
|
||||
return (
|
||||
<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>
|
||||
<AgeTd className="text-center">
|
||||
{confirmation && confirmation.timestamp > 0 ? (
|
||||
@ -98,7 +94,7 @@ export const ValidatorsConfirmations = ({
|
||||
</tbody>
|
||||
</table>
|
||||
<RequiredConfirmations>
|
||||
At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
|
||||
{requiredSignatures} of {validatorList.length} confirmations required
|
||||
</RequiredConfirmations>
|
||||
</div>
|
||||
)
|
||||
|
@ -33,7 +33,7 @@ export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: str
|
||||
}
|
||||
return (
|
||||
<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)" />
|
||||
<TextContainer className="col-10">
|
||||
{text}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -54,19 +54,14 @@ export const CONFIRMATIONS_STATUS = {
|
||||
}
|
||||
|
||||
export const VALIDATOR_CONFIRMATION_STATUS = {
|
||||
SUCCESS: 'Confirmed',
|
||||
MANUAL: 'Manual',
|
||||
EXECUTION_SUCCESS: 'Executed',
|
||||
SUCCESS: 'Success',
|
||||
FAILED: 'Failed',
|
||||
FAILED_VALID: 'Failed valid',
|
||||
PENDING: 'Pending',
|
||||
WAITING: 'Waiting',
|
||||
NOT_REQUIRED: 'Not required',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const RECENT_AGE = 'Recent'
|
||||
|
||||
export const SEARCHING_TX = 'Searching Transaction...'
|
||||
|
||||
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
|
||||
|
@ -29,16 +29,17 @@ export interface useMessageConfirmationsParams {
|
||||
foreignStartBlock: Maybe<number>
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
targetValidatorList: string[]
|
||||
blockConfirmations: number
|
||||
}
|
||||
|
||||
export interface ConfirmationParam {
|
||||
export interface BasicConfirmationParam {
|
||||
validator: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface ConfirmationParam extends BasicConfirmationParam {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ExecutionData {
|
||||
@ -47,7 +48,6 @@ export interface ExecutionData {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
executionResult: boolean
|
||||
blockNumber: number
|
||||
}
|
||||
|
||||
export const useMessageConfirmations = ({
|
||||
@ -58,7 +58,6 @@ export const useMessageConfirmations = ({
|
||||
foreignStartBlock,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
targetValidatorList,
|
||||
blockConfirmations
|
||||
}: useMessageConfirmationsParams) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
@ -66,7 +65,7 @@ export const useMessageConfirmations = ({
|
||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
||||
const [signatureCollected, setSignatureCollected] = useState(false)
|
||||
const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(false)
|
||||
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
|
||||
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
|
||||
const [executionData, setExecutionData] = useState<ExecutionData>({
|
||||
@ -74,8 +73,7 @@ export const useMessageConfirmations = ({
|
||||
validator: '',
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = 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 execution tx on the foreign network is waiting for block confirmations
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
const hasCollectedSignatures = !!signatureCollected // true or string[]
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
|
||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return
|
||||
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
@ -180,7 +179,7 @@ export const useMessageConfirmations = ({
|
||||
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
|
||||
@ -253,35 +252,6 @@ export const useMessageConfirmations = ({
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
|
||||
if (fromHome) {
|
||||
if (!targetValidatorList || !targetValidatorList.length) return
|
||||
const msgHash = home.web3.utils.sha3(message.data)!
|
||||
const allValidators = [...validatorList, ...targetValidatorList].filter((v, i, s) => s.indexOf(v) === i)
|
||||
const manualConfirmations = []
|
||||
for (let i = 0; i < allValidators.length; i++) {
|
||||
try {
|
||||
const overrideSignatures: {
|
||||
[key: string]: string
|
||||
} = require(`../snapshots/signatures_${allValidators[i]}.json`)
|
||||
if (overrideSignatures[msgHash]) {
|
||||
console.log(`Adding manual signature from ${allValidators[i]}`)
|
||||
manualConfirmations.push({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.MANUAL,
|
||||
validator: allValidators[i],
|
||||
timestamp: 0,
|
||||
txHash: '',
|
||||
signature: overrideSignatures[msgHash]
|
||||
})
|
||||
} else {
|
||||
console.log(`No manual signature from ${allValidators[i]} was found`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Signatures overrides are not present for ${allValidators[i]}`)
|
||||
}
|
||||
}
|
||||
setConfirmations(manualConfirmations)
|
||||
}
|
||||
|
||||
getConfirmationsForTx(
|
||||
message.data,
|
||||
home.web3,
|
||||
@ -314,8 +284,7 @@ export const useMessageConfirmations = ({
|
||||
home.bridgeContract,
|
||||
requiredSignatures,
|
||||
waitingBlocksResolved,
|
||||
homeStartBlock,
|
||||
targetValidatorList
|
||||
homeStartBlock
|
||||
]
|
||||
)
|
||||
|
||||
@ -374,10 +343,7 @@ export const useMessageConfirmations = ({
|
||||
// Sets the message status based in the collected information
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
|
||||
existsConfirmation(confirmations)
|
||||
) {
|
||||
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) {
|
||||
const newStatus = executionData.executionResult
|
||||
? CONFIRMATIONS_STATUS.SUCCESS
|
||||
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
|
||||
|
@ -4,13 +4,19 @@ import Web3 from 'web3'
|
||||
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
|
||||
import { BRIDGE_VALIDATORS_ABI } from '../abis'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
|
||||
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 [requiredSignatures, setRequiredSignatures] = useState(0)
|
||||
const [validatorList, setValidatorList] = useState<string[]>([])
|
||||
const [validatorList, setValidatorList] = useState([])
|
||||
|
||||
const { home, foreign } = useStateProvider()
|
||||
|
||||
@ -23,34 +29,34 @@ export const useValidatorContract = (isHome: boolean, blockNumber: number | 'lat
|
||||
|
||||
const callRequiredSignatures = async (
|
||||
contract: Maybe<Contract>,
|
||||
blockNumber: number | 'latest',
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const callValidatorList = async (
|
||||
contract: Maybe<Contract>,
|
||||
blockNumber: number | 'latest',
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const web3 = isHome ? home.web3 : foreign.web3
|
||||
const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
||||
const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = isHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
const web3 = fromHome ? home.web3 : foreign.web3
|
||||
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -62,11 +68,11 @@ export const useValidatorContract = (isHome: boolean, blockNumber: number | 'lat
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!web3 || !blockNumber) return
|
||||
callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
|
||||
callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
|
||||
if (!web3 || !receipt) return
|
||||
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api)
|
||||
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api)
|
||||
},
|
||||
[validatorContract, blockNumber, web3, snapshotProvider, api]
|
||||
[validatorContract, receipt, web3, snapshotProvider, api]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createContext, ReactNode } from 'react'
|
||||
import React, { createContext, ReactNode, useState } from 'react'
|
||||
import { useNetwork } from '../hooks/useNetwork'
|
||||
import {
|
||||
HOME_RPC_URL,
|
||||
@ -25,6 +25,8 @@ export interface StateContext {
|
||||
home: BaseNetworkParams
|
||||
foreign: BaseNetworkParams
|
||||
loading: boolean
|
||||
error: string
|
||||
setError: Function
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
@ -42,7 +44,9 @@ const initialState = {
|
||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
},
|
||||
loading: true
|
||||
loading: true,
|
||||
error: '',
|
||||
setError: () => {}
|
||||
}
|
||||
|
||||
const StateContext = createContext<StateContext>(initialState)
|
||||
@ -54,6 +58,7 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
homeWeb3: homeNetwork.web3,
|
||||
foreignWeb3: foreignNetwork.web3
|
||||
})
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const value = {
|
||||
home: {
|
||||
@ -68,7 +73,9 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
bridgeContract: foreignBridge,
|
||||
...foreignNetwork
|
||||
},
|
||||
loading: homeNetwork.loading || foreignNetwork.loading
|
||||
loading: homeNetwork.loading || foreignNetwork.loading,
|
||||
error,
|
||||
setError
|
||||
}
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>
|
||||
|
@ -28,7 +28,5 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
|
||||
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
|
||||
--failed-color: ${props => props.theme.failed.textColor};
|
||||
--failed-bg-color: ${props => props.theme.failed.backgroundColor};
|
||||
--warning-color: ${props => props.theme.warning.textColor};
|
||||
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
|
||||
}
|
||||
`
|
||||
|
@ -17,10 +17,6 @@ const theme = {
|
||||
failed: {
|
||||
textColor: '#de4437',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
},
|
||||
warning: {
|
||||
textColor: '#ffa758',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
}
|
||||
}
|
||||
export default theme
|
||||
|
@ -14,40 +14,37 @@ const messageData = '0x123456'
|
||||
const OTHER_HASH = 'aabbccdd'
|
||||
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
|
||||
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
|
||||
describe('getFailedTransactions', () => {
|
||||
test('should only return failed transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '1', to }
|
||||
]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(3)
|
||||
})
|
||||
})
|
||||
describe('getSuccessTransactions', () => {
|
||||
test('should only return success transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '0', to },
|
||||
{ isError: '1', to },
|
||||
{ isError: '1', to }
|
||||
]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(2)
|
||||
})
|
||||
})
|
||||
describe('filterValidatorSignatureTransaction', () => {
|
||||
|
@ -5,7 +5,7 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { APIPendingTransaction, APITransaction } from '../explorer'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
|
||||
import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
|
||||
jest.mock('../validatorConfirmationHelpers')
|
||||
|
||||
@ -18,9 +18,6 @@ const messageData = '0x111111111'
|
||||
const web3 = {
|
||||
utils: {
|
||||
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
|
||||
},
|
||||
eth: {
|
||||
accounts: new Web3().eth.accounts
|
||||
}
|
||||
} as Web3
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
@ -28,7 +25,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
const validatorList = [validator1, validator2, validator3]
|
||||
const signature =
|
||||
'0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
|
||||
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
|
||||
const bridgeContract = {
|
||||
methods: {
|
||||
signature: () => ({
|
||||
@ -64,19 +61,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -113,8 +110,9 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).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[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -137,7 +135,7 @@ describe('getConfirmationsForTx', () => {
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -146,19 +144,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -210,19 +208,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -259,8 +257,9 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(getValidatorConfirmation).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[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -282,16 +281,16 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -305,22 +304,22 @@ describe('getConfirmationsForTx', () => {
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -357,8 +356,9 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).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[1][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
@ -382,26 +382,26 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -414,22 +414,22 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
@ -492,22 +492,22 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
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: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ 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 }
|
||||
])
|
||||
)
|
||||
@ -521,13 +521,13 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator !== validator1
|
||||
@ -536,7 +536,7 @@ describe('getConfirmationsForTx', () => {
|
||||
txHash: validatorData.validator !== validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -596,9 +596,9 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
@ -610,13 +610,9 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
})
|
||||
test('should remove pending state after transaction mined', async () => {
|
||||
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
|
||||
const validatorList = [validator1, validator2, validator3, validator4]
|
||||
|
||||
// Validator1 success (ts=100)
|
||||
// Validator2 failed (ts=200)
|
||||
// Validator3 Pending (ts=300)
|
||||
// Validator4 Excess confirmation (Failed) (ts=400)
|
||||
// Validator1 success
|
||||
// Validator2 failed
|
||||
// Validator3 Pending
|
||||
|
||||
getValidatorConfirmation
|
||||
.mockImplementationOnce(() => async (validator: string) => ({
|
||||
@ -627,57 +623,41 @@ describe('getConfirmationsForTx', () => {
|
||||
.mockImplementation(() => async (validator: string) => ({
|
||||
validator,
|
||||
status:
|
||||
validator === validator1 || validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x100' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : 0
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash:
|
||||
validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : ''
|
||||
txHash: validatorData.validator !== validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
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 : ''
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 300 : 0
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
}))
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -718,7 +698,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
expect(getValidatorFailedTransaction).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(setPendingConfirmations).toBeCalledTimes(1)
|
||||
@ -732,32 +712,28 @@ describe('getConfirmationsForTx', () => {
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
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: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, 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 }
|
||||
])
|
||||
)
|
||||
|
||||
@ -785,13 +761,14 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setResult).toBeCalledTimes(7)
|
||||
expect(getValidatorConfirmation).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[1][0]).toEqual(true)
|
||||
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
|
||||
|
||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
||||
@ -804,26 +781,23 @@ describe('getConfirmationsForTx', () => {
|
||||
const res7 = setResult.mock.calls[6][0](res6)
|
||||
expect(res5).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
])
|
||||
)
|
||||
expect(res6).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
])
|
||||
)
|
||||
expect(res7).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
})
|
||||
|
@ -84,11 +84,10 @@ describe('getFinalizationEvent', () => {
|
||||
expect(setResult).toBeCalledTimes(1)
|
||||
expect(setResult.mock.calls[0][0]).toEqual({
|
||||
validator: validator1,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash,
|
||||
timestamp,
|
||||
executionResult: true,
|
||||
blockNumber: 5523145
|
||||
executionResult: true
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -238,8 +237,7 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -296,8 +294,7 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false,
|
||||
blockNumber: expect.any(Number)
|
||||
executionResult: false
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(1)
|
||||
|
@ -22,16 +22,6 @@ export const getRequiredBlockConfirmations = async (
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
let blockConfirmations
|
||||
|
||||
try {
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
} catch {}
|
||||
|
||||
if (blockConfirmations) {
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
@ -45,10 +35,16 @@ export const getRequiredBlockConfirmations = async (
|
||||
|
||||
const events = [...eventsFromSnapshot, ...contractEvents]
|
||||
|
||||
let blockConfirmations
|
||||
if (events.length > 0) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@ -56,25 +52,11 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
|
||||
|
||||
export const getRequiredSignatures = async (
|
||||
contract: Contract,
|
||||
blockNumber: number | 'latest',
|
||||
blockNumber: number,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
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 snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
@ -90,25 +72,17 @@ export const getRequiredSignatures = async (
|
||||
|
||||
// Use the value form last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
;({ requiredSignatures } = event.returnValues)
|
||||
const { requiredSignatures } = event.returnValues
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
export const getValidatorList = async (
|
||||
contract: Contract,
|
||||
blockNumber: number | 'latest',
|
||||
blockNumber: number,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
try {
|
||||
const currentList = await contract.methods.validatorList().call()
|
||||
|
||||
if (currentList) {
|
||||
return currentList
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
|
||||
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
@ -12,7 +12,6 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
|
||||
export interface APITransaction {
|
||||
from: string
|
||||
timeStamp: string
|
||||
isError: string
|
||||
input: string
|
||||
@ -55,7 +54,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
|
||||
url.searchParams.append('module', 'account')
|
||||
url.searchParams.append('action', 'txlist')
|
||||
url.searchParams.append('address', account)
|
||||
url.searchParams.append('filterby', 'to')
|
||||
url.searchParams.append('filterby', 'from')
|
||||
url.searchParams.append('startblock', startBlock.toString())
|
||||
url.searchParams.append('endblock', endBlock.toString())
|
||||
|
||||
@ -65,7 +64,7 @@ export const fetchAccountTransactions = async ({ account, startBlock, endBlock,
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result || []
|
||||
return result.result
|
||||
}
|
||||
|
||||
export const fetchPendingTransactions = async ({
|
||||
@ -181,12 +180,10 @@ export const getLogs = async (
|
||||
if (topics[i] !== null) {
|
||||
url.searchParams.append(`topic${i}`, topics[i] as string)
|
||||
for (let j = 0; j < i; j++) {
|
||||
if (topics[j] !== null) {
|
||||
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logs = await fetch(url.toString()).then(res => res.json())
|
||||
|
||||
@ -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 (
|
||||
account: string,
|
||||
@ -207,9 +204,9 @@ export const getFailedTransactions = async (
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
): 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 (
|
||||
@ -220,9 +217,9 @@ export const getSuccessTransactions = async (
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
): 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 = (
|
||||
|
@ -2,37 +2,26 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
import {
|
||||
getValidatorConfirmation,
|
||||
getValidatorFailedTransaction,
|
||||
getValidatorPendingTransaction,
|
||||
getSuccessExecutionTransaction
|
||||
} from './validatorConfirmationHelpers'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { signatureToVRS } from './signatures'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => {
|
||||
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
|
||||
const confirmations = [...oldConfirmations]
|
||||
newConfirmations.forEach(validatorData => {
|
||||
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
if (index === -1) {
|
||||
confirmations.push(validatorData)
|
||||
return
|
||||
}
|
||||
const currentStatus = confirmations[index].status
|
||||
const newStatus = validatorData.status
|
||||
if (
|
||||
validatorData.txHash ||
|
||||
!!validatorData.signature ||
|
||||
(validatorData as ConfirmationParam).txHash ||
|
||||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
|
||||
) {
|
||||
confirmations[index] = {
|
||||
status: validatorData.status,
|
||||
validator: validatorData.validator,
|
||||
timestamp: confirmations[index].timestamp || validatorData.timestamp,
|
||||
txHash: confirmations[index].txHash || validatorData.txHash,
|
||||
signature: confirmations[index].signature || validatorData.signature
|
||||
}
|
||||
confirmations[index] = validatorData
|
||||
}
|
||||
})
|
||||
return confirmations
|
||||
@ -56,17 +45,19 @@ export const getConfirmationsForTx = async (
|
||||
setPendingConfirmations: Function,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => {
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
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) {
|
||||
return
|
||||
}
|
||||
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
|
||||
setResult((currentConfirmations: ConfirmationParam[]) => {
|
||||
setResult((currentConfirmations: BasicConfirmationParam[]) => {
|
||||
if (currentConfirmations && currentConfirmations.length) {
|
||||
return mergeConfirmations(currentConfirmations, confirmations)
|
||||
}
|
||||
@ -76,37 +67,11 @@ export const getConfirmationsForTx = async (
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const hasEnoughSignatures = successConfirmations.length >= requiredSignatures
|
||||
const hasEnoughSignatures = successConfirmations.length === requiredSignatures
|
||||
|
||||
updateConfirmations(validatorConfirmations)
|
||||
setSignatureCollected(hasEnoughSignatures)
|
||||
|
||||
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 (!hasEnoughSignatures) {
|
||||
// Check if confirmation is pending
|
||||
@ -119,6 +84,16 @@ export const getConfirmationsForTx = async (
|
||||
)
|
||||
updateConfirmations(validatorPendingConfirmations)
|
||||
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(
|
||||
@ -128,27 +103,13 @@ export const getConfirmationsForTx = async (
|
||||
// Check if confirmation failed
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
|
||||
)
|
||||
)
|
||||
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
)
|
||||
if (hasEnoughSignatures && !fromHome) {
|
||||
const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0))
|
||||
validatorFailedConfirmations = validatorFailedConfirmations.map(
|
||||
c =>
|
||||
c.timestamp < lastTS
|
||||
? c
|
||||
: {
|
||||
...c,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
}
|
||||
)
|
||||
}
|
||||
setFailedConfirmations(
|
||||
!hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED)
|
||||
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
)
|
||||
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
|
||||
updateConfirmations(validatorFailedConfirmations)
|
||||
|
||||
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
|
||||
const notRequiredConfirmations = missingConfirmations.map(c => ({
|
||||
validator: c.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
|
||||
}))
|
||||
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
|
||||
// or some success transactions were not fetched successfully
|
||||
if (
|
||||
|
@ -59,12 +59,11 @@ export const getSuccessExecutionData = async (
|
||||
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator: validatorAddress,
|
||||
txHash: event.transactionHash,
|
||||
timestamp: blockTimestamp,
|
||||
executionResult: event.returnValues.status,
|
||||
blockNumber: event.blockNumber
|
||||
executionResult: event.returnValues.status
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -116,8 +115,7 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: pendingTx.hash,
|
||||
timestamp: nowTimestamp,
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} else {
|
||||
@ -146,8 +144,7 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false,
|
||||
blockNumber: parseInt(failedTx.blockNumber)
|
||||
executionResult: false
|
||||
})
|
||||
setFailedExecution(true)
|
||||
}
|
||||
|
@ -1,45 +1,38 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
|
||||
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
|
||||
export const getValidatorConfirmation = (
|
||||
web3: Web3,
|
||||
hashMsg: string,
|
||||
bridgeContract: Contract,
|
||||
fromHome: boolean
|
||||
) => async (validator: string): Promise<ConfirmationParam> => {
|
||||
confirmationContractMethod: Function
|
||||
) => async (validator: string): Promise<BasicConfirmationParam> => {
|
||||
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
|
||||
|
||||
const fromCache = validatorsCache.getData(hashSenderMsg)
|
||||
if (fromCache) {
|
||||
return fromCache
|
||||
const signatureFromCache = validatorsCache.get(hashSenderMsg)
|
||||
if (signatureFromCache) {
|
||||
return {
|
||||
validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg)
|
||||
const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
// If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (confirmed) {
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
}
|
||||
validatorsCache.setData(hashSenderMsg, confirmation)
|
||||
return confirmation
|
||||
validatorsCache.set(hashSenderMsg, confirmed)
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +43,7 @@ export const getSuccessExecutionTransaction = (
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const { validator } = validatorData
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
||||
@ -94,12 +87,11 @@ export const getSuccessExecutionTransaction = (
|
||||
}
|
||||
|
||||
export const getValidatorFailedTransaction = (
|
||||
web3: Web3,
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
@ -114,33 +106,30 @@ export const getValidatorFailedTransaction = (
|
||||
startBlock,
|
||||
endBlock: homeBlockNumberProvider.get() || 0
|
||||
})
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validatorData.validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp: parseInt(failedTx.timeStamp)
|
||||
}
|
||||
txHashTimestamp = parseInt(failedTx.timeStamp)
|
||||
txHash = failedTx.hash
|
||||
|
||||
if (failedTx.input && failedTx.input.length > 10) {
|
||||
try {
|
||||
const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`)
|
||||
confirmation.signature = res[0]
|
||||
confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
console.log(`Adding manual signature from failed message from ${validatorData.validator}`)
|
||||
} catch {}
|
||||
}
|
||||
validatorsCache.setData(validatorCacheKey, confirmation)
|
||||
return confirmation
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: validatorData.validator,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +137,7 @@ export const getValidatorPendingTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const failedTransactions = await getPendingTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
|
@ -10,37 +10,6 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
|
||||
export interface MessageObject {
|
||||
id: 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))
|
||||
@ -57,33 +26,15 @@ export const filterEventsByAbi = (
|
||||
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
|
||||
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 => {
|
||||
const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
|
||||
let sender, executor, obToken, obReceiver
|
||||
if (encodedData.length >= 160) {
|
||||
sender = `0x${encodedData.slice(66, 106)}`
|
||||
executor = `0x${encodedData.slice(106, 146)}`
|
||||
const dataOffset =
|
||||
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
|
||||
if (encodedData.length >= dataOffset + 64) {
|
||||
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
|
||||
let decodedLogs: { [p: string]: string } = {
|
||||
messageId: '',
|
||||
encodedData: ''
|
||||
}
|
||||
if (encodedData.length >= dataOffset + 128) {
|
||||
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: messageId || '',
|
||||
data: encodedData || '',
|
||||
sender,
|
||||
executor,
|
||||
obToken,
|
||||
obReceiver
|
||||
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) {
|
||||
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]])
|
||||
}
|
||||
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
# 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
|
||||
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
|
||||
- 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.
|
||||
|
||||
|
@ -7,15 +7,7 @@ const {
|
||||
HOME_AMB_ABI,
|
||||
FOREIGN_AMB_ABI
|
||||
} = require('../../commons')
|
||||
const {
|
||||
web3Home,
|
||||
web3Foreign,
|
||||
web3HomeRedundant,
|
||||
web3HomeFallback,
|
||||
web3ForeignRedundant,
|
||||
web3ForeignFallback,
|
||||
web3ForeignArchive
|
||||
} = require('../src/services/web3')
|
||||
const { web3Home, web3Foreign } = require('../src/services/web3')
|
||||
const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils')
|
||||
const { EXIT_CODES } = require('../src/utils/constants')
|
||||
|
||||
@ -35,11 +27,9 @@ const {
|
||||
ORACLE_HOME_EVENTS_REPROCESSING,
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE,
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY,
|
||||
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE,
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY,
|
||||
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY
|
||||
} = process.env
|
||||
|
||||
let homeAbi
|
||||
@ -73,12 +63,9 @@ const homeConfig = {
|
||||
bridgeAddress: COMMON_HOME_BRIDGE_ADDRESS,
|
||||
bridgeABI: homeAbi,
|
||||
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,
|
||||
blockPollingLimit: parseInt(ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT, 10),
|
||||
web3: web3Home,
|
||||
web3Redundant: web3HomeRedundant,
|
||||
web3Fallback: web3HomeFallback,
|
||||
bridgeContract: homeContract,
|
||||
eventContract: homeContract,
|
||||
reprocessingOptions: {
|
||||
@ -94,13 +81,9 @@ const foreignConfig = {
|
||||
bridgeAddress: COMMON_FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeABI: foreignAbi,
|
||||
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,
|
||||
blockPollingLimit: parseInt(ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT, 10),
|
||||
web3: web3Foreign,
|
||||
web3Redundant: web3ForeignRedundant,
|
||||
web3Fallback: web3ForeignFallback,
|
||||
web3Archive: web3ForeignArchive || web3Foreign,
|
||||
bridgeContract: foreignContract,
|
||||
eventContract: foreignContract,
|
||||
reprocessingOptions: {
|
||||
|
@ -1,14 +1,17 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
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
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.foreign,
|
||||
queue: 'foreign-prioritized',
|
||||
id: 'foreign',
|
||||
name: 'sender-foreign',
|
||||
web3: web3Foreign,
|
||||
web3Redundant: web3ForeignRedundant,
|
||||
web3Fallback: web3ForeignFallback,
|
||||
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
const baseConfig = require('./base.config')
|
||||
|
||||
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
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
main: baseConfig.home,
|
||||
queue: 'home-prioritized',
|
||||
id: 'home',
|
||||
name: 'sender-home',
|
||||
web3: web3Home,
|
||||
web3Redundant: web3HomeRedundant,
|
||||
web3Fallback: web3HomeFallback,
|
||||
resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
const baseConfig = require('./base.config')
|
||||
const { web3ForeignArchive } = require('../src/services/web3')
|
||||
|
||||
const id = `${baseConfig.id}-information-request`
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
web3ForeignArchive: web3ForeignArchive || baseConfig.foreign.web3,
|
||||
main: baseConfig.home,
|
||||
event: 'UserRequestForInformation',
|
||||
sender: 'home',
|
||||
|
@ -57,5 +57,8 @@ services:
|
||||
ORACLE_MEV_FOREIGN_FLASHBOTS_AUTH_SIGNING_KEY: 82db7175932f4e6c8e45283b78b54fd5f195149378ec90d95b8fd0ec8bdadf1d
|
||||
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)
|
||||
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
|
||||
entrypoint: yarn mev:sender:foreign
|
||||
|
@ -19,7 +19,6 @@
|
||||
"confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher",
|
||||
"manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager",
|
||||
"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: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'",
|
||||
|
@ -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()
|
@ -35,13 +35,11 @@ Object.keys(asyncCalls).forEach(method => {
|
||||
})
|
||||
|
||||
function processInformationRequestsBuilder(config) {
|
||||
const { home, foreign } = config
|
||||
const { home, foreign, web3ForeignArchive } = config
|
||||
|
||||
let validatorContract = null
|
||||
let blockFinder = null
|
||||
|
||||
foreign.web3Archive.currentProvider.startSyncStateChecker(foreign.syncCheckInterval)
|
||||
|
||||
return async function processInformationRequests(informationRequests) {
|
||||
const txToSend = []
|
||||
|
||||
@ -51,15 +49,13 @@ function processInformationRequestsBuilder(config) {
|
||||
|
||||
if (blockFinder === null) {
|
||||
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 =
|
||||
(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 lastForeignBlock = await getBlock(foreign.web3Archive, foreignBlockNumber)
|
||||
const lastForeignBlock = await getBlock(foreign.web3, foreignBlockNumber)
|
||||
|
||||
if (homeBlock.timestamp > lastForeignBlock.timestamp) {
|
||||
rootLogger.debug(
|
||||
@ -89,7 +85,7 @@ function processInformationRequestsBuilder(config) {
|
||||
logger.info({ requestSelector, method: asyncCallMethod, data }, 'Processing async request')
|
||||
|
||||
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) {
|
||||
throw e
|
||||
}
|
||||
|
@ -15,9 +15,13 @@ if (process.argv.length < 3) {
|
||||
}
|
||||
|
||||
const config = require(path.join('../config/', process.argv[2]))
|
||||
const GasPrice = require('./services/gasPrice')
|
||||
|
||||
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 flashbotsProvider
|
||||
|
||||
@ -28,7 +32,9 @@ async function initialize() {
|
||||
web3.currentProvider.urls.forEach(checkHttps(config.id))
|
||||
|
||||
chainId = await getChainId(web3)
|
||||
if (!FAKE_MEV_RELAY_TX_SENDING) {
|
||||
flashbotsProvider = await mevForeign.getFlashbotsProvider(chainId)
|
||||
}
|
||||
return runMain()
|
||||
} catch (e) {
|
||||
logger.error(e.message)
|
||||
@ -65,7 +71,13 @@ async function main() {
|
||||
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)
|
||||
|
||||
if (!bestJob) {
|
||||
@ -115,7 +127,7 @@ async function main() {
|
||||
{ nonce, fromBlock: pendingBlockNumber, toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1 },
|
||||
'Sending MEV bundles'
|
||||
)
|
||||
const txHash = await sendTx({
|
||||
const opts = {
|
||||
data: bestJob.data,
|
||||
nonce,
|
||||
value: bestJob.value,
|
||||
@ -134,7 +146,14 @@ async function main() {
|
||||
toBlock: pendingBlockNumber + mevForeign.bundlesPerIteration - 1,
|
||||
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}`)
|
||||
|
||||
|
@ -32,8 +32,8 @@ if (process.argv.length < 3) {
|
||||
|
||||
const config = require(path.join('../config/', process.argv[2]))
|
||||
|
||||
const { web3, web3Fallback, syncCheckInterval } = config.main
|
||||
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.main.web3Redundant : web3
|
||||
const { web3, web3Fallback } = config
|
||||
const web3Redundant = ORACLE_TX_REDUNDANCY === 'true' ? config.web3Redundant : web3
|
||||
|
||||
const nonceKey = `${config.id}:nonce`
|
||||
let chainId = 0
|
||||
@ -43,7 +43,6 @@ async function initialize() {
|
||||
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
|
||||
|
||||
web3.currentProvider.urls.forEach(checkHttps(config.id))
|
||||
web3.currentProvider.startSyncStateChecker(syncCheckInterval)
|
||||
|
||||
GasPrice.start(config.id, web3)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
const fetch = require('node-fetch')
|
||||
const promiseRetry = require('promise-retry')
|
||||
const { utils } = require('web3')
|
||||
const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants')
|
||||
|
||||
const { onInjected } = require('./injectedLogger')
|
||||
@ -40,54 +39,19 @@ function HttpListProvider(urls, options = {}) {
|
||||
this.options = { ...defaultOptions, ...options }
|
||||
this.currentIndex = 0
|
||||
this.lastTimeUsedPrimary = 0
|
||||
this.latestBlock = 0
|
||||
this.syncStateCheckerIntervalId = 0
|
||||
|
||||
onInjected(logger => {
|
||||
this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` })
|
||||
})
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.startSyncStateChecker = function(syncCheckInterval) {
|
||||
if (this.urls.length > 1 && syncCheckInterval > 0 && this.syncStateCheckerIntervalId === 0) {
|
||||
this.syncStateCheckerIntervalId = setInterval(this.checkLatestBlock.bind(this), syncCheckInterval)
|
||||
}
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.checkLatestBlock = function() {
|
||||
const payload = { jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }
|
||||
this.send(payload, (error, result) => {
|
||||
if (error) {
|
||||
this.logger.warn({ oldBlock: this.latestBlock }, 'Failed to request latest block from all RPC urls')
|
||||
} else if (result.error) {
|
||||
this.logger.warn(
|
||||
{ oldBlock: this.latestBlock, error: result.error.message },
|
||||
'Failed to make eth_blockNumber request due to unknown error, switching to fallback RPC'
|
||||
)
|
||||
this.switchToFallbackRPC()
|
||||
} else {
|
||||
const blockNumber = utils.hexToNumber(result.result)
|
||||
if (blockNumber > this.latestBlock) {
|
||||
this.logger.debug({ oldBlock: this.latestBlock, newBlock: blockNumber }, 'Updating latest block number')
|
||||
this.latestBlock = blockNumber
|
||||
} else {
|
||||
this.logger.warn(
|
||||
{ oldBlock: this.latestBlock, newBlock: blockNumber },
|
||||
'Latest block on the node was not updated since last request, switching to fallback RPC'
|
||||
)
|
||||
this.switchToFallbackRPC()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
HttpListProvider.prototype.switchToFallbackRPC = function(index) {
|
||||
const prevIndex = this.currentIndex
|
||||
const newIndex = index || (prevIndex + 1) % this.urls.length
|
||||
if (this.urls.length < 2 || prevIndex === newIndex) {
|
||||
HttpListProvider.prototype.switchToFallbackRPC = function() {
|
||||
if (this.urls.length < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
const prevIndex = this.currentIndex
|
||||
const newIndex = (prevIndex + 1) % this.urls.length
|
||||
this.logger.info(
|
||||
{ index: newIndex, oldURL: this.urls[prevIndex], newURL: this.urls[newIndex] },
|
||||
'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 (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)
|
||||
} catch (e) {
|
||||
|
@ -27,6 +27,7 @@ module.exports = {
|
||||
MIN_GAS_PRICE_BUMP_FACTOR: 0.1,
|
||||
DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000,
|
||||
FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000,
|
||||
BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT: 10,
|
||||
SENDER_QUEUE_MAX_PRIORITY: 10,
|
||||
SENDER_QUEUE_SEND_PRIORITY: 5,
|
||||
SENDER_QUEUE_CHECK_STATUS_PRIORITY: 1,
|
||||
|
@ -169,8 +169,7 @@ function isNonceError(e) {
|
||||
message.includes('transaction nonce is too low') ||
|
||||
message.includes('nonce too low') ||
|
||||
message.includes('transaction with same nonce in the queue') ||
|
||||
message.includes('oldnonce') ||
|
||||
message.includes(`the tx doesn't have the correct nonce`)
|
||||
message.includes('oldnonce')
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,11 @@ const logger = require('./services/logger')
|
||||
const { getShutdownFlag } = require('./services/shutdownState')
|
||||
const { getBlockNumber, getRequiredBlockConfirmations, getEvents } = require('./tx/web3')
|
||||
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) {
|
||||
logger.error('Please check the number of arguments, config file was not provided')
|
||||
@ -34,21 +38,21 @@ const {
|
||||
pollingInterval,
|
||||
chain,
|
||||
reprocessingOptions,
|
||||
blockPollingLimit,
|
||||
syncCheckInterval
|
||||
blockPollingLimit
|
||||
} = config.main
|
||||
const lastBlockRedisKey = `${config.id}:lastProcessedBlock`
|
||||
const lastReprocessedBlockRedisKey = `${config.id}:lastReprocessedBlock`
|
||||
const seenEventsRedisKey = `${config.id}:seenEvents`
|
||||
let lastProcessedBlock = Math.max(startBlock - 1, 0)
|
||||
let lastReprocessedBlock
|
||||
let lastSeenBlockNumber = 0
|
||||
let sameBlockNumberCounter = 0
|
||||
|
||||
async function initialize() {
|
||||
try {
|
||||
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
|
||||
|
||||
web3.currentProvider.urls.forEach(checkHttps(chain))
|
||||
web3.currentProvider.startSyncStateChecker(syncCheckInterval)
|
||||
|
||||
await getLastProcessedBlock()
|
||||
await getLastReprocessedBlock()
|
||||
@ -221,6 +225,28 @@ async function getLastBlockToProcess(web3, bridgeContract) {
|
||||
getBlockNumber(web3),
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user