Compare commits

..

2 Commits

Author SHA1 Message Date
Kirill Fedoseev
5490b8b029 Drop duplicated UserRequestForSignature events in monitor 2021-05-19 14:48:42 +03:00
Kirill Fedoseev
3f93fef016 Add oracle e2e test for requestMessageReconfirm 2021-05-17 19:46:29 +03:00
123 changed files with 2451 additions and 4427 deletions

View File

@ -77,7 +77,7 @@ jobs:
- name: Rebuild and push updated images
run: |
function check_if_image_exists() {
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
updated=()
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
@ -104,7 +104,7 @@ jobs:
- name: Rebuild and push molecule runner e2e image
run: |
function check_if_image_exists() {
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then
echo "Image already exists"
@ -205,7 +205,7 @@ jobs:
- name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
- name: Pull e2e oracle image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb

View File

@ -8,11 +8,11 @@ COMMON_HOME_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in t
COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x"
COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x"
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL
COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow`
COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer
COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. | URL
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow`
COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer
COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
@ -52,15 +52,6 @@ ORACLE_SHUTDOWN_CONTRACT_ADDRESS | Optional contract address in the side chain a
ORACLE_SHUTDOWN_CONTRACT_METHOD | Method signature to be used in the side chain to identify the current shutdown status. Method should return boolean. Default value is `isShutdown()`. | `function signature`
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Foreign chain. Infinite, if not provided. | `integer`
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Home chain. Infinite, if not provided. | `integer`
ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trigger RPC fallback to the next URL from the list (or a retry in case of a single RPC URL). Default is `-32603,-32002,-32005`. Should be a comma-separated list of negative integers. | `string`
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

View File

@ -19,7 +19,6 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
COPY commons/package.json ./commons/
COPY oracle-e2e/package.json ./oracle-e2e/
COPY monitor-e2e/package.json ./monitor-e2e/
COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js
COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production

View File

@ -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 |

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
cd $(dirname $0)
../e2e-commons/up.sh deploy generate-amb-tx blocks alm alm-e2e
../e2e-commons/up.sh deploy blocks alm alm-e2e
# run oracle amb e2e tests to generate transactions for alm
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run alm

View File

@ -6,8 +6,8 @@ jest.setTimeout(60000)
const statusText = 'Success'
const statusSelector = 'label[data-id="status"]'
const homeToForeignTxURL = 'http://localhost:3004/77/0x295efbe6ae98937ef35d939376c9bd752b4dc6f6899a9d5ddd6a57cea3d76c89'
const foreignToHomeTxURL = 'http://localhost:3004/42/0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const homeToForeignTxURL = 'http://localhost:3004/77/0xbc83d43bdc675a615a2b820e43e52d25857aa5fdd77acf2dd92cd247af2c693c'
const foreignToHomeTxURL = 'http://localhost:3004/42/0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
describe('ALM', () => {
let browser

View File

@ -123,24 +123,6 @@ const abi: AbiItem[] = [
stateMutability: 'nonpayable',
type: 'function'
},
{
constant: false,
inputs: [
{
name: '_data',
type: 'bytes'
},
{
name: '_signatures',
type: 'bytes'
}
],
name: 'safeExecuteSignaturesWithAutoGasLimit',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function'
},
{
constant: true,
inputs: [

View File

@ -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>

View File

@ -1,50 +1,38 @@
import React, { useEffect, useState } from 'react'
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
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 ||
@ -60,43 +48,9 @@ export const ExecutionConfirmation = ({
const formattedValidator =
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
useEffect(
() => {
if (!availableManualExecution || !foreign.bridgeContract) return
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
p.then(({ major, minor }: any) => {
major = parseInt(major, 10)
minor = parseInt(minor, 10)
if (major < 5 || (major === 5 && minor < 7)) return
setSafeExecutionAvailable(true)
})
},
[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 +68,6 @@ export const ExecutionConfirmation = ({
return (
<StyledExecutionConfirmation>
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
<table>
<Thead>
<tr>
@ -153,14 +105,10 @@ export const ExecutionConfirmation = ({
{availableManualExecution && (
<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>
)}

View File

@ -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']}

View File

@ -14,92 +14,32 @@ 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`
const StyledButton = styled.button`
color: var(--button-color);
border-color: var(--font-color);
margin-top: 10px;
min-width: 120px;
padding: 1rem;
&:focus {
outline: var(--button-color);
}
`
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
]
)
useEffect(
() => {
@ -127,16 +67,12 @@ 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 =
safeExecutionAvailable && !allowFailures
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
: bridge.methods.executeSignatures
const data = executeMethod(messageData, signatures).encodeABI()
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false)
library.eth
@ -194,38 +130,17 @@ export const ManualExecutionButton = ({
foreign.bridgeContract,
setError,
messageData,
signatureCollected,
setExecutionData,
setPendingExecution,
safeExecutionAvailable,
allowFailures,
foreign.web3,
validSignatures
setPendingExecution
]
)
return (
<div>
<div className="is-center">
<ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
{title}
</ActionButton>
</div>
{safeExecutionAvailable && (
<div
title="Allow executed message to fail and record its failure on-chain without reverting the whole transaction.
Use fixed gas limit for execution."
className="is-center"
style={{ paddingTop: 10 }}
>
<input
type="checkbox"
id="allow-failures"
checked={allowFailures}
onChange={e => setAllowFailures(e.target.checked)}
/>
<label htmlFor="allow-failures">Unsafe mode</label>
</div>
)}
<div className="is-center">
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
Execute
</StyledButton>
</div>
)
}

View File

@ -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>
)

View File

@ -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}

View File

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

View File

@ -54,19 +54,14 @@ export const CONFIRMATIONS_STATUS = {
}
export const VALIDATOR_CONFIRMATION_STATUS = {
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.`

View File

@ -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

View File

@ -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 {

View File

@ -1,4 +1,4 @@
import React, { createContext, ReactNode } from 'react'
import React, { createContext, ReactNode, useState } from 'react'
import { useNetwork } from '../hooks/useNetwork'
import {
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>

View File

@ -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};
}
`

View File

@ -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

View File

@ -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', () => {

View File

@ -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 : ''
}))
getValidatorFailedTransaction
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x200' : '',
timestamp: validatorData.validator === validator2 ? 200 : 0
}))
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2 || validatorData.validator === validator4
? validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED
: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash:
validatorData.validator === validator2 ? '0x200' : validatorData.validator === validator4 ? '0x400' : '',
timestamp: validatorData.validator === validator2 ? 200 : validatorData.validator === validator4 ? 400 : ''
txHash: validatorData.validator !== validator2 ? '0x123' : '',
timestamp: validatorData.validator !== validator2 ? 123 : 0
}))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status:
validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x123' : '',
timestamp: validatorData.validator === validator2 ? 123 : 0
}))
getValidatorPendingTransaction
.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 }
])
)
})

View File

@ -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)

View File

@ -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]
// Use the value from last event before the transaction
const event = events[events.length - 1]
blockConfirmations = event.returnValues.requiredBlockConfirmations
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()

View File

@ -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,9 +180,7 @@ 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')
}
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
}
}
}
@ -197,7 +194,7 @@ export const getLogs = async (
}))
}
const filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase()
const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase()
export const getFailedTransactions = async (
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 = (

View File

@ -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,8 @@ export const getConfirmationsForTx = async (
)
updateConfirmations(validatorPendingConfirmations)
setPendingConfirmations(validatorPendingConfirmations.length > 0)
} else {
setPendingConfirmations(false)
}
const undefinedConfirmations = validatorConfirmations.filter(
@ -128,27 +95,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 +112,30 @@ 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)
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())
)
)
}
}
// 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 (

View File

@ -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)
}

View File

@ -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,

View File

@ -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)}`
}
if (encodedData.length >= dataOffset + 128) {
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
}
let decodedLogs: { [p: string]: string } = {
messageId: '',
encodedData: ''
}
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 }
})
}

View File

@ -1,5 +1,5 @@
const HOME_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/HomeBridgeErcToNative').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/XDaiForeignBridge.json').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative').abi
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockRewardMock').abi
const BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi

View File

@ -1,5 +1,3 @@
const { soliditySha3 } = require('web3-utils')
function strip0x(input) {
return input.replace(/^0x/, '')
}
@ -41,35 +39,8 @@ const normalizeAMBMessageEvent = e => {
return parseAMBMessage(msgData)
}
const ambInformationSignatures = [
'eth_call(address,bytes)',
'eth_call(address,bytes,uint256)',
'eth_call(address,address,uint256,bytes)',
'eth_blockNumber()',
'eth_getBlockByNumber()',
'eth_getBlockByNumber(uint256)',
'eth_getBlockByHash(bytes32)',
'eth_getBalance(address)',
'eth_getBalance(address,uint256)',
'eth_getTransactionCount(address)',
'eth_getTransactionCount(address,uint256)',
'eth_getTransactionByHash(bytes32)',
'eth_getTransactionReceipt(bytes32)',
'eth_getStorageAt(address,bytes32)',
'eth_getStorageAt(address,bytes32,uint256)'
]
const ambInformationSelectors = Object.fromEntries(ambInformationSignatures.map(sig => [soliditySha3(sig), sig]))
const normalizeAMBInfoRequest = e => ({
messageId: e.returnValues.messageId,
sender: e.returnValues.sender,
requestSelector: ambInformationSelectors[e.returnValues.requestSelector] || 'unknown',
data: e.returnValues.data
})
module.exports = {
strip0x,
parseAMBMessage,
normalizeAMBMessageEvent,
ambInformationSignatures,
normalizeAMBInfoRequest
normalizeAMBMessageEvent
}

View File

@ -8,10 +8,8 @@
"test": "NODE_ENV=test mocha"
},
"dependencies": {
"@mycrypto/gas-estimation": "^1.1.0",
"gas-price-oracle": "^0.1.5",
"web3-utils": "^1.3.0",
"node-fetch": "^2.1.2"
"web3-utils": "^1.3.0"
},
"devDependencies": {
"bn-chai": "^1.0.1",

View File

@ -1,7 +1,5 @@
const { toWei, toBN, BN } = require('web3-utils')
const { GasPriceOracle } = require('gas-price-oracle')
const { estimateFees } = require('@mycrypto/gas-estimation')
const fetch = require('node-fetch')
const { BRIDGE_MODES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
@ -177,27 +175,20 @@ const gasPriceWithinLimits = (gasPrice, limits) => {
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
let gasPrice = oracleGasPrice * factor
gasPrice = gasPriceWithinLimits(gasPrice, limits)
return toWei(gasPrice.toFixed(2).toString(), 'gwei')
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
const gasPriceFromSupplier = async (web3, url, options = {}) => {
// fetchFn has to be supplied (instead of just url to oracle),
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
try {
let json
if (url === 'eip1559-gas-estimation') {
const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFees(web3)
const res = { maxFeePerGas: maxFeePerGas.toString(10), maxPriorityFeePerGas: maxPriorityFeePerGas.toString(10) }
options.logger &&
options.logger.debug &&
options.logger.debug(res, 'Gas price updated using eip1559-gas-estimation')
return res
}
if (url === 'gas-price-oracle') {
json = await gasPriceOracle.fetchGasPricesOffChain()
} else if (url) {
const response = await fetch(url, { timeout: 2000 })
if (fetchFn) {
const response = await fetchFn()
json = await response.json()
} else {
return null
json = await gasPriceOracle.fetchGasPricesOffChain()
}
const oracleGasPrice = json[options.speedType]
@ -214,7 +205,7 @@ const gasPriceFromSupplier = async (web3, url, options = {}) => {
options.logger.debug &&
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
return { gasPrice: normalizedGasPrice }
return normalizedGasPrice
} catch (e) {
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
}
@ -223,11 +214,11 @@ const gasPriceFromSupplier = async (web3, url, options = {}) => {
const gasPriceFromContract = async (bridgeContract, options = {}) => {
try {
const gasPrice = (await bridgeContract.methods.gasPrice().call()).toString()
const gasPrice = await bridgeContract.methods.gasPrice().call()
options.logger &&
options.logger.debug &&
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
return { gasPrice }
return gasPrice
} catch (e) {
options.logger &&
options.logger.error &&

@ -1 +1 @@
Subproject commit 908a48107919d4ab127f9af07d44d47eac91547e
Subproject commit c2575aa268734ab6bd03955c6545bd634516e262

View File

@ -1,6 +1,6 @@
/var/log/docker/*/docker.log {
rotate 5
size 100M
size 1G
compress
missingok
delaycompress
@ -8,7 +8,7 @@
}
/var/log/docker/*.log {
rotate 5
size 100M
size 1G
compress
missingok
delaycompress

View File

@ -7,7 +7,7 @@
loop_control:
loop_var: file
- name: Set the oracle's containers local logs configuration file
- name: Set the local container logs configuration file
template:
src: 31-oracle-docker.conf.j2
dest: /etc/rsyslog.d/31-oracle-docker.conf
@ -15,22 +15,6 @@
group: root
mode: 0644
- name: Set the redis container local logs configuration file
template:
src: 32-redis-docker.conf.j2
dest: /etc/rsyslog.d/32-redis-docker.conf
owner: root
group: root
mode: 0644
- name: Set the rabbit MQ container local logs configuration file
template:
src: 33-rabbit-docker.conf.j2
dest: /etc/rsyslog.d/33-rabbit-docker.conf
owner: root
group: root
mode: 0644
- name: Set the log configuration file to send container logs to remote server
template:
src: 36-oracle-remote-logging.conf.j2

View File

@ -27,6 +27,17 @@
set_fact:
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
- name: Get foreign erc type
become_user: "{{ compose_service_user }}"
shell: docker-compose run --rm --entrypoint "node scripts/initialChecks.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: ERCTYPE
- name: Set FOREIGN_ERC_TYPE variable
set_fact:
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
- name: Extend docker compose file for erc to native
set_fact: composefileoverride="-f docker-compose-transfer.yml"
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"

View File

@ -11,16 +11,9 @@ ORACLE_HOME_RPC_POLLING_INTERVAL={{ ORACLE_HOME_RPC_POLLING_INTERVAL }}
## Foreign contract
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
{% if ORACLE_FOREIGN_ARCHIVE_RPC_URL | default('') != '' %}
ORACLE_FOREIGN_ARCHIVE_RPC_URL={{ ORACLE_FOREIGN_ARCHIVE_RPC_URL }}
{% endif %}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% endif %}
## Gasprice
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
@ -54,28 +47,8 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
{% if ORACLE_FOREIGN_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_FOREIGN_TX_RESEND_INTERVAL={{ ORACLE_FOREIGN_TX_RESEND_INTERVAL }}
{% endif %}
{% if ORACLE_HOME_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_HOME_TX_RESEND_INTERVAL={{ ORACLE_HOME_TX_RESEND_INTERVAL }}
{% endif %}
## Emergency shutdown configuration
{% if ORACLE_SHUTDOWN_SERVICE_URL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_URL={{ ORACLE_SHUTDOWN_SERVICE_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | default('') != '' %}
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL={{ ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL }}
{% endif %}
{% if ORACLE_SIDE_RPC_URL | default('') != '' %}
ORACLE_SIDE_RPC_URL={{ ORACLE_SIDE_RPC_URL }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_ADDRESS | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_ADDRESS={{ ORACLE_SHUTDOWN_CONTRACT_ADDRESS }}
{% endif %}
{% if ORACLE_SHUTDOWN_CONTRACT_METHOD | default('') != '' %}
ORACLE_SHUTDOWN_CONTRACT_METHOD={{ ORACLE_SHUTDOWN_CONTRACT_METHOD }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% endif %}
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}

View File

@ -1,11 +0,0 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Redis" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*redis.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'redis' then \
?DockerLogFileName_Redis
$FileCreateMode 0600

View File

@ -1,11 +0,0 @@
$FileCreateMode 0644
template(name="DockerLogFileName_Rabbit" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*rabbit.*)\\/[a-zA-Z0-9]+\\[")
constant(value="/docker.log")
}
if $programname contains 'oracle' and $programname contains 'rabbit' then \
?DockerLogFileName_Rabbit
$FileCreateMode 0600

View File

@ -6,12 +6,14 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C
COMMON_FOREIGN_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000
@ -23,9 +25,3 @@ ORACLE_HOME_START_BLOCK=1
ORACLE_FOREIGN_START_BLOCK=1
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt
ORACLE_FOREIGN_ARCHIVE_RPC_URL=http://parity2:8545
ORACLE_HOME_EVENTS_REPROCESSING=false
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10
ORACLE_FOREIGN_EVENTS_REPROCESSING=true
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10

View File

@ -6,6 +6,8 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c
COMMON_FOREIGN_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1
@ -22,9 +24,3 @@ ORACLE_ALLOW_HTTP_FOR_RPC=yes
ORACLE_HOME_START_BLOCK=1
ORACLE_FOREIGN_START_BLOCK=1
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=/mono/oracle/access-lists/block_list.txt
ORACLE_HOME_EVENTS_REPROCESSING=true
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE=10
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY=10
ORACLE_FOREIGN_EVENTS_REPROCESSING=true
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE=10
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY=10

View File

@ -38,7 +38,7 @@
"ercToNativeBridge": {
"home": "0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c",
"foreign": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
"foreignToken": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
"monitor": "http://monitor-erc20-native:3012/bridge"
},
"amb": {
@ -49,12 +49,6 @@
"blockedHomeBox": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977",
"monitor": "http://monitor-amb:3013/bridge"
},
"amb2": {
"home": "0x5A42E119990c3F3A80Fea20aAF4c3Ff4DB240Cc9",
"foreign": "0x897527391ad3837604973d78D3514f44c36AB9FC",
"homeBox": "0xb008E9076fCbDB2C3AF84225Bc07Eb35B2bE5ECD",
"foreignBox": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
},
"homeRPC": {
"URL": "http://parity1:8545",
"ID": "77"

View File

@ -32,7 +32,7 @@ FOREIGN_GAS_PRICE=10000000000
FOREIGN_REWARDABLE=false
BLOCK_REWARD_ADDRESS=0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B
ERC20_TOKEN_ADDRESS=0x6B175474E89094C44Da98b954EedeAC495271d0F
ERC20_TOKEN_ADDRESS=0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"

View File

@ -35,9 +35,3 @@ echo -e "\n\n############ Deploying one more test contract for amb ############\
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null
echo -e "\n\n############ Deploying one more amb without oracle for confirm relay tests ############\n"
cp "$ENVS_PATH/amb.env" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null

View File

@ -15,46 +15,42 @@ docker network create --driver bridge ultimate || true
docker-compose up -d parity1 parity2 e2e
startValidator () {
db_env="-e ORACLE_QUEUE_URL=amqp://$3 -e ORACLE_REDIS_URL=redis://$2"
db_env="-e ORACLE_QUEUE_URL=amqp://$4 -e ORACLE_REDIS_URL=redis://$3"
docker-compose $1 run -d --name $2 redis
docker-compose $1 run -d --name $3 rabbit
docker-compose $1 run -d --name $3 redis
docker-compose $1 run -d --name $4 rabbit
if [[ -z "$MODE" || "$MODE" == erc-to-native ]]; then
docker-compose $1 run $oraclePK $db_env -d oracle-erc20-native yarn watcher:signature-request
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose $1 run $oracleAddr $db_env -d oracle-erc20-native yarn watcher:transfer
docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:signature-request
docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose $1 run $2 $db_env -d oracle-erc20-native yarn watcher:transfer
fi
if [[ -z "$MODE" || "$MODE" == amb ]]; then
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn watcher:signature-request
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:collected-signatures
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:affirmation-request
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn watcher:information-request
docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:signature-request
docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:collected-signatures
docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:affirmation-request
docker-compose $1 run $2 $db_env -d oracle-amb yarn watcher:information-request
fi
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn sender:home
docker-compose $1 run $oraclePK $db_env -d oracle-amb yarn sender:foreign
docker-compose $1 run $oracleAddr $db_env -d oracle-amb yarn manager:shutdown
docker-compose $1 run $2 $db_env -d oracle-amb yarn sender:home
docker-compose $1 run $2 $db_env -d oracle-amb yarn sender:foreign
docker-compose $1 run $2 $db_env -d oracle-amb yarn manager:shutdown
}
while [ "$1" != "" ]; do
if [ "$1" == "oracle" ]; then
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
startValidator "-p validator1" redis rabbit
startValidator "-p validator1" "" redis rabbit
fi
if [ "$1" == "oracle-validator-2" ]; then
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" redis2 rabbit2
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" "$oracle2Values" redis2 rabbit2
fi
if [ "$1" == "oracle-validator-3" ]; then
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" redis3 rabbit3
oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" "$oracle3Values" redis3 rabbit3
fi
if [ "$1" == "alm" ]; then
@ -88,38 +84,13 @@ while [ "$1" != "" ]; do
if [ "$1" == "alm-e2e" ]; then
MODE=amb
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
startValidator "-p validator1" redis rabbit
startValidator "-p validator1" "" redis rabbit
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" redis2 rabbit2
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
startValidator "-p validator2" "$oracle2Values" redis2 rabbit2
oracleAddr="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d"
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" redis3 rabbit3
fi
if [ "$1" == "generate-amb-tx" ]; then
docker-compose run e2e yarn workspace oracle-e2e run generate-amb-tx
fi
if [ "$1" == "manual-amb-relay" ]; then
oraclePK="-e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
env="-e COMMON_HOME_BRIDGE_ADDRESS=0x5A42E119990c3F3A80Fea20aAF4c3Ff4DB240Cc9 -e COMMON_FOREIGN_BRIDGE_ADDRESS=0x897527391ad3837604973d78D3514f44c36AB9FC"
# these tx hash are hardcoded and need to be updated manually
# once e2e environment setup process is changed
echo '0xea625a823bc5018dc3a4efe349f623e5ebb8c987b55f44d50d6556f42af9a400' > txHashes.txt
docker-compose -p validator1 run -v $(pwd)/txHashes.txt:/tmp/txHashes.txt $oraclePK $env oracle-amb yarn confirm:affirmation-request \
/tmp/txHashes.txt \
0x031c42e44485002c9215a5b1b75e9516131485ce29884a58765bf7a0038538f9
docker-compose -p validator1 run $oraclePK $env oracle-amb yarn confirm:signature-request \
0x1506a18af91afe732167ccbc178b55fc2547da4a814d13c015b6f496cf171754 | tee .tmp.log
tx_hash=$(cat .tmp.log | grep generatedTransactionHash | jq -r .generatedTransactionHash)
rm .tmp.log
rm txHashes.txt
docker-compose -p validator1 run $oraclePK $env oracle-amb yarn confirm:collected-signatures $tx_hash
oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
startValidator "-p validator3" "$oracle3Values" redis3 rabbit3
fi
shift # Shift all the parameters down by one

View File

@ -1,9 +1,10 @@
require('dotenv').config()
const logger = require('./logger')('detectMediators.js')
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
const eventsInfo = require('./utils/events')
const { getHomeTxSender, getForeignTxSender, isHomeContract, isForeignContract } = require('./utils/web3Cache')
const { addExecutionStatus, addRetrievalStatus } = require('./utils/message')
const { normalizeAMBMessageEvent, normalizeAMBInfoRequest } = require('../commons')
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
const { addExecutionStatus } = require('./utils/message')
const { normalizeAMBMessageEvent } = require('../commons')
function countInteractions(requests) {
const stats = {}
@ -29,41 +30,6 @@ function countInteractions(requests) {
return stats
}
function countInfoRequests(requests) {
const stats = {}
requests.forEach(msg => {
if (!stats[msg.sender]) {
stats[msg.sender] = {}
}
if (!stats[msg.sender][msg.requestSelector]) {
stats[msg.sender][msg.requestSelector] = {
callSucceeded: {
callbackSucceeded: 0,
callbackFailed: 0
},
callFailed: {
callbackSucceeded: 0,
callbackFailed: 0
},
pending: 0
}
}
const stat = stats[msg.sender][msg.requestSelector]
if (msg.callStatus === true && msg.callbackStatus === true) {
stat.callSucceeded.callbackSucceeded += 1
} else if (msg.callStatus === true && msg.callbackStatus === false) {
stat.callSucceeded.callbackFailed += 1
} else if (msg.callStatus === false && msg.callbackStatus === true) {
stat.callFailed.callbackSucceeded += 1
} else if (msg.callStatus === false && msg.callbackStatus === false) {
stat.callFailed.callbackFailed += 1
} else {
stat.pending += 1
}
})
return stats
}
const normalize = event => ({
...normalizeAMBMessageEvent(event),
txHash: event.transactionHash,
@ -122,13 +88,10 @@ async function main(mode) {
homeToForeignRequests,
foreignToHomeRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
informationRequests,
informationResponses
foreignToHomeConfirmations
} = await eventsInfo(mode)
const homeToForeign = homeToForeignRequests.map(normalize).map(addExecutionStatus(homeToForeignConfirmations))
const foreignToHome = foreignToHomeRequests.map(normalize).map(addExecutionStatus(foreignToHomeConfirmations))
const infoRequests = informationRequests.map(normalizeAMBInfoRequest).map(addRetrievalStatus(informationResponses))
for (const event of homeToForeign) {
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
@ -183,7 +146,6 @@ async function main(mode) {
floatingMediators,
remotelyControlledMediators,
unknown,
informationReceivers: countInfoRequests(infoRequests),
lastChecked: Math.floor(Date.now() / 1000)
}
}

View File

@ -46,19 +46,29 @@ async function main(bridgeMode, eventsInfo) {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
let investedAmountInDai = 0
let bridgeDsrBalance = 0
let displayChaiToken = false
try {
logger.debug('calling foreignBridge.methods.isChaiTokenEnabled')
if (await foreignBridge.methods.isChaiTokenEnabled().call()) {
displayChaiToken = true
logger.debug('calling foreignBridge.methods.investedAmountInDai')
investedAmountInDai = await foreignBridge.methods.investedAmountInDai().call()
logger.debug('calling foreignBridge.methods.dsrBalance')
bridgeDsrBalance = await foreignBridge.methods.dsrBalance().call()
} else {
logger.debug('Chai token is currently disabled')
}
} catch (e) {
logger.debug('Methods for chai token are not present')
}
logger.debug('calling erc20Contract.methods.balanceOf')
const foreignErc20Balance = await erc20Contract.methods
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
.call({}, foreignDelayedBlockNumber)
let foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
try {
logger.debug('calling foreignBridge.methods.investedAmount')
const invested = await foreignBridge.methods.investedAmount(erc20Address).call({}, foreignDelayedBlockNumber)
foreignErc20BalanceBN = foreignErc20BalanceBN.plus(invested)
} catch (_) {
logger.debug('compounding related methods are not present in the foreign bridge')
}
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME_BRIDGE_ADDRESS)
logger.debug('calling homeBridge.methods.blockRewardContract')
@ -74,16 +84,30 @@ async function main(bridgeMode, eventsInfo) {
const mintedCoinsBN = new BN(mintedCoins)
const burntCoinsBN = new BN(burntCoins)
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
const foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
const investedAmountInDaiBN = new BN(investedAmountInDai)
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)
const diff = foreignErc20BalanceBN
.plus(investedAmountInDaiBN)
.minus(totalSupplyBN)
.toFixed()
const foreign = {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
}
if (displayChaiToken) {
foreign.investedErc20Balance = Web3Utils.fromWei(investedAmountInDai)
foreign.accumulatedInterest = Web3Utils.fromWei(bridgeDsrBalanceBN.minus(investedAmountInDaiBN).toString(10))
}
const diff = foreignErc20BalanceBN.minus(totalSupplyBN).toFixed()
logger.debug('Done')
return {
home: {
totalSupply: Web3Utils.fromWei(totalSupplyBN.toFixed())
},
foreign: {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
},
foreign,
balanceDiff: Number(Web3Utils.fromWei(diff)),
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)

View File

@ -21,9 +21,7 @@ async function main(bridgeMode, eventsInfo) {
homeToForeignConfirmations,
homeToForeignRequests,
foreignToHomeConfirmations,
foreignToHomeRequests,
informationRequests,
informationResponses
foreignToHomeRequests
} = eventsInfo
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
@ -36,9 +34,7 @@ async function main(bridgeMode, eventsInfo) {
fromForeignToHomeDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
home: {
toForeign: homeToForeignRequests.length,
fromForeign: foreignToHomeConfirmations.length,
informationRequests: informationRequests.length,
informationResponses: informationResponses.length
fromForeign: foreignToHomeConfirmations.length
},
foreign: {
fromHome: homeToForeignConfirmations.length,

View File

@ -11,6 +11,7 @@ const {
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI
} = require('../../commons')
const { normalizeEventInformation } = require('./message')
const { filterTransferBeforeES } = require('./tokenUtils')
const { writeFile, readCacheFile } = require('./file')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./web3')
const { getPastEvents } = require('./web3Cache')
@ -100,6 +101,21 @@ async function main(mode) {
chain: 'home'
})).map(normalizeEvent)
homeToForeignRequests = [...homeToForeignRequests, ...homeToForeignRequestsNew]
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
const used = {}
const total = homeToForeignRequests.length
const onlyFirstUniqueFilter = e => {
const msgId = e.returnValues.messageId || e.transactionHash
if (used[msgId]) {
return false
}
used[msgId] = true
return true
}
homeToForeignRequests = homeToForeignRequests.filter(onlyFirstUniqueFilter)
const dropped = total - homeToForeignRequests.length
logger.debug(`Dropped ${dropped}/${total} UserRequestForSignature events with same message id`)
}
logger.debug("calling foreignBridge.getPastEvents('RelayedMessage')")
const homeToForeignConfirmations = (await getPastEvents(foreignBridge, {
@ -128,27 +144,6 @@ async function main(mode) {
})).map(normalizeEvent)
foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew]
let informationRequests
let informationResponses
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
logger.debug("calling homeBridge.getPastEvents('UserRequestForInformation')")
informationRequests = (await getPastEvents(homeBridge, {
event: 'UserRequestForInformation',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('InformationRetrieved')")
informationResponses = (await getPastEvents(homeBridge, {
event: 'InformationRetrieved',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber,
safeToBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
}
if (isExternalErc20) {
logger.debug("calling erc20Contract.getPastEvents('Transfer')")
let transferEvents = (await getPastEvents(erc20Contract, {
@ -159,32 +154,80 @@ async function main(mode) {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
}))
.map(normalizeEvent)
.filter(e => e.recipient !== ZERO_ADDRESS) // filter mint operation during SCD-to-MCD swaps
.filter(e => e.recipient.toLowerCase() !== '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643') // filter cDai withdraws during compounding
// Get transfer events for each previously used Sai token
const saiTokenAddress = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, saiTokenAddress)
logger.debug('Half duplex token:', saiTokenAddress)
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
const blockNumberHalfDuplexDisabled = 9884448
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: Math.min(blockNumberHalfDuplexDisabled, foreignDelayedBlockNumber),
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
})).map(normalizeEvent)
transferEvents = [...halfDuplexTransferEvents, ...transferEvents]
let directTransfers = transferEvents
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
if (tokensSwappedAbiExists) {
logger.debug('collecting half duplex tokens participated in the bridge balance')
logger.debug("calling foreignBridge.getPastEvents('TokensSwapped')")
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
event: 'TokensSwapped',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
})
// Get token swap events emitted by foreign bridge
const bridgeTokensSwappedEvents = tokensSwappedEvents.filter(e => e.address === COMMON_FOREIGN_BRIDGE_ADDRESS)
// Get transfer events for each previous erc20
const uniqueTokenAddressesSet = new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))
// Exclude chai token from previous erc20
try {
logger.debug('calling foreignBridge.chaiToken() to remove it from half duplex tokens list')
const chaiToken = await foreignBridge.methods.chaiToken().call()
uniqueTokenAddressesSet.delete(chaiToken)
} catch (e) {
logger.debug('call to foreignBridge.chaiToken() failed')
}
// Exclude dai token from previous erc20
try {
logger.debug('calling foreignBridge.erc20token() to remove it from half duplex tokens list')
const daiToken = await foreignBridge.methods.erc20token().call()
uniqueTokenAddressesSet.delete(daiToken)
} catch (e) {
logger.debug('call to foreignBridge.erc20token() failed')
}
const uniqueTokenAddresses = [...uniqueTokenAddressesSet]
await Promise.all(
uniqueTokenAddresses.map(async tokenAddress => {
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
logger.debug('Half duplex token:', tokenAddress)
logger.debug("calling halfDuplexTokenContract.getPastEvents('Transfer')")
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignDelayedBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
},
chain: 'foreign'
})).map(normalizeEvent)
// Remove events after the ES
logger.debug('filtering half duplex transfers happened before ES')
const validHalfDuplexTransfers = await filterTransferBeforeES(halfDuplexTransferEvents)
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]
})
)
// filter transfer that is part of a token swap
directTransfers = transferEvents.filter(
e =>
bridgeTokensSwappedEvents.findIndex(
t => t.transactionHash === e.referenceTx && e.recipient === ZERO_ADDRESS
) === -1
)
}
// Get transfer events that didn't have a UserRequestForAffirmation event in the same transaction
const directTransfers = transferEvents.filter(
directTransfers = directTransfers.filter(
e => foreignToHomeRequests.findIndex(t => t.referenceTx === e.referenceTx) === -1
)
@ -197,8 +240,6 @@ async function main(mode) {
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
informationRequests,
informationResponses,
isExternalErc20,
bridgeMode,
homeBlockNumber,

View File

@ -8,8 +8,8 @@ function readFile(filePath, parseJson = true) {
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
} catch (_) {
console.error(`File ${filePath} does not exist`)
} catch (e) {
console.error('readFlle', e)
return {
error: 'the bridge statistics are not available'
}

View File

@ -28,21 +28,6 @@ function addExecutionStatus(processedList) {
}
}
function addRetrievalStatus(retrievedInfoList) {
const statuses = {}
retrievedInfoList.forEach(e => {
statuses[e.returnValues.messageId] = {
callStatus: e.returnValues.status,
callbackStatus: e.returnValues.callbackStatus
}
})
return deliveredMsg => {
deliveredMsg.callStatus = statuses[deliveredMsg.messageId].callStatus
deliveredMsg.callbackStatus = statuses[deliveredMsg.messageId].callbackStatus
return deliveredMsg
}
}
/**
* Normalizes the different event objects to facilitate data processing
* @param {Object} event
@ -104,7 +89,6 @@ module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered,
addExecutionStatus,
addRetrievalStatus,
normalizeEventInformation,
eventWithoutReference,
unclaimedHomeToForeignRequests,

View File

@ -0,0 +1,27 @@
// https://etherscan.io/tx/0xd0c3c92c94e05bc71256055ce8c4c993e047f04e04f3283a04e4cb077b71f6c6
const blockNumberHalfDuplexDisabled = 9884448
/**
* Returns true if the event was before the bridge stopped supporting half duplex transfers.
*/
async function transferBeforeES(event) {
return event.blockNumber < blockNumberHalfDuplexDisabled
}
async function filterTransferBeforeES(array) {
const newArray = []
// Iterate events from newer to older
for (let i = array.length - 1; i >= 0; i--) {
const beforeES = await transferBeforeES(array[i])
if (beforeES) {
// add element to first position so the new array will have the same order
newArray.unshift(array[i])
}
}
return newArray
}
module.exports = {
filterTransferBeforeES,
blockNumberHalfDuplexDisabled
}

View File

@ -1,5 +1,6 @@
require('dotenv').config()
const Web3Utils = require('web3').utils
const fetch = require('node-fetch')
const logger = require('./logger')('validators')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, gasPriceFromSupplier } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
@ -80,7 +81,7 @@ async function main(bridgeMode) {
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
logger.debug('calling home getGasPrices')
homeGasPrice =
(await gasPriceFromSupplier(COMMON_HOME_GAS_PRICE_SUPPLIER_URL, homeGasPriceSupplierOpts)) ||
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
@ -92,9 +93,13 @@ async function main(bridgeMode) {
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
logger.debug('calling foreign getGasPrices')
const fetchFn =
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL === 'gas-price-oracle'
? null
: () => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL)
foreignGasPrice =
(await gasPriceFromSupplier(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL, foreignGasPriceSupplierOpts)) ||
(await gasPriceFromSupplier(fetchFn, foreignGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))

View File

@ -5,7 +5,6 @@
"main": "index.js",
"scripts": {
"start": "mocha",
"generate-amb-tx": "node ./scripts/generate-amb-tx.js",
"lint": "eslint . --ignore-path ../.eslintignore",
"amb": "mocha test/amb.js",
"erc-to-native": "mocha test/ercToNative.js",

View File

@ -10,7 +10,7 @@ case "$mode" in
;;
esac
MODE="$mode" ../e2e-commons/up.sh deploy generate-amb-tx manual-amb-relay blocks oracle oracle-validator-2 oracle-validator-3
MODE="$mode" ../e2e-commons/up.sh deploy blocks oracle oracle-validator-2 oracle-validator-3
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run start $script
rc=$?

View File

@ -1,29 +0,0 @@
const Web3 = require('web3')
const { user, homeRPC, foreignRPC, amb2: amb } = require('../../e2e-commons/constants.json')
const { BOX_ABI } = require('../../commons')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
const opts = {
from: user.address,
gas: 400000,
gasPrice: '1'
}
const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox, opts)
const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox, opts)
async function main() {
const res1 = await homeBox.methods.setValueOnOtherNetwork(123, amb.home, amb.foreignBox).send()
const res2 = await foreignBox.methods.setValueOnOtherNetwork(456, amb.foreign, amb.homeBox).send()
const res3 = await foreignBox.methods.setValueOnOtherNetwork(789, amb.foreign, amb.homeBox).send()
console.log(res1.transactionHash)
console.log(res2.transactionHash)
console.log(res3.transactionHash)
}
main()

View File

@ -1,9 +1,8 @@
const Web3 = require('web3')
const assert = require('assert')
const { ASYNC_CALL_ERRORS } = require('../../oracle/src/utils/constants')
const { user, homeRPC, foreignRPC, amb, validator } = require('../../e2e-commons/constants.json')
const { uniformRetry } = require('../../e2e-commons/utils')
const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI, ambInformationSignatures } = require('../../commons')
const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI } = require('../../commons')
const { delay, setRequiredSignatures } = require('./utils')
const { toBN } = Web3.utils
@ -27,49 +26,27 @@ const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox, opts)
const homeBridge = new homeWeb3.eth.Contract(HOME_AMB_ABI, amb.home, opts)
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, amb.foreign, opts)
function validateBlock(web3, serialized, block) {
assert.strictEqual(serialized.length, 2 + 64 * 12)
const values = web3.eth.abi.decodeParameter(
'(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)',
serialized
)
assert.strictEqual(values[0], block.number.toString(), 'wrong block number returned')
assert.strictEqual(values[1], block.hash, 'wrong block hash returned')
assert.strictEqual(values[2], block.miner, 'wrong block miner returned')
assert.strictEqual(values[3], block.gasUsed.toString(), 'wrong block gasUsed returned')
assert.strictEqual(values[4], block.gasLimit.toString(), 'wrong block gasLimit returned')
assert.strictEqual(values[5], block.parentHash, 'wrong block parentHash returned')
assert.strictEqual(values[6], block.receiptsRoot, 'wrong block receiptsRoot returned')
assert.strictEqual(values[7], block.stateRoot, 'wrong block stateRoot returned')
assert.strictEqual(values[8], block.transactionsRoot, 'wrong block transactionsRoot returned')
assert.strictEqual(values[9], block.timestamp.toString(), 'wrong block timestamp returned')
assert.strictEqual(values[10], block.difficulty, 'wrong block difficulty returned')
assert.strictEqual(values[11], block.totalDifficulty, 'wrong block totalDifficulty returned')
}
function validateTransaction(web3, serialized, tx) {
assert.strictEqual(serialized.length, 64 * 13 + tx.input.length + 56)
const values = web3.eth.abi.decodeParameter(
'(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)',
serialized
)
assert.strictEqual(values[0], tx.hash, 'wrong txHash returned')
assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned')
assert.strictEqual(values[2], tx.blockHash.toString(), 'wrong tx blockHash returned')
assert.strictEqual(values[3], tx.transactionIndex.toString(), 'wrong tx transactionIndex returned')
assert.strictEqual(values[4], tx.from, 'wrong tx from returned')
assert.strictEqual(values[5], tx.to, 'wrong tx to returned')
assert.strictEqual(values[6], tx.value, 'wrong tx value returned')
assert.strictEqual(values[7], tx.nonce.toString(), 'wrong tx nonce returned')
assert.strictEqual(values[8], tx.gas.toString(), 'wrong tx gas returned')
assert.strictEqual(values[9], tx.gasPrice, 'wrong tx gasPrice returned')
assert.strictEqual(values[10], tx.input, 'wrong tx data returned')
}
describe('arbitrary message bridging', () => {
let requiredSignatures = 1
before(async () => {
for (const method of ambInformationSignatures) {
const allowedMethods = [
'eth_call(address,bytes)',
'eth_call(address,bytes,uint256)',
'eth_call(address,address,uint256,bytes)',
'eth_blockNumber()',
'eth_getBlockByNumber()',
'eth_getBlockByNumber(uint256)',
'eth_getBlockByHash(bytes32)',
'eth_getBalance(address)',
'eth_getBalance(address,uint256)',
'eth_getTransactionCount(address)',
'eth_getTransactionCount(address,uint256)',
'eth_getTransactionByHash(bytes32)',
'eth_getTransactionReceipt(bytes32)',
'eth_getStorageAt(address,bytes32)',
'eth_getStorageAt(address,bytes32,uint256)'
]
for (const method of allowedMethods) {
const selector = homeWeb3.utils.soliditySha3(method)
await homeBridge.methods.enableAsyncRequestSelector(selector, true).send({ from: validator.address })
}
@ -103,19 +80,6 @@ describe('arbitrary message bridging', () => {
}
})
})
if (process.env.ULTIMATE !== 'true') {
describe('Confirm Relay', () => {
it('should process lost affirmation-request via confirm relay', async () => {
const value = await homeBox.methods.value().call()
assert(value === '789', 'incorrect value')
})
it('should process lost signature-request & collected-signatures via confirm relay', async () => {
const value = await foreignBox.methods.value().call()
assert(value === '123', 'incorrect value')
})
})
}
describe('Home to Foreign', () => {
describe('Subsidized Mode', () => {
it('should bridge message', async () => {
@ -124,13 +88,12 @@ describe('arbitrary message bridging', () => {
const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
const res = await homeBox.methods
await homeBox.methods
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
.send()
.catch(e => {
console.error(e)
})
console.log(res.transactionHash)
// check that value changed and balance decreased
await uniformRetry(async retry => {
@ -177,6 +140,73 @@ describe('arbitrary message bridging', () => {
const value = await foreignBox.methods.value().call()
assert(!toBN(value).eq(toBN(newValue)), 'Message should not be relayed by oracle automatically')
})
it('should reconfirm message in case of validators change', async () => {
const newValue = 15
const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
const signatures = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,
toBlock: 'latest'
})
const { events } = await homeBox.methods
.setValueOnOtherNetworkUsingManualLane(newValue, amb.home, amb.foreignBox)
.send()
.catch(e => {
console.error(e)
})
const message = homeWeb3.eth.abi.decodeParameter('bytes', events['0'].raw.data)
await delay(10000)
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 3,
options: {
from: validator.address,
gas: '4000000'
}
})
const signatures2 = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,
toBlock: 'latest'
})
assert(
signatures2.length === signatures.length + requiredSignatures,
`Incorrect amount of signatures submitted, got ${signatures2.length}, expected ${signatures.length +
requiredSignatures}`
)
await homeBridge.methods.requestMessageReconfirm(message).send()
await delay(10000)
const signatures3 = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,
toBlock: 'latest'
})
assert(
signatures3.length === signatures.length + 3,
`Incorrect amount of signatures submitted, got ${signatures3.length}, expected ${signatures.length + 3}`
)
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
})
}
it('should confirm but not relay message from manual lane', async () => {
@ -223,13 +253,12 @@ describe('arbitrary message bridging', () => {
const initialValue = await homeBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
const res = await foreignBox.methods
await foreignBox.methods
.setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox)
.send()
.catch(e => {
console.error(e)
})
console.log(res.transactionHash)
// check that value changed and balance decreased
await uniformRetry(async retry => {
@ -304,7 +333,7 @@ describe('arbitrary message bridging', () => {
await makeAsyncCall(selector, data2)
assert(!(await homeBox.methods.status().call()), 'status is true')
assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect')
assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect')
const data3 = homeWeb3.eth.abi.encodeParameters(
['address', 'address', 'uint256', 'bytes'],
@ -314,7 +343,7 @@ describe('arbitrary message bridging', () => {
await makeAsyncCall(selector, data3)
assert(!(await homeBox.methods.status().call()), 'status is true')
assert.strictEqual(await homeBox.methods.data().call(), ASYNC_CALL_ERRORS.REVERT, 'returned data is incorrect')
assert.strictEqual(await homeBox.methods.data().call(), null, 'returned data is incorrect')
})
it('should make async eth_call for specific block', async () => {
@ -323,7 +352,7 @@ describe('arbitrary message bridging', () => {
const selector = homeWeb3.utils.soliditySha3('eth_call(address,bytes,uint256)')
const data1 = homeWeb3.eth.abi.encodeParameters(
['address', 'bytes', 'uint256'],
[amb.foreignBox, foreignBox.methods.value().encodeABI(), 25]
[amb.foreignBox, foreignBox.methods.value().encodeABI(), 60]
)
const data2 = homeWeb3.eth.abi.encodeParameters(
['address', 'bytes', 'uint256'],
@ -355,11 +384,6 @@ describe('arbitrary message bridging', () => {
await makeAsyncCall(selector, data3)
assert(!(await homeBox.methods.status().call()), 'status is true')
assert.strictEqual(
await homeBox.methods.data().call(),
ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE,
'returned data is incorrect'
)
})
it('should make async eth_blockNumber', async () => {
@ -379,8 +403,15 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
assert.strictEqual(data.length, 2 + 64 * 3)
const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters(
['uint256', 'bytes32', 'address'],
data
)
const block = await foreignWeb3.eth.getBlock(blockNumber)
validateBlock(homeWeb3, data, block)
assert.strictEqual(number, blockNumber, 'wrong block number returned')
assert.strictEqual(hash, block.hash, 'wrong block hash returned')
assert.strictEqual(miner, block.miner, 'wrong block miner returned')
})
it('should make async eth_getBlockByNumber and return latest block', async () => {
@ -390,7 +421,7 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
assert.strictEqual(data.length, 2 + 64 * 12)
assert.strictEqual(data.length, 2 + 64 * 3)
})
it('should make async eth_getBlockByHash', async () => {
@ -402,7 +433,16 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
validateBlock(homeWeb3, data, block)
assert.strictEqual(data.length, 2 + 64 * 3)
const { 0: number, 1: hash, 2: miner } = homeWeb3.eth.abi.decodeParameters(
['uint256', 'bytes32', 'address'],
data
)
assert.strictEqual(number, blockNumber, 'wrong block number returned')
assert.strictEqual(hash, block.hash, 'wrong block hash returned')
assert.strictEqual(miner, block.miner, 'wrong block miner returned')
})
it('should make async eth_getBalance', async () => {
@ -490,7 +530,7 @@ describe('arbitrary message bridging', () => {
})
it('should make async eth_getTransactionByHash', async () => {
const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
const tx = await foreignWeb3.eth.getTransaction(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionByHash(bytes32)')
@ -498,11 +538,32 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
validateTransaction(homeWeb3, data, tx)
const dataTypes = [
'bytes32',
'uint256',
'address',
'address',
'uint256',
'uint256',
'uint256',
'uint256',
'bytes'
]
const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data)
assert.strictEqual(values[0], txHash, 'wrong txHash returned')
assert.strictEqual(values[1], tx.blockNumber.toString(), 'wrong tx blockNumber returned')
assert.strictEqual(values[2], tx.from, 'wrong tx from returned')
assert.strictEqual(values[3], tx.to, 'wrong tx to returned')
assert.strictEqual(values[4], tx.value, 'wrong tx value returned')
assert.strictEqual(values[5], tx.nonce.toString(), 'wrong tx nonce returned')
assert.strictEqual(values[6], tx.gas.toString(), 'wrong tx gas returned')
assert.strictEqual(values[7], tx.gasPrice, 'wrong tx gasPrice returned')
assert.strictEqual(values[8], tx.input, 'wrong tx data returned')
})
it('should make async eth_getTransactionReceipt', async () => {
const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6'
const receipt = await foreignWeb3.eth.getTransactionReceipt(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionReceipt(bytes32)')
@ -510,25 +571,18 @@ describe('arbitrary message bridging', () => {
assert(await homeBox.methods.status().call(), 'status is false')
const data = await homeBox.methods.data().call()
const values = homeWeb3.eth.abi.decodeParameter(
'(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])',
data
)
const dataTypes = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]']
const values = homeWeb3.eth.abi.decodeParameters(dataTypes, data)
assert.strictEqual(values[0], txHash, 'wrong txHash returned')
assert.strictEqual(values[1], receipt.blockNumber.toString(), 'wrong tx blockNumber returned')
assert.strictEqual(values[2], receipt.blockHash, 'wrong tx blockHash returned')
assert.strictEqual(values[3], receipt.transactionIndex.toString(), 'wrong tx transactionIndex returned')
assert.strictEqual(values[4].toLowerCase(), receipt.from, 'wrong tx from returned')
assert.strictEqual(values[5].toLowerCase(), receipt.to, 'wrong tx to returned')
assert.strictEqual(values[6], receipt.gasUsed.toString(), 'wrong gasUsed to returned')
assert.strictEqual(values[7], receipt.status, 'wrong tx status returned')
assert.strictEqual(values[8].length, 1, 'wrong logs length returned')
assert.strictEqual(values[8][0][0], receipt.logs[0].address, 'wrong log address returned')
assert.strictEqual(values[8][0][1].length, 2, 'wrong log topics length returned')
assert.strictEqual(values[8][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned')
assert.strictEqual(values[8][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned')
assert.strictEqual(values[8][0][2], receipt.logs[0].data, 'wrong log data returned')
assert.strictEqual(values[2], receipt.status, 'wrong tx status returned')
assert.strictEqual(values[3].length, 1, 'wrong logs length returned')
assert.strictEqual(values[3][0][0], receipt.logs[0].address, 'wrong log address returned')
assert.strictEqual(values[3][0][1].length, 2, 'wrong log topics length returned')
assert.strictEqual(values[3][0][1][0], receipt.logs[0].topics[0], 'wrong event signature returned')
assert.strictEqual(values[3][0][1][1], receipt.logs[0].topics[1], 'wrong message id returned')
assert.strictEqual(values[3][0][2], receipt.logs[0].data, 'wrong log data returned')
})
it('should make async eth_getStorageAt', async () => {

View File

@ -33,10 +33,6 @@ const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME
describe('erc to native', () => {
before(async () => {
console.log('Initializing interest')
await foreignBridge.methods
.initializeInterest(ercToNativeBridge.foreignToken, 1, 1, validator.address)
.send({ from: validator.address, gas: '4000000' })
if (process.env.ULTIMATE === 'true') {
return
}
@ -104,8 +100,6 @@ describe('erc to native', () => {
const transferValue = homeWeb3.utils.toWei('0.05')
// transfer that should not be processed by the filter
await erc20Token.methods.transfer(secondUser.address, transferValue).send({ from: user.address, gas: 100000 })
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
@ -116,7 +110,6 @@ describe('erc to native', () => {
.catch(e => {
console.error(e)
})
await foreignBridge.methods.investDai().send({ from: validator.address, gas: '4000000' })
// check that balance increases
await uniformRetry(async retry => {

View File

@ -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.

View File

@ -6,7 +6,6 @@ module.exports = {
...baseConfig,
main: baseConfig.foreign,
event: 'UserRequestForAffirmation',
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@ -7,17 +7,8 @@ const {
HOME_AMB_ABI,
FOREIGN_AMB_ABI
} = require('../../commons')
const {
web3Home,
web3Foreign,
web3HomeRedundant,
web3HomeFallback,
web3ForeignRedundant,
web3ForeignFallback,
web3ForeignArchive
} = require('../src/services/web3')
const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants')
const { web3Home, web3Foreign } = require('../src/services/web3')
const { privateKeyToAddress } = require('../src/utils/utils')
const {
ORACLE_BRIDGE_MODE,
@ -31,15 +22,7 @@ const {
ORACLE_HOME_START_BLOCK,
ORACLE_FOREIGN_START_BLOCK,
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT,
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT,
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_RPC_BLOCK_POLLING_LIMIT
} = process.env
let homeAbi
@ -73,19 +56,11 @@ 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: {
enabled: ORACLE_HOME_EVENTS_REPROCESSING === 'true',
batchSize: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 1000,
blockDelay: parseInt(ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 500
}
eventContract: homeContract
}
const foreignContract = new web3Foreign.eth.Contract(foreignAbi, COMMON_FOREIGN_BRIDGE_ADDRESS)
@ -94,41 +69,19 @@ 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: {
enabled: ORACLE_FOREIGN_EVENTS_REPROCESSING === 'true',
batchSize: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE, 10) || 500,
blockDelay: parseInt(ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY, 10) || 250
}
eventContract: foreignContract
}
const maxProcessingTime =
parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval)
let validatorPrivateKey
if (ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY) {
validatorPrivateKey = add0xPrefix(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
const derived = privateKeyToAddress(validatorPrivateKey)
if (ORACLE_VALIDATOR_ADDRESS && derived.toLowerCase() !== ORACLE_VALIDATOR_ADDRESS.toLowerCase()) {
console.error(
`Derived address from private key - ${derived} is different from ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}`
)
process.exit(EXIT_CODES.INCOMPATIBILITY)
}
}
module.exports = {
eventFilter: {},
validatorPrivateKey,
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(validatorPrivateKey),
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY),
maxProcessingTime,
shutdownKey: 'oracle-shutdown',
home: homeConfig,

View File

@ -6,7 +6,6 @@ module.exports = {
...baseConfig,
main: baseConfig.home,
event: 'CollectedSignatures',
sender: 'foreign',
queue: 'foreign-prioritized',
name: `watcher-${id}`,
id

View File

@ -1,37 +0,0 @@
const baseConfig = require('./base.config')
const { DEFAULT_TRANSACTION_RESEND_INTERVAL } = require('../src/utils/constants')
const { MEV_HELPER_ABI } = require('../src/utils/mev')
const { web3Foreign, getFlashbotsProvider } = require('../src/services/web3')
const {
ORACLE_FOREIGN_TX_RESEND_INTERVAL,
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS,
ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE
} = process.env
const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS)
module.exports = {
...baseConfig,
pollingInterval: baseConfig.foreign.pollingInterval,
mevForeign: {
contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
contract,
minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS,
bundlesPerIteration: Math.max(parseInt(ORACLE_MEV_FOREIGN_BUNDLES_BLOCK_RANGE, 10) || 5, 1),
getFlashbotsProvider
},
mevJobsRedisKey: `${baseConfig.id}-collected-signatures-mev:mevJobs`,
id: 'mev-sender-foreign',
name: 'mev-sender-foreign',
web3: web3Foreign,
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
}

View File

@ -1,14 +1,18 @@
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',
oldQueue: 'foreign',
id: 'foreign',
name: 'sender-foreign',
web3: web3Foreign,
web3Redundant: web3ForeignRedundant,
web3Fallback: web3ForeignFallback,
resendInterval: parseInt(ORACLE_FOREIGN_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
}

View File

@ -1,14 +1,18 @@
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',
oldQueue: 'home',
id: 'home',
name: 'sender-home',
web3: web3Home,
web3Redundant: web3HomeRedundant,
web3Fallback: web3HomeFallback,
resendInterval: parseInt(ORACLE_HOME_TX_RESEND_INTERVAL, 10) || DEFAULT_TRANSACTION_RESEND_INTERVAL
}

View File

@ -1,12 +1,13 @@
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',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@ -1,30 +0,0 @@
const baseConfig = require('./base.config')
const { MEV_HELPER_ABI } = require('../src/utils/mev')
const {
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS
} = process.env
const id = `${baseConfig.id}-collected-signatures-mev`
const contract = new baseConfig.foreign.web3.eth.Contract(MEV_HELPER_ABI, ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS)
module.exports = {
...baseConfig,
mevForeign: {
contractAddress: ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS,
contract,
minGasPrice: ORACLE_MEV_FOREIGN_MIN_GAS_PRICE,
flatMinerFee: ORACLE_MEV_FOREIGN_FLAT_MINER_FEE,
maxPriorityFeePerGas: ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS,
maxFeePerGas: ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS
},
main: baseConfig.home,
event: 'CollectedSignatures',
name: `watcher-${id}`,
id
}

View File

@ -6,7 +6,6 @@ module.exports = {
...baseConfig,
main: baseConfig.home,
event: 'UserRequestForSignature',
sender: 'home',
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@ -1,7 +1,20 @@
const baseConfig = require('./base.config')
const { ERC20_ABI, ZERO_ADDRESS } = require('../../commons')
const { ERC20_ABI } = require('../../commons')
const { EXIT_CODES } = require('../src/utils/constants')
const initialChecksJson = process.argv[3]
if (!initialChecksJson) {
throw new Error('initial check parameter was not provided.')
}
let initialChecks
try {
initialChecks = JSON.parse(initialChecksJson)
} catch (e) {
throw new Error('Error on decoding values from initial checks.')
}
const id = `${baseConfig.id}-transfer`
if (baseConfig.id !== 'erc-native') {
@ -9,15 +22,14 @@ if (baseConfig.id !== 'erc-native') {
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
}
// exact address of the token contract is set in the watcher.js checkConditions() function
baseConfig.foreign.eventContract = new baseConfig.foreign.web3.eth.Contract(ERC20_ABI, ZERO_ADDRESS)
module.exports = {
...baseConfig,
main: baseConfig.foreign,
main: {
...baseConfig.foreign,
eventContract: new baseConfig.foreign.web3.eth.Contract(ERC20_ABI, initialChecks.bridgeableTokenAddress)
},
event: 'Transfer',
eventFilter: { to: baseConfig.foreign.bridgeAddress },
sender: 'home',
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
queue: 'home-prioritized',
name: `watcher-${id}`,
id

View File

@ -1,61 +0,0 @@
---
version: '2.4'
services:
redis:
cpus: 0.1
mem_limit: 500m
command: [ redis-server, --appendonly, 'yes' ]
hostname: redis
image: redis:4
restart: unless-stopped
volumes: [ '~/bridge_data/helpers/redis:/data' ]
interestFetcher:
cpus: 0.1
mem_limit: 500m
image: poanetwork/tokenbridge-oracle:latest
env_file: ./.env
environment:
NODE_ENV: production
INTEREST_FETCHER_PRIVATE_KEY: ${INTEREST_FETCHER_PRIVATE_KEY}
INTEREST_FETCH_CONTRACT_ADDRESS: '0xCd152c7Bd4189Ddee97EaBb783FC5cD93CF2D230'
INTERVAL: 300000
restart: unless-stopped
entrypoint: yarn helper:interestFether
mevWatcher:
cpus: 0.1
mem_limit: 500m
image: poanetwork/tokenbridge-oracle:latest
env_file: ./.env
environment:
NODE_ENV: production
ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS}
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD'
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: '15000' # CollectedSignatures event polling interval
ORACLE_HOME_START_BLOCK: 'TBD'
ORACLE_HOME_SKIP_MANUAL_LANE: 'true'
restart: unless-stopped
entrypoint: yarn mev:watcher:collected-signatures
mevSender:
cpus: 0.1
mem_limit: 500m
image: poanetwork/tokenbridge-oracle:latest
env_file: ./.env
environment:
NODE_ENV: production
ORACLE_VALIDATOR_ADDRESS: ${ORACLE_VALIDATOR_ADDRESS}
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: ${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}
ORACLE_MEV_FOREIGN_HELPER_CONTRACT_ADDRESS: 'TBD'
ORACLE_MEV_FOREIGN_MIN_GAS_PRICE: '50000000000' # 50 gwei
ORACLE_MEV_FOREIGN_FLAT_MINER_FEE: '1500000000000000' # 0.0015 eth = 300k gas * 5 gwei
ORACLE_MEV_FOREIGN_MAX_PRIORITY_FEE_PER_GAS: '0' # 0 gwei
ORACLE_MEV_FOREIGN_MAX_FEE_PER_GAS: '1000000000000' # 1000 gwei
ORACLE_MEV_FOREIGN_FLASHBOTS_RPC_URL: 'https://relay-goerli.flashbots.net'
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)
restart: unless-stopped
entrypoint: yarn mev:sender:foreign

View File

@ -13,15 +13,7 @@
"sender:home": "./scripts/start-worker.sh sender home-sender",
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"confirm:transfer": "./scripts/start-worker.sh confirmRelay transfer-watcher",
"confirm:affirmation-request": "./scripts/start-worker.sh confirmRelay affirmation-request-watcher",
"confirm:signature-request": "./scripts/start-worker.sh confirmRelay signature-request-watcher",
"confirm:collected-signatures": "./scripts/start-worker.sh confirmRelay collected-signatures-watcher",
"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'",
"test": "NODE_ENV=test mocha",
"test:watch": "NODE_ENV=test mocha --watch --reporter=min",
@ -31,19 +23,17 @@
"author": "",
"license": "ISC",
"dependencies": {
"@flashbots/ethers-provider-bundle": "^0.4.3",
"amqp-connection-manager": "^2.0.0",
"amqplib": "^0.5.2",
"bignumber.js": "^7.2.1",
"dotenv": "^5.0.1",
"ethers": "^5.5.3",
"ioredis": "^3.2.2",
"node-fetch": "^2.1.2",
"pino": "^4.17.3",
"pino-pretty": "^2.0.1",
"promise-limit": "^2.7.0",
"promise-retry": "^1.1.1",
"web3": "^1.6.0"
"web3": "^1.3.0"
},
"devDependencies": {
"bn-chai": "^1.0.1",

View File

@ -36,7 +36,7 @@ async function main() {
data,
nonce,
gasPrice: FOREIGN_TEST_TX_GAS_PRICE,
value: '0',
amount: '0',
gasLimit,
to: bridgeableTokenAddress,
web3: web3Foreign,

View File

@ -29,7 +29,7 @@ async function main() {
data: '0x',
nonce,
gasPrice: HOME_TEST_TX_GAS_PRICE,
value: web3Home.utils.toWei(HOME_MIN_AMOUNT_PER_TX),
amount: HOME_MIN_AMOUNT_PER_TX,
gasLimit: 100000,
to: COMMON_HOME_BRIDGE_ADDRESS,
web3: web3Home,

View File

@ -0,0 +1,26 @@
require('../env')
const { getTokensState } = require('../src/utils/tokenState')
const { FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons')
const { web3Foreign } = require('../src/services/web3')
const emptyLogger = {
debug: () => {},
info: () => {}
}
async function initialChecks() {
const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
let result = {}
if (ORACLE_BRIDGE_MODE === 'ERC_TO_NATIVE') {
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
result = await getTokensState(bridge, emptyLogger)
}
console.log(JSON.stringify(result))
return result
}
const result = initialChecks()
module.exports = result

View File

@ -1,65 +0,0 @@
require('../env')
const { isAddress } = require('web3').utils
const { privateKeyToAddress, setIntervalAndRun } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants')
const { web3Home } = require('../src/services/web3')
const { sendTx } = require('../src/tx/sendTx')
const privateKey = process.env.INTEREST_FETCHER_PRIVATE_KEY
const interval = process.env.INTERVAL ? parseInt(process.env.INTERVAL, 10) : 3600 * 1000
const contractAddress = process.env.INTEREST_FETCH_CONTRACT_ADDRESS
if (!privateKey) {
console.error('Environment variable INTEREST_FETCHER_PRIVATE_KEY is not set')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
if (interval < 300 * 1000) {
console.error('Interval is to small, should be at least 5 minutes')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
if (!isAddress(contractAddress)) {
console.error('Invalid contract address provided', contractAddress)
process.exit(EXIT_CODES.GENERAL_ERROR)
}
const gasPrice = process.env.COMMON_HOME_GAS_PRICE_FALLBACK || '1000000000'
async function main() {
// assuming that we are using this contract - https://github.com/omni/interest-fetcher-contract
const contract = new web3Home.eth.Contract([{ name: 'fetchInterest', type: 'function', inputs: [] }], contractAddress)
const chainId = await web3Home.eth.getChainId()
const data = contract.methods.fetchInterest().encodeABI()
const senderAddress = privateKeyToAddress(privateKey)
console.log(
`Initialized, chainId=${chainId}, data=${data}, contract=${contractAddress}, interval=${interval / 1000}s`
)
await setIntervalAndRun(async () => {
let gasLimit
try {
gasLimit = await contract.methods.fetchInterest().estimateGas()
} catch (e) {
console.log('Gas limit estimation failed, will retry later', new Date())
return
}
const nonce = await web3Home.eth.getTransactionCount(senderAddress)
const txHash = await sendTx({
privateKey,
to: contractAddress,
data,
nonce,
gasPrice,
gasLimit: Math.round(gasLimit * 1.5),
value: '0',
chainId,
web3: web3Home
})
console.log('Sent transaction with fetch interest', txHash, new Date())
}, interval)
}
main()

View File

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

View File

@ -8,10 +8,12 @@ LOGS_DIR="logs/"
WORKER="${WORKERS_DIR}${1}.js"
CONFIG="${2}.config.js"
LOG="${LOGS_DIR}${2}.txt"
TX_HASH=${@:3}
TX_HASH="${3}"
CHECKS=$(node scripts/initialChecks.js)
if [ "${NODE_ENV}" = "production" ]; then
exec node "${WORKER}" "${CONFIG}" $TX_HASH
exec node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH"
else
node "${WORKER}" "${CONFIG}" $TX_HASH | tee -a "${LOG}" | pino-pretty
node "${WORKER}" "${CONFIG}" "$CHECKS" "$TX_HASH" | tee -a "${LOG}" | pino-pretty
fi

View File

@ -1,39 +1,22 @@
require('../env')
const path = require('path')
const fs = require('fs')
const { isAttached, connectWatcherToQueue, connection } = require('./services/amqpClient')
const logger = require('./services/logger')
const GasPrice = require('./services/gasPrice')
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
const { sendTx } = require('./tx/sendTx')
const { getTokensState } = require('./utils/tokenState')
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
const { ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
const { ORACLE_VALIDATOR_ADDRESS, ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_ALLOW_HTTP_FOR_RPC } = process.env
if (process.argv.length < 4) {
if (process.argv.length < 5) {
logger.error('Please check the number of arguments, transaction hash is not present')
process.exit(EXIT_CODES.GENERAL_ERROR)
}
const config = require(path.join('../config/', process.argv[2]))
const { web3, eventContract, chain, bridgeContract } = config.main
const isTxHash = txHash => txHash.length === 66 && web3.utils.isHexStrict(txHash)
function readTxHashes(filePath) {
return fs
.readFileSync(filePath)
.toString()
.split('\n')
.map(v => v.trim())
.filter(isTxHash)
}
const txHashesArgs = process.argv.slice(3)
const rawTxHashes = txHashesArgs.filter(isTxHash)
const txHashesFiles = txHashesArgs.filter(path => fs.existsSync(path)).flatMap(readTxHashes)
const txHashes = [...rawTxHashes, ...txHashesFiles]
const txHash = process.argv[4]
const processSignatureRequests = require('./events/processSignatureRequests')(config)
const processCollectedSignatures = require('./events/processCollectedSignatures')(config)
@ -44,13 +27,15 @@ const processAMBCollectedSignatures = require('./events/processAMBCollectedSigna
const processAMBAffirmationRequests = require('./events/processAMBAffirmationRequests')(config)
const processAMBInformationRequests = require('./events/processAMBInformationRequests')(config)
const { web3, eventContract } = config.main
let attached
async function initialize() {
try {
const checkHttps = checkHTTPS(ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(chain))
web3.currentProvider.urls.forEach(checkHttps(config.chain))
attached = await isAttached()
if (attached) {
@ -74,12 +59,12 @@ async function runMain({ sendToQueue }) {
const sendJob = attached ? sendToQueue : sendJobTx
if (!attached || connection.isConnected()) {
if (config.maxProcessingTime) {
await watchdog(() => main({ sendJob, txHashes }), config.maxProcessingTime, () => {
await watchdog(() => main({ sendJob, txHash }), config.maxProcessingTime, () => {
logger.fatal('Max processing time reached')
process.exit(EXIT_CODES.MAX_TIME_REACHED)
})
} else {
await main({ sendJob, txHashes })
await main({ sendJob, txHash })
}
} else {
setTimeout(() => {
@ -114,37 +99,27 @@ function processEvents(events) {
}
}
async function main({ sendJob, txHashes }) {
if (config.id === 'erc-native-transfer') {
logger.debug('Getting token address to listen Transfer events')
const state = await getTokensState(bridgeContract, logger)
eventContract.options.address = state.bridgeableTokenAddress
}
async function main({ sendJob, txHash }) {
try {
const events = await getEventsFromTx({
web3,
contract: eventContract,
event: config.event,
txHash,
filter: config.eventFilter
})
logger.info(`Found ${events.length} ${config.event} events`)
logger.info(`Processing ${txHashes.length} input transactions`)
for (const txHash of txHashes) {
try {
logger.info({ txHash }, `Processing transaction`)
const events = await getEventsFromTx({
web3,
contract: eventContract,
event: config.event,
txHash,
filter: config.eventFilter
})
logger.info({ txHash }, `Found ${events.length} ${config.event} events`)
if (events.length) {
const job = await processEvents(events)
logger.info('Transactions to send:', job.length)
if (events.length) {
const job = await processEvents(events)
logger.info({ txHash }, 'Transactions to send:', job.length)
if (job.length) {
await sendJob(job)
}
if (job.length) {
await sendJob(job)
}
} catch (e) {
logger.error(e)
}
} catch (e) {
logger.error(e)
}
await connection.close()
@ -153,13 +128,9 @@ async function main({ sendJob, txHashes }) {
}
async function sendJobTx(jobs) {
const { web3 } = config.sender === 'foreign' ? config.foreign : config.home
await GasPrice.start(chain, web3, true)
const gasPriceOptions = GasPrice.gasPriceOptions()
const gasPrice = await GasPrice.start(config.chain, true)
const chainId = await getChainId(web3)
let nonce = await getNonce(web3, config.validatorAddress)
let nonce = await getNonce(web3, ORACLE_VALIDATOR_ADDRESS)
await syncForEach(jobs, async job => {
let gasLimit
@ -174,13 +145,13 @@ async function sendJobTx(jobs) {
const txHash = await sendTx({
data: job.data,
nonce,
value: '0',
gasPrice: gasPrice.toString(10),
amount: '0',
gasLimit,
privateKey: config.validatorPrivateKey,
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY,
to: job.to,
chainId,
web3,
gasPriceOptions
web3
})
nonce++
@ -196,8 +167,8 @@ async function sendJobTx(jobs) {
)
if (e.message.toLowerCase().includes('insufficient funds')) {
const currentBalance = await web3.eth.getBalance(config.validatorAddress)
const minimumBalance = gasLimit.multipliedBy(gasPriceOptions.gasPrice || gasPriceOptions.maxFeePerGas)
const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS)
const minimumBalance = gasLimit.multipliedBy(gasPrice)
logger.error(
`Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.`
)

View File

@ -15,7 +15,7 @@ async function estimateGas({ web3, homeBridge, validatorContract, message, addre
const gasEstimate = await homeBridge.methods.executeAffirmation(message).estimateGas({
from: address
})
const msgGasLimit = Math.ceil((parseAMBHeader(message).gasLimit * 64) / 63)
const msgGasLimit = parseAMBHeader(message).gasLimit
// message length in bytes
const len = strip0x(message).length / 2 - MIN_AMB_HEADER_LENGTH

View File

@ -4,6 +4,7 @@ const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError
const logger = require('../../services/logger').child({
module: 'processCollectedSignatures:estimateGas'
})
const { parseAMBHeader } = require('../../utils/message')
const web3 = new Web3()
const { toBN } = Web3.utils
@ -21,9 +22,15 @@ async function estimateGas({
address
}) {
try {
return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas({
from: address
})
const msgGasLimit = parseAMBHeader(message).gasLimit
// + estimateExtraGas(len)
// is not needed here, since estimateGas will already take into account gas
// needed for memory expansion, message processing, etc.
return gasEstimate + msgGasLimit
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e

View File

@ -1,183 +0,0 @@
require('dotenv').config()
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('../../services/HttpListProvider')
const { getValidatorContract } = require('../../tx/web3')
const rootLogger = require('../../services/logger')
const { signatureToVRS, packSignatures } = require('../../utils/message')
const { readAccessListFile, isRevertError } = require('../../utils/utils')
const { parseAMBMessage } = require('../../../../commons')
const estimateGas = require('../processAMBCollectedSignatures/estimateGas')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const { MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
const { ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, ORACLE_HOME_TO_FOREIGN_BLOCK_LIST } = process.env
const ORACLE_HOME_SKIP_MANUAL_LANE = process.env.ORACLE_HOME_SKIP_MANUAL_LANE === 'true'
function processCollectedSignaturesBuilder(config) {
const { home, foreign, mevForeign } = config
let validatorContract = null
return async function processCollectedSignatures(signatures) {
const txToSend = []
if (validatorContract === null) {
validatorContract = await getValidatorContract(foreign.bridgeContract, foreign.web3)
}
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
const callbacks = signatures
.map(colSignature => async () => {
const { messageHash, NumberOfCollectedSignatures } = colSignature.returnValues
const logger = rootLogger.child({
eventTransactionHash: colSignature.transactionHash
})
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
const message = await home.bridgeContract.methods.message(messageHash).call()
const parsedMessage = parseAMBMessage(message)
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST || ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) {
const sender = parsedMessage.sender.toLowerCase()
const executor = parsedMessage.executor.toLowerCase()
if (ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
const allowanceList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST, logger)
if (!allowanceList.includes(executor) && !allowanceList.includes(sender)) {
logger.info(
{ sender, executor },
'Validator skips a message. Neither sender nor executor addresses are in the allowance list.'
)
return
}
} else if (ORACLE_HOME_TO_FOREIGN_BLOCK_LIST) {
const blockList = await readAccessListFile(ORACLE_HOME_TO_FOREIGN_BLOCK_LIST, logger)
if (blockList.includes(executor)) {
logger.info({ executor }, 'Validator skips a message. Executor address is in the block list.')
return
}
if (blockList.includes(sender)) {
logger.info({ sender }, 'Validator skips a message. Sender address is in the block list.')
return
}
}
}
if (ORACLE_HOME_SKIP_MANUAL_LANE && parsedMessage.decodedDataType.manualLane) {
logger.info(
{ dataType: parsedMessage.dataType },
'Validator skips a message. Message was forwarded to the manual lane by the extension'
)
return
}
logger.debug({ NumberOfCollectedSignatures }, 'Number of signatures to get')
const requiredSignatures = []
requiredSignatures.length = NumberOfCollectedSignatures
requiredSignatures.fill(0)
const signaturesArray = []
const [v, r, s] = [[], [], []]
logger.debug('Getting message signatures')
const signaturePromises = requiredSignatures.map(async (el, index) => {
logger.debug({ index }, 'Getting message signature')
const signature = await home.bridgeContract.methods.signature(messageHash, index).call()
const vrs = signatureToVRS(signature)
v.push(vrs.v)
r.push(vrs.r)
s.push(vrs.s)
signaturesArray.push(vrs)
})
await Promise.all(signaturePromises)
const signatures = packSignatures(signaturesArray)
logger.info(`Processing messageId: ${parsedMessage.messageId}`)
let gasEstimate
try {
logger.debug('Estimate gas')
gasEstimate = await estimateGas({
foreignBridge: foreign.bridgeContract,
validatorContract,
v,
r,
s,
signatures,
message,
numberOfCollectedSignatures: NumberOfCollectedSignatures,
messageId: parsedMessage.messageId,
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof AlreadyProcessedError) {
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
return
} else if (e instanceof IncompatibleContractError || e instanceof InvalidValidatorError) {
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')
throw e
}
}
const executeData = foreign.bridgeContract.methods.executeSignatures(message, signatures).encodeABI()
const profit = await estimateProfit(
mevForeign.contract,
mevForeign.minGasPrice,
executeData,
mevForeign.flatMinerFee
)
if (profit === '0') {
logger.error('No MEV opportunity found when testing with min gas price, skipping job')
return
}
logger.info(`Estimated profit of ${profit} when simulating with ${mevForeign.minGasPrice} gas price`)
txToSend.push({
profit,
executeData,
data: mevForeign.contract.methods.execute(executeData).encodeABI(),
gasEstimate,
extraGas: EXTRA_GAS_ABSOLUTE,
maxFeePerGas: mevForeign.maxFeePerGas,
maxPriorityFeePerGas: mevForeign.maxPriorityFeePerGas,
transactionReference: colSignature.transactionHash,
to: mevForeign.contractAddress,
value: mevForeign.flatMinerFee
})
})
.map(promise => limit(promise))
await Promise.all(callbacks)
return txToSend
}
}
async function estimateProfit(contract, gasPrice, data, minerFee) {
return contract.methods
.estimateProfit(gasPrice, data)
.call({ value: minerFee })
.then(
res => res.toString(),
e => {
if (isRevertError(e)) {
return '0'
}
throw e
}
)
}
module.exports = {
processCollectedSignaturesBuilder,
estimateProfit
}

View File

@ -1,7 +1,6 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS, ASYNC_ETH_CALL_MAX_GAS_LIMIT } = require('../../../utils/constants')
const { zipToObject, isRevertError } = require('../../../utils/utils')
const { zipToObject } = require('../../../utils/utils')
const argTypes = {
to: 'address',
@ -18,23 +17,14 @@ function makeCall(argNames) {
const { blockNumber, ...opts } = zipToObject(argNames, args)
if (blockNumber && toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
return [false, '0x']
}
// different clients might use different default gas limits, so it makes sense to limit it by some large number
if (!opts.gas || toBN(opts.gas).gt(toBN(ASYNC_ETH_CALL_MAX_GAS_LIMIT))) {
opts.gas = ASYNC_ETH_CALL_MAX_GAS_LIMIT
}
return web3.eth
const [status, result] = await web3.eth
.call(opts, blockNumber || foreignBlock.number)
.then(result => [true, web3.eth.abi.encodeParameter('bytes', result)])
.catch(e => {
if (isRevertError(e)) {
return [false, ASYNC_CALL_ERRORS.REVERT]
}
throw e
})
.then(result => [true, result], err => [false, err.data])
return [status, web3.eth.abi.encodeParameter('bytes', result)]
}
}

View File

@ -1,7 +1,5 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const address = web3.eth.abi.decodeParameter('address', data)
@ -14,7 +12,7 @@ async function callArchive(web3, data, foreignBlock) {
const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data)
if (toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
return [false, '0x']
}
const balance = await web3.eth.getBalance(address, blockNumber)

View File

@ -1,4 +1,3 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeBlock } = require('./serializers')
async function call(web3, data, foreignBlock) {
@ -7,7 +6,7 @@ async function call(web3, data, foreignBlock) {
const block = await web3.eth.getBlock(blockHash)
if (block === null || block.number > foreignBlock.number) {
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
return [false, '0x']
}
return [true, serializeBlock(web3, block)]

View File

@ -1,13 +1,12 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeBlock } = require('./serializers')
async function call(web3, data, foreignBlock) {
const blockNumber = web3.eth.abi.decodeParameter('uint256', data)
if (toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
return [false, '0x']
}
const block = await web3.eth.getBlock(blockNumber)

View File

@ -1,7 +1,5 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const { 0: address, 1: slot } = web3.eth.abi.decodeParameters(['address', 'bytes32'], data)
@ -14,7 +12,7 @@ async function callArchive(web3, data, foreignBlock) {
const { 0: address, 1: slot, 2: blockNumber } = web3.eth.abi.decodeParameters(['address', 'bytes32', 'uint256'], data)
if (toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
return [false, '0x']
}
const value = await web3.eth.getStorageAt(address, slot, blockNumber)

View File

@ -1,4 +1,3 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeTx } = require('./serializers')
async function call(web3, data, foreignBlock) {
@ -7,7 +6,7 @@ async function call(web3, data, foreignBlock) {
const tx = await web3.eth.getTransaction(hash)
if (tx === null || tx.blockNumber > foreignBlock.number) {
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
return [false, '0x']
}
return [true, serializeTx(web3, tx)]

View File

@ -1,7 +1,5 @@
const { toBN } = require('web3').utils
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
async function call(web3, data, foreignBlock) {
const address = web3.eth.abi.decodeParameter('address', data)
@ -14,7 +12,7 @@ async function callArchive(web3, data, foreignBlock) {
const { 0: address, 1: blockNumber } = web3.eth.abi.decodeParameters(['address', 'uint256'], data)
if (toBN(blockNumber).gt(toBN(foreignBlock.number))) {
return [false, ASYNC_CALL_ERRORS.BLOCK_IS_IN_THE_FUTURE]
return [false, '0x']
}
const nonce = await web3.eth.getTransactionCount(address, blockNumber)

View File

@ -1,4 +1,3 @@
const { ASYNC_CALL_ERRORS } = require('../../../utils/constants')
const { serializeReceipt } = require('./serializers')
async function call(web3, data, foreignBlock) {
@ -7,7 +6,7 @@ async function call(web3, data, foreignBlock) {
const receipt = await web3.eth.getTransactionReceipt(hash)
if (receipt === null || receipt.blockNumber > foreignBlock.number) {
return [false, ASYNC_CALL_ERRORS.NOT_FOUND]
return [false, '0x']
}
return [true, serializeReceipt(web3, receipt)]

View File

@ -1,30 +1,15 @@
const { ZERO_ADDRESS } = require('../../../../../commons')
const serializeBlock = (web3, block) => {
const args = [
block.number,
block.hash,
block.miner,
block.gasUsed,
block.gasLimit,
block.parentHash,
block.receiptsRoot,
block.stateRoot,
block.transactionsRoot,
block.timestamp,
block.difficulty,
block.totalDifficulty
]
const type = '(uint256,bytes32,address,uint256,uint256,bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256)'
return web3.eth.abi.encodeParameter(type, args)
const args = [block.number, block.hash, block.miner]
const types = ['uint256', 'bytes32', 'address']
return web3.eth.abi.encodeParameters(types, args)
}
const serializeTx = (web3, tx) => {
const args = [
tx.hash,
tx.blockNumber,
tx.blockHash,
tx.transactionIndex,
tx.from,
tx.to || ZERO_ADDRESS,
tx.value,
@ -33,26 +18,16 @@ const serializeTx = (web3, tx) => {
tx.gasPrice,
tx.input
]
const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,uint256,uint256,uint256,bytes)'
return web3.eth.abi.encodeParameter(type, args)
const types = ['bytes32', 'uint256', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes']
return web3.eth.abi.encodeParameters(types, args)
}
const normalizeLog = log => [log.address, log.topics, log.data]
const serializeReceipt = (web3, receipt) => {
const args = [
receipt.transactionHash,
receipt.blockNumber,
receipt.blockHash,
receipt.transactionIndex,
receipt.from,
receipt.to || ZERO_ADDRESS,
receipt.gasUsed,
receipt.status,
receipt.logs.map(normalizeLog)
]
const type = '(bytes32,uint256,bytes32,uint256,address,address,uint256,bool,(address,bytes32[],bytes)[])'
return web3.eth.abi.encodeParameter(type, args)
const args = [receipt.transactionHash, receipt.blockNumber, receipt.status, receipt.logs.map(normalizeLog)]
const types = ['bytes32', 'uint256', 'bool', '(address,bytes32[],bytes)[]']
return web3.eth.abi.encodeParameters(types, args)
}
module.exports = {

View File

@ -6,16 +6,7 @@ const logger = require('../../services/logger').child({
const { strip0x } = require('../../../../commons')
const { AMB_AFFIRMATION_REQUEST_EXTRA_GAS_ESTIMATOR: estimateExtraGas } = require('../../utils/constants')
async function estimateGas({
web3,
homeBridge,
validatorContract,
messageId,
status,
result,
address,
homeBlockNumber
}) {
async function estimateGas({ web3, homeBridge, validatorContract, messageId, status, result, address }) {
try {
const gasEstimate = await homeBridge.methods.confirmInformation(messageId, status, result).estimateGas({
from: address
@ -24,8 +15,7 @@ async function estimateGas({
// message length in bytes
const len = strip0x(result).length / 2
let callbackGasLimit = parseInt(await homeBridge.methods.maxGasPerTx().call(), 10)
callbackGasLimit = Math.ceil((callbackGasLimit * 64) / 63)
const callbackGasLimit = parseInt(await homeBridge.methods.maxGasPerTx().call(), 10)
return gasEstimate + callbackGasLimit + estimateExtraGas(len)
} catch (e) {
@ -61,20 +51,6 @@ async function estimateGas({
throw new InvalidValidatorError(`${address} is not a validator`)
}
logger.debug('Check if InformationRetrieved event for this message already exists')
const logs = await homeBridge.getPastEvents('InformationRetrieved', {
fromBlock: homeBlockNumber,
toBlock: 'latest',
filter: { messageId }
})
if (logs.length > 0) {
logger.warn(
'This particular message was already signed and processed by other validators.' +
'However, evaluated async call result is different from the one recorded on-chain.'
)
throw new AlreadyProcessedError(e.message)
}
throw new Error('Unknown error while processing message')
}
}

View File

@ -4,13 +4,7 @@ const { soliditySha3 } = require('web3').utils
const { HttpListProviderError } = require('../../services/HttpListProvider')
const rootLogger = require('../../services/logger')
const makeBlockFinder = require('../../services/blockFinder')
const {
EXIT_CODES,
MAX_CONCURRENT_EVENTS,
EXTRA_GAS_ABSOLUTE,
ASYNC_CALL_ERRORS,
MAX_ASYNC_CALL_RESULT_LENGTH
} = require('../../utils/constants')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS, EXTRA_GAS_ABSOLUTE } = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const { getValidatorContract, getBlock, getBlockNumber, getRequiredBlockConfirmations } = require('../../tx/web3')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
@ -35,13 +29,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 +43,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,17 +79,12 @@ 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 => {
const [status, result] = await call(web3ForeignArchive, data, foreignClosestBlock).catch(e => {
if (e instanceof HttpListProviderError) {
throw e
}
logger.error({ error: e.message }, 'Unknown error during async call execution')
throw e
return [false, '0x']
})
if (result.length > 2 + MAX_ASYNC_CALL_RESULT_LENGTH * 2) {
status = false
result = ASYNC_CALL_ERRORS.RESULT_IS_TOO_LONG
}
logger.info({ requestSelector, method: asyncCallMethod, status, result }, 'Request result obtained')
let gasEstimate
@ -112,8 +97,7 @@ function processInformationRequestsBuilder(config) {
messageId,
status,
result,
address: config.validatorAddress,
homeBlockNumber: homeBlock.number
address: config.validatorAddress
})
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {

View File

@ -8,6 +8,8 @@ const estimateGas = require('../processSignatureRequests/estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
function processSignatureRequestsBuilder(config) {
@ -35,7 +37,7 @@ function processSignatureRequestsBuilder(config) {
const { sender, executor } = parseAMBMessage(message)
logger.info({ sender, executor }, `Processing signatureRequest ${messageId}`)
const signature = web3.eth.accounts.sign(message, config.validatorPrivateKey)
const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`)
let gasEstimate
try {

View File

@ -6,9 +6,11 @@ const logger = require('../../services/logger').child({
async function estimateGas({ web3, homeBridge, validatorContract, recipient, value, txHash, address }) {
try {
return await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({
const gasEstimate = await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({
from: address
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e

View File

@ -20,7 +20,8 @@ async function estimateGas({
signatures
}) {
try {
return await foreignBridge.methods.executeSignatures(message, signatures).estimateGas()
const gasEstimate = await foreignBridge.methods.executeSignatures(message, signatures).estimateGas()
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e

View File

@ -6,9 +6,10 @@ const logger = require('../../services/logger').child({
async function estimateGas({ web3, homeBridge, validatorContract, signature, message, address }) {
try {
return await homeBridge.methods.submitSignature(signature, message).estimateGas({
const gasEstimate = await homeBridge.methods.submitSignature(signature, message).estimateGas({
from: address
})
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {
throw e

View File

@ -8,6 +8,8 @@ const estimateGas = require('./estimateGas')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
function processSignatureRequestsBuilder(config) {
@ -46,7 +48,7 @@ function processSignatureRequestsBuilder(config) {
expectedMessageLength
})
const signature = web3.eth.accounts.sign(message, config.validatorPrivateKey)
const signature = web3.eth.accounts.sign(message, `0x${ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY}`)
let gasEstimate
try {

View File

@ -13,25 +13,12 @@ const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
function processTransfersBuilder(config) {
const { bridgeContract, web3 } = config.home
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature('UserRequestForAffirmation(address,uint256)')
const redeemHash = web3.eth.abi.encodeEventSignature('Redeem(address,uint256,uint256)')
const transferHash = web3.eth.abi.encodeEventSignature('Transfer(address,address,uint256)')
const foreignBridgeAddress = config.foreign.bridgeAddress
const decodeAddress = data => web3.eth.abi.decodeParameter('address', data)
const isUserRequestForAffirmation = e =>
e.address.toLowerCase() === foreignBridgeAddress.toLowerCase() && e.topics[0] === userRequestForAffirmationHash
const isRedeem = cTokenAddress => e =>
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
e.topics[0] === redeemHash &&
decodeAddress(e.data.slice(0, 66)).toLowerCase() === foreignBridgeAddress.toLowerCase()
const isCTokenTransfer = cTokenAddress => e =>
e.address.toLowerCase() === cTokenAddress.toLowerCase() &&
e.topics[0] === transferHash &&
decodeAddress(e.topics[1]).toLowerCase() === foreignBridgeAddress.toLowerCase() &&
decodeAddress(e.topics[2]).toLowerCase() === cTokenAddress.toLowerCase()
const userRequestForAffirmationAbi = config.foreign.bridgeABI.find(
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
)
const tokensSwappedAbi = config.foreign.bridgeABI.find(e => e.type === 'event' && e.name === 'TokensSwapped')
const userRequestForAffirmationHash = web3.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
const tokensSwappedHash = tokensSwappedAbi ? web3.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
let validatorContract = null
@ -45,35 +32,37 @@ function processTransfersBuilder(config) {
rootLogger.debug(`Processing ${transfers.length} Transfer events`)
const callbacks = transfers
.map(transfer => async () => {
const { from, to, value } = transfer.returnValues
const { from, value } = transfer.returnValues
const logger = rootLogger.child({
eventTransactionHash: transfer.transactionHash,
from,
to,
value
eventTransactionHash: transfer.transactionHash
})
logger.info('Processing transfer')
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
const receipt = await config.foreign.web3.eth.getTransactionReceipt(transfer.transactionHash)
if (receipt.logs.some(isUserRequestForAffirmation)) {
logger.info('Transfer event discarded because affirmation is detected in the same transaction')
const existsAffirmationEvent = receipt.logs.some(
e => e.address === config.foreign.bridgeAddress && e.topics[0] === userRequestForAffirmationHash
)
if (existsAffirmationEvent) {
logger.info(
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
transfer.transactionHash
}`
)
return
}
if (from === ZERO_ADDRESS) {
logger.info('Mint-like transfers from zero address are not processed')
return
}
const existsTokensSwappedEvent = tokensSwappedAbi
? receipt.logs.some(e => e.address === config.foreign.bridgeAddress && e.topics[0] === tokensSwappedHash)
: false
// when bridge performs a withdrawal from Compound, the following three events occur
// * token.Transfer(from=cToken, to=bridge, amount=X)
// * cToken.Redeem(redeemer=bridge, amount=X, tokens=Y)
// * cToken.Transfer(from=bridge, to=cToken, amount=Y)
if (receipt.logs.some(isRedeem(from)) && receipt.logs.some(isCTokenTransfer(from))) {
logger.info('Transfer event discarded because cToken redeem is detected in the same transaction')
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
logger.info(
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
)
return
}

Some files were not shown because too many files have changed in this diff Show More