Compare commits

..

24 Commits

Author SHA1 Message Date
Alexander Kolotov
0451d6e373 Merge the develop branch to the master branch, preparation to v2.6.0 2021-01-11 20:17:53 -06:00
Kirill Fedoseev
409044b8a5 Add env variables for selective validator balance checks (#507) 2021-01-10 20:18:06 -06:00
Kirill Fedoseev
5fc52f42d7 ALM: fix blocking Blockscout/Etherscan requests (#505) 2021-01-07 13:35:23 -06:00
Alexander Kolotov
8a0d9f38b0 Added mediator endpoint in the monitor web service (#503) 2020-12-24 19:15:51 +03:00
Alexander Kolotov
1aee0a84ef Merge the develop branch to the master branch, preparation to v2.6.0-rc3 2020-12-22 10:58:25 +03:00
Alexander Kolotov
811b1a27f1 setLogger added to the RedundantHttpListProvider (#501) 2020-12-21 22:16:37 +03:00
Kirill Fedoseev
4d468ae107 Detect all AMB mediators in monitor (#493) 2020-12-20 01:19:49 +03:00
Kirill Fedoseev
4497a024b1 Fix RPC urls in the ultimate tests (#498) 2020-12-20 01:15:46 +03:00
Kirill Fedoseev
6ce98ff3dd Fetch signatures from RPC endpoint immediatly after enough signatures are collected (#499) 2020-12-20 01:13:49 +03:00
Kirill Fedoseev
04f66b243c Handle RPC error about ancient blocks (#500) 2020-12-20 01:10:47 +03:00
Kirill Fedoseev
21581b3c01 Change message status when signatures are manually submitted (#497) 2020-12-07 23:12:30 +03:00
Kirill Fedoseev
bbc68f9fa2 ALM manual signatures execution (#471) 2020-11-26 00:22:21 +03:00
Kirill Fedoseev
5327688a20 Handle JSONRPC error codes in web3 providers (#491) 2020-11-12 02:20:03 +03:00
Alexander Kolotov
1122daf9a1 Merge the develop branch to the master branch, preparation to v2.6.0-rc2 2020-11-08 15:35:47 +03:00
Kirill Fedoseev
12269d7426 Remove access lists tests from ultimate test suites (#488) 2020-11-08 01:11:21 +03:00
Alexander Kolotov
683fa0728d Update the contract's submodule to the release 5.5.0 (#486) 2020-11-07 16:22:53 +03:00
Alexander Kolotov
dd2075c351 Security Audit report by Quantstamp added (#485) 2020-11-07 13:15:35 +03:00
Kirill Fedoseev
ce29b95729 Support AMB manual lane and allowance/block lists in the monitor (#484) 2020-11-04 22:16:43 +03:00
Kirill Fedoseev
eb1069497a Cache fetched events in monitor (#482) 2020-11-04 14:24:42 +03:00
Kirill Fedoseev
0228fc7d5f Support of manual lane in the AMB oracle (#483) 2020-10-31 21:02:56 +03:00
Kirill Fedoseev
f8d85b14de Add allowance/block lists support to monitor (#477) 2020-10-29 11:25:43 +03:00
Kirill Fedoseev
5fa9d21246 Fix set-env deprecation warnings (#478) 2020-10-28 15:35:11 +03:00
Kirill Fedoseev
611b8c539d Improve workflow with several RPC URLs in oracle (#476) 2020-10-28 14:20:50 +03:00
Kirill Fedoseev
389cea3c39 Reduce time complexity of events comparison in monitor (#475) 2020-10-23 13:48:37 +03:00
132 changed files with 3297 additions and 2485 deletions

View File

@@ -8,11 +8,16 @@
**/docs
**/*.md
monitor/**/*.env*
oracle/**/*.env*
!**/.env.example
contracts/test
contracts/build
oracle/test
monitor/test
monitor/responses
monitor/cache/*
commons/test
oracle/**/*.png
oracle/**/*.jpg

View File

@@ -19,9 +19,11 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: true
- run: git submodule status > submodule.status
- id: get_cache_key
run: echo "::set-output name=cache_key::cache-repo-${{ hashFiles('yarn.lock', 'package.json', 'submodule.status') }}"
- name: Set cache key
id: get_cache_key
run: |
git submodule status > submodule.status
echo "::set-output name=cache_key::cache-repo-${{ hashFiles('yarn.lock', 'package.json', 'submodule.status') }}"
- uses: actions/cache@v2
id: cache-repo
with:
@@ -29,7 +31,8 @@ jobs:
**/node_modules
contracts/build
key: ${{ steps.get_cache_key.outputs.cache_key }}
- if: ${{ !steps.cache-repo.outputs.cache-hit }}
- name: Install dependencies and compile contracts
if: ${{ !steps.cache-repo.outputs.cache-hit }}
run: |
yarn install --frozen-lockfile
yarn run install:deploy
@@ -56,7 +59,8 @@ jobs:
**/node_modules
contracts/build
key: ${{ needs.initialize.outputs.cache_key }}
- run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
- name: yarn run ${{ matrix.task }}
run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
ui-coverage:
runs-on: ubuntu-latest
needs:
@@ -76,7 +80,8 @@ jobs:
**/node_modules
contracts/build
key: ${{ needs.initialize.outputs.cache_key }}
- run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn workspace ui run coverage
- name: yarn workspace ui run coverage
run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn workspace ui run coverage
- uses: coverallsapp/github-action@master
with:
github-token: ${{ github.token }}
@@ -87,14 +92,16 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: true
- run: |
- name: Evaluate e2e docker images tags
run: |
git submodule status > submodule.status
echo "::set-env name=E2E_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}"
echo "::set-env name=ORACLE_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}"
echo "::set-env name=MONITOR_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}"
echo "::set-env name=UI_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}"
echo "::set-env name=ALM_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}"
- run: |
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- name: Rebuild and push updated images
run: |
function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
@@ -119,8 +126,10 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: true
- run: echo "::set-env name=MOLECULE_RUNNER_TAG::${{ hashFiles('./deployment-e2e/Dockerfile') }}"
- run: |
- name: Evaluate e2e molecule runner tag
run: echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
- name: Rebuild and push molecule runner e2e image
run: |
function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
}
@@ -150,14 +159,15 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: true
- run: |
- name: Evaluate e2e docker images tags
run: |
git submodule status > submodule.status
echo "::set-env name=E2E_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}"
echo "::set-env name=ORACLE_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}"
echo "::set-env name=MONITOR_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}"
echo "::set-env name=UI_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}"
echo "::set-env name=ALM_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}"
- if: ${{ matrix.use-cache }}
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- if: ${{ matrix.use-cache }}
uses: actions/cache@v2
id: cache-repo
with:
@@ -165,11 +175,14 @@ jobs:
**/node_modules
contracts/build
key: ${{ needs.initialize.outputs.cache_key }}
- run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
- name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: yarn run ${{ matrix.task }}
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
deployment:
runs-on: ubuntu-latest
needs:
- build-e2e-images
- build-molecule-runner
strategy:
fail-fast: false
@@ -179,15 +192,17 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: true
- run: echo "::set-env name=MOLECULE_RUNNER_TAG::${{ hashFiles('./deployment-e2e/Dockerfile') }}"
- run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: Evaluate e2e molecule runner tag
run: echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
- name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- run: deployment-e2e/molecule.sh ${{ matrix.task }}
ultimate:
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
runs-on: ubuntu-latest
needs:
- initialize
- build-e2e-images
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
strategy:
fail-fast: false
matrix:
@@ -195,24 +210,29 @@ jobs:
include:
- task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC'
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE'
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC'
ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
steps:
- uses: actions/checkout@v2
with:
submodules: true
- run: |
- name: Evaluate e2e docker images tags
run: |
git submodule status > submodule.status
echo "::set-env name=E2E_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}"
echo "::set-env name=ORACLE_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}"
echo "::set-env name=MONITOR_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}"
echo "::set-env name=UI_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}"
echo "::set-env name=ALM_TAG::${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}"
echo "::set-env name=MOLECULE_RUNNER_TAG::${{ hashFiles('./deployment-e2e/Dockerfile') }}"
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
- uses: actions/cache@v2
id: cache-repo
with:
@@ -220,12 +240,32 @@ jobs:
**/node_modules
contracts/build
key: ${{ needs.initialize.outputs.cache_key }}
- run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
- run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
- run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- run: sudo chown -R $USER:docker /var/run/docker.sock
- 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 blocks
- name: Pull e2e oracle image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
- if: ${{ matrix.ui-e2e-grep }}
name: Pull e2e ui image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull ui
docker build \
--build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
--build-arg UI_TAG=${UI_TAG} \
--build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
-f ./e2e-commons/Dockerfile.ui \
-t ui_ui:latest \
.
- name: Deploy oracle and ui
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- name: Reset docker socket permissions
run: sudo chown -R $USER:docker /var/run/docker.sock
- name: Run ui e2e tests
if: ${{ matrix.ui-e2e-grep }}
run: cd ui-e2e && xvfb-run yarn mocha -g "${{ matrix.ui-e2e-grep }}" -b ./test.js
- if: ${{ !matrix.ui-e2e-grep }}
- name: Run oracle e2e tests
if: ${{ !matrix.ui-e2e-grep }}
run: docker-compose -f ./e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run ${{ matrix.task }}

13
.gitignore vendored
View File

@@ -10,11 +10,8 @@ dist
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
*.env*
!.env.example
.idea
.nyc_output
logs/
@@ -49,5 +46,9 @@ __pycache__
#monitor
monitor/responses/*
monitor/configs/*.env
monitor/cache/*
!monitor/cache/.gitkeep
!monitor/.gitkeep
# Local Netlify folder
.netlify

View File

@@ -41,6 +41,7 @@ ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST | Filename with a list of addresses, separ
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST | Filename with a list of addresses, separated by newlines. If set, determines the blocked set of accounts whose requests will not be automatically processed by the CollectedSignatures watcher. Has a lower priority than the `ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST` | string
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
ORACLE_ALWAYS_RELAY_SIGNATURES | If set to `true`, the oracle will always relay signatures even if it was not the last who finilized the signatures collecting process. The default is `false`. | `true` / `false`
ORACLE_RPC_REQUEST_TIMEOUT | Timeout in milliseconds for a single RPC request. Default value is `ORACLE_*_RPC_POLLING_INTERVAL * 2`. | integer
## UI configuration
@@ -78,3 +79,8 @@ MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or
MONITOR_PORT | The port for the Monitor. | integer
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs | `true` / `false`
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
@@ -15,6 +16,8 @@
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3",
"@web3-react/core": "^6.1.1",
"@web3-react/injected-connector": "^6.0.7",
"customize-cra": "^1.0.0",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
@@ -27,8 +30,9 @@
"react-scripts": "3.0.1",
"styled-components": "^5.1.1",
"typescript": "^3.5.2",
"web3": "1.2.7",
"web3-eth-contract": "1.2.7"
"web3": "1.2.11",
"web3-eth-contract": "1.2.11",
"web3-utils": "1.2.11"
},
"scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",

1
alm/public/_redirects Normal file
View File

@@ -0,0 +1 @@
/* /index.html 200

View File

@@ -1,14 +1,18 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { Web3ReactProvider } from '@web3-react/core'
import Web3 from 'web3'
import { MainPage } from './components/MainPage'
import { StateProvider } from './state/StateProvider'
function App() {
return (
<BrowserRouter>
<StateProvider>
<MainPage />
</StateProvider>
<Web3ReactProvider getLibrary={provider => new Web3(provider)}>
<StateProvider>
<MainPage />
</StateProvider>
</Web3ReactProvider>
</BrowserRouter>
)
}

View File

@@ -49,7 +49,16 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
} = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({
const {
confirmations,
status,
executionData,
signatureCollected,
waitingBlocksResolved,
setExecutionData,
executionEventsFetched,
setPendingExecution
} = useMessageConfirmations({
message,
receipt,
fromHome,
@@ -102,7 +111,17 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved}
/>
{signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />}
{signatureCollected && (
<ExecutionConfirmation
messageData={message.data}
executionData={executionData}
isHome={!fromHome}
signatureCollected={signatureCollected}
setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched}
setPendingExecution={setPendingExecution}
/>
)}
</StyledConfirmationContainer>
</div>
)

View File

@@ -1,24 +1,45 @@
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton'
const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
`
export interface ExecutionConfirmationParams {
messageData: string
executionData: ExecutionData
setExecutionData: Function
signatureCollected: boolean | string[]
isHome: boolean
executionEventsFetched: boolean
setPendingExecution: Function
}
export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => {
export const ExecutionConfirmation = ({
messageData,
executionData,
setExecutionData,
signatureCollected,
isHome,
executionEventsFetched,
setPendingExecution
}: ExecutionConfirmationParams) => {
const availableManualExecution =
!isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
executionEventsFetched &&
!!executionData.validator))
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -48,26 +69,47 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
<table>
<Thead>
<tr>
<th>Executed by</th>
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
<th className="text-center">Status</th>
<th className="text-center">Age</th>
{!requiredManualExecution && <th className="text-center">Age</th>}
{availableManualExecution && <th className="text-center">Actions</th>}
</tr>
</Thead>
<tbody>
<tr>
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
<td>
{requiredManualExecution ? (
'Manual user action is required to complete the operation'
) : formattedValidator ? (
formattedValidator
) : (
SEARCHING_TX
<SimpleLoading />
)}
</AgeTd>
</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
{!requiredManualExecution && (
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">
{formatTimestamp(executionData.timestamp)}
</ExplorerTxLink>
) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
''
) : (
SEARCHING_TX
)}
</AgeTd>
)}
{availableManualExecution && (
<td>
<ManualExecutionButton
messageData={messageData}
setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]}
setPendingExecution={setPendingExecution}
/>
</td>
)}
</tr>
</tbody>
</table>

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

@@ -0,0 +1,109 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from '@web3-react/core'
import { INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures'
const StyledButton = styled.button`
color: var(--button-color);
border-color: var(--font-color);
margin-top: 10px;
&:focus {
outline: var(--button-color);
}
`
interface ManualExecutionButtonParams {
messageData: string
setExecutionData: Function
signatureCollected: string[]
setPendingExecution: Function
}
export const ManualExecutionButton = ({
messageData,
setExecutionData,
signatureCollected,
setPendingExecution
}: ManualExecutionButtonParams) => {
const { foreign, setError } = useStateProvider()
const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false)
useEffect(
() => {
if (!manualExecution || !foreign.chainId) return
if (!active) {
activate(new InjectedConnector({ supportedChainIds: [foreign.chainId] }), e => {
if (e.message.includes('Unsupported chain id')) {
setError(INCORRECT_CHAIN_ERROR)
const { ethereum } = window as any
// remove the error message after chain is correctly changed to the foreign one
const listener = (chainId: string) => {
if (parseInt(chainId.slice(2), 16) === foreign.chainId) {
ethereum.removeListener('chainChanged', listener)
setError((error: string) => (error === INCORRECT_CHAIN_ERROR ? '' : error))
}
}
ethereum.on('chainChanged', listener)
} else {
setError(e.message)
}
setManualExecution(false)
})
return
}
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false)
library.eth
.sendTransaction({
from: account,
to: foreign.bridgeAddress,
data
})
.on('transactionHash', (txHash: string) => {
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
validator: account,
txHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
setPendingExecution(true)
})
.on('error', (e: Error) => setError(e.message))
},
[
manualExecution,
library,
activate,
active,
account,
foreign.chainId,
foreign.bridgeAddress,
foreign.bridgeContract,
setError,
messageData,
signatureCollected,
setExecutionData,
setPendingExecution
]
)
return (
<div className="is-center">
<StyledButton className="button outline" onClick={() => setManualExecution(true)}>
Execute
</StyledButton>
</div>
)
}

View File

@@ -1,6 +1,6 @@
import React from 'react'
export const CloseIcon = () => (
export const CloseIcon = ({ color }: { color?: string }) => (
<svg
aria-hidden="true"
focusable="false"
@@ -10,12 +10,9 @@ export const CloseIcon = () => (
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 352 512"
fill="#1890ff"
fill={color || '#1890ff'}
height="1em"
>
<path
fill="#1890ff"
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
/>
<path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
</svg>
)

View File

@@ -0,0 +1,31 @@
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(--failed-color);
border-radius: 4px;
margin-bottom: 20px;
padding-top: 10px;
`
const CloseIconContainer = styled.div`
cursor: pointer;
`
const TextContainer = styled.div`
flex-direction: column;
`
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">{error}</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)

View File

@@ -1,6 +1,6 @@
import React from 'react'
export const InfoIcon = () => (
export const InfoIcon = ({ color }: { color?: string }) => (
<svg
className="col-1 is-left"
viewBox="64 64 896 896"
@@ -8,7 +8,7 @@ export const InfoIcon = () => (
data-icon="info-circle"
width="1em"
height="1em"
fill="#1890ff"
fill={color || '#1890ff'}
aria-hidden="true"
>
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z" />

View File

@@ -13,6 +13,9 @@ export const FOREIGN_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_FO
export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER_API || ''
export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
export const ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION: boolean =
(process.env.REACT_APP_ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION || '').toLowerCase() === 'true'
export const HOME_RPC_POLLING_INTERVAL: number = 5000
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
export const BLOCK_RANGE: number = 50
@@ -61,3 +64,5 @@ export const VALIDATOR_CONFIRMATION_STATUS = {
}
export const SEARCHING_TX = 'Searching Transaction...'
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`

View File

@@ -1,4 +1,6 @@
// %t will be replaced by the time -> x minutes/hours/days ago
import { ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from './constants'
export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = {
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
SUCCESS_ONE_MESSAGE: 'was initiated %t',
@@ -24,7 +26,7 @@ export const CONFIRMATIONS_STATUS_LABEL_HOME: { [key: string]: string } = {
SUCCESS_MESSAGE_FAILED: 'Success',
EXECUTION_FAILED: 'Execution failed',
EXECUTION_PENDING: 'Execution pending',
EXECUTION_WAITING: 'Execution waiting',
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION ? 'Manual execution waiting' : 'Execution waiting',
FAILED: 'Confirmation Failed',
PENDING: 'Confirmation Pending',
WAITING_VALIDATORS: 'Confirmation Waiting',
@@ -55,11 +57,12 @@ export const CONFIRMATIONS_STATUS_DESCRIPTION_HOME: { [key: string]: string } =
SUCCESS_MESSAGE_FAILED:
'The specified transaction was included in a block,\nthe validators collected signatures and the cross-chain relay was executed correctly,\nbut the contained message execution failed.\nContact the support of the application you used to produce the transaction for the clarifications.',
EXECUTION_FAILED:
'The specified transaction was included in a block\nand the validators collected signatures. The\nvalidators transaction with collected signatures was\nsent but did not succeed. Contact to the validators by messaging\non %linkhttps://forum.poa.network/c/support',
'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but did not succeed. Contact to the validators by messaging\non %linkhttps://forum.poa.network/c/support',
EXECUTION_PENDING:
'The specified transaction was included in a block\nand the validators collected signatures. The\nvalidators transaction with collected signatures was\nsent but is not yet added to a block.',
EXECUTION_WAITING:
'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks. If the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
'The specified transaction was included in a block\nand the validators collected signatures. The\n transaction with collected signatures was\nsent but is not yet added to a block.',
EXECUTION_WAITING: ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
? 'The specified transaction was included in a block\nand the validators collected signatures.\nNow the manual user action is required to complete message execution.\n Please, press the "Execute" button.'
: 'The specified transaction was included in a block\nand the validators collected signatures. Either\n1. One of the validators is waiting for chain finalization.\n2. A validator skipped its duty to relay signatures.\n3. The execution transaction is still pending (e.g. due to the gas price spike).\nCheck status again after a few blocks or force execution by pressing the "Execute" button.\nIf the issue still persists contact to the validators by messaging on %linkhttps://forum.poa.network/c/support',
FAILED:
'The specified transaction was included in a block,\nbut transactions with signatures sent by a majority of\nvalidators failed. The cross-chain relay request will\nnot be processed. Contact to the validators by\nmessaging on %linkhttps://forum.poa.network/c/support',
PENDING:

View File

@@ -3,7 +3,6 @@ import { TransactionReceipt } from 'web3-eth'
import { MessageObject } from '../utils/web3'
import { useEffect, useState } from 'react'
import { EventData } from 'web3-eth-contract'
import { getAffirmationsSigned, getMessagesSigned } from '../utils/contract'
import {
BLOCK_RANGE,
CONFIRMATIONS_STATUS,
@@ -63,11 +62,12 @@ export const useMessageConfirmations = ({
blockConfirmations
}: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider()
const [confirmations, setConfirmations] = useState<Array<ConfirmationParam>>([])
const [confirmations, setConfirmations] = useState([])
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
const [waitingBlocks, setWaitingBlocks] = useState(false)
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
const [signatureCollected, setSignatureCollected] = useState(false)
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
const [executionData, setExecutionData] = useState<ExecutionData>({
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
@@ -93,7 +93,7 @@ export const useMessageConfirmations = ({
// Check if the validators are waiting for block confirmations to verify the message
useEffect(
() => {
if (!receipt || !blockConfirmations) return
if (!receipt || !blockConfirmations || waitingBlocksResolved) return
const subscriptions: Array<number> = []
@@ -126,7 +126,16 @@ export const useMessageConfirmations = ({
blockProvider.stop()
}
},
[blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt]
[
blockConfirmations,
foreign.web3,
fromHome,
validatorList,
home.web3,
receipt,
setConfirmations,
waitingBlocksResolved
]
)
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
@@ -173,6 +182,7 @@ export const useMessageConfirmations = ({
useEffect(
() => {
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return
if (waitingBlocksForExecutionResolved) return
const subscriptions: Array<number> = []
@@ -201,7 +211,7 @@ export const useMessageConfirmations = ({
homeBlockNumberProvider.stop()
}
},
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt]
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved]
)
// Checks if validators verified the message
@@ -218,14 +228,12 @@ export const useMessageConfirmations = ({
})
}
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
getConfirmationsForTx(
message.data,
home.web3,
validatorList,
home.bridgeContract,
confirmationContractMethod,
fromHome,
setConfirmations,
requiredSignatures,
setSignatureCollected,
@@ -251,7 +259,8 @@ export const useMessageConfirmations = ({
home.bridgeContract,
requiredSignatures,
waitingBlocksResolved,
timestamp
timestamp,
setConfirmations
]
)
@@ -289,7 +298,8 @@ export const useMessageConfirmations = ({
getExecutionFailedTransactionForMessage,
setFailedExecution,
getExecutionPendingTransactionsForMessage,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
return () => {
@@ -369,6 +379,9 @@ export const useMessageConfirmations = ({
status,
signatureCollected,
executionData,
waitingBlocksResolved
setExecutionData,
waitingBlocksResolved,
executionEventsFetched,
setPendingExecution
}
}

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

@@ -9,7 +9,7 @@ import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
jest.mock('../validatorConfirmationHelpers')
const getValidatorSuccessTransaction = helpers.getValidatorSuccessTransaction as jest.Mock<any>
const getSuccessExecutionTransaction = helpers.getSuccessExecutionTransaction as jest.Mock<any>
const getValidatorConfirmation = helpers.getValidatorConfirmation as jest.Mock<any>
const getValidatorFailedTransaction = helpers.getValidatorFailedTransaction as jest.Mock<any>
const getValidatorPendingTransaction = helpers.getValidatorPendingTransaction as jest.Mock<any>
@@ -24,8 +24,15 @@ const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3]
const bridgeContract = {} as Contract
const confirmationContractMethod = () => {}
const signature =
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4'
const bridgeContract = {
methods: {
signature: () => ({
call: () => signature
})
}
} as Contract
const requiredSignatures = 2
const waitingBlocksResolved = true
let subscriptions: Array<number> = []
@@ -42,7 +49,7 @@ const unsubscribe = () => {
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
getValidatorSuccessTransaction.mockClear()
getSuccessExecutionTransaction.mockClear()
getValidatorConfirmation.mockClear()
getValidatorFailedTransaction.mockClear()
getValidatorPendingTransaction.mockClear()
@@ -54,7 +61,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '',
@@ -83,7 +90,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -102,9 +109,10 @@ describe('getConfirmationsForTx', () => {
expect(subscriptions.length).toEqual(1)
expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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)
@@ -114,14 +122,16 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual(
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
@@ -134,7 +144,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '',
@@ -163,7 +173,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -179,9 +189,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(1)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -198,7 +208,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 ? '0x123' : '',
@@ -227,7 +237,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -244,11 +254,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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)
@@ -258,14 +269,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual(
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
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 },
@@ -283,7 +304,7 @@ describe('getConfirmationsForTx', () => {
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
@@ -315,7 +336,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -332,11 +353,12 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).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)
@@ -346,7 +368,27 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual(
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ 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 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
@@ -354,7 +396,7 @@ describe('getConfirmationsForTx', () => {
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
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 },
@@ -372,7 +414,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '',
@@ -407,7 +449,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -423,9 +465,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -437,14 +479,32 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setResult.mock.calls[0][0]()).toEqual(
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, 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 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
])
)
expect(setResult.mock.calls[1][0]).toEqual(
expect(res4).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -461,7 +521,7 @@ describe('getConfirmationsForTx', () => {
validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '',
@@ -493,7 +553,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -510,9 +570,9 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(subscriptions.length).toEqual(0)
expect(setResult).toBeCalledTimes(2)
expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
@@ -524,14 +584,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
expect(setResult.mock.calls[0][0]()).toEqual(
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ 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(setResult.mock.calls[1][0]).toEqual(
expect(res3).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
@@ -555,7 +625,7 @@ describe('getConfirmationsForTx', () => {
status:
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
}))
getValidatorSuccessTransaction
getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
@@ -604,57 +674,7 @@ describe('getConfirmationsForTx', () => {
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
timestamp,
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations,
getSuccessTransactions
)
unsubscribe()
expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getValidatorSuccessTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1)
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setResult.mock.calls[0][0]()).toEqual(
expect.arrayContaining([
{ 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(setResult.mock.calls[1][0]).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
])
)
await getConfirmationsForTx(
messageData,
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
true,
setResult,
requiredSignatures,
setSignatureCollected,
@@ -671,11 +691,80 @@ describe('getConfirmationsForTx', () => {
unsubscribe()
expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1)
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
expect(setPendingConfirmations).toBeCalledTimes(1)
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
const res1 = setResult.mock.calls[0][0]()
const res2 = setResult.mock.calls[1][0](res1)
const res3 = setResult.mock.calls[2][0](res2)
const res4 = setResult.mock.calls[3][0](res3)
expect(res1).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
])
)
expect(res2).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, 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 },
{ 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, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
])
)
await getConfirmationsForTx(
messageData,
web3,
validatorList,
bridgeContract,
true,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
timestamp,
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations,
getSuccessTransactions
)
unsubscribe()
expect(setResult).toBeCalledTimes(7)
expect(getValidatorConfirmation).toBeCalledTimes(2)
expect(getValidatorSuccessTransaction).toBeCalledTimes(2)
expect(setSignatureCollected).toBeCalledTimes(2)
expect(getSuccessExecutionTransaction).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)
@@ -687,14 +776,24 @@ describe('getConfirmationsForTx', () => {
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false)
expect(setResult.mock.calls[2][0]()).toEqual(
const res5 = setResult.mock.calls[4][0](res4)
const res6 = setResult.mock.calls[5][0](res5)
const res7 = setResult.mock.calls[6][0](res6)
expect(res5).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
])
)
expect(setResult.mock.calls[3][0]).toEqual(
expect(res6).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
])
)
expect(res7).toEqual(
expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },

View File

@@ -61,6 +61,7 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
contract,
@@ -76,7 +77,8 @@ describe('getFinalizationEvent', () => {
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()
@@ -110,6 +112,7 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn()
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
contract,
@@ -125,7 +128,8 @@ describe('getFinalizationEvent', () => {
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()
@@ -159,6 +163,7 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
contract,
@@ -174,7 +179,8 @@ describe('getFinalizationEvent', () => {
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()
@@ -208,6 +214,7 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([{ hash: txHash }])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
contract,
@@ -223,7 +230,8 @@ describe('getFinalizationEvent', () => {
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()
@@ -264,6 +272,7 @@ describe('getFinalizationEvent', () => {
const setFailedExecution = jest.fn()
const getPendingExecution = jest.fn().mockResolvedValue([])
const setPendingExecution = jest.fn()
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
contract,
@@ -279,7 +288,8 @@ describe('getFinalizationEvent', () => {
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
)
unsubscribe()

View File

@@ -15,13 +15,19 @@ export const checkWaitingBlocksForExecution = async (
const currentBlock = blockProvider.get()
if (currentBlock && currentBlock >= targetBlock) {
setExecutionData({
const undefinedExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
})
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? undefinedExecutionState
: data
)
setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false)
blockProvider.stop()
@@ -31,13 +37,20 @@ export const checkWaitingBlocksForExecution = async (
nextInterval = 500
} else {
setWaitingBlocksForExecution(true)
setExecutionData({
const waitingExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
})
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? waitingExecutionState
: data
)
}
const timeoutId = setTimeout(
() =>

View File

@@ -7,20 +7,37 @@ import {
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
import {
getValidatorConfirmation,
getValidatorFailedTransaction,
getValidatorPendingTransaction,
getValidatorSuccessTransaction
getSuccessExecutionTransaction
} from './validatorConfirmationHelpers'
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => {
const confirmations = [...oldConfirmations]
newConfirmations.forEach(validatorData => {
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
const currentStatus = confirmations[index].status
const newStatus = validatorData.status
if (
(validatorData as ConfirmationParam).txHash ||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
) {
confirmations[index] = validatorData
}
})
return confirmations
}
export const getConfirmationsForTx = async (
messageData: string,
web3: Maybe<Web3>,
validatorList: string[],
bridgeContract: Maybe<Contract>,
confirmationContractMethod: Function,
fromHome: boolean,
setResult: Function,
requiredSignatures: number,
setSignatureCollected: Function,
@@ -35,48 +52,47 @@ export const getConfirmationsForTx = async (
) => {
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
// If all the information was not collected, then it should retry
let shouldRetry = false
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const hashMsg = web3.utils.soliditySha3Raw(messageData)
let validatorConfirmations = await Promise.all(
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
)
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
setResult((prevConfirmations: ConfirmationParam[]) => {
if (prevConfirmations && prevConfirmations.length) {
successConfirmations.forEach(validatorData => {
const index = prevConfirmations.findIndex(e => e.validator === validatorData.validator)
validatorConfirmations[index] = validatorData
})
return prevConfirmations
} else {
return validatorConfirmations
const updateConfirmations = (confirmations: BasicConfirmationParam[]) => {
if (confirmations.length === 0) {
return
}
})
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
setResult((currentConfirmations: BasicConfirmationParam[]) => {
if (currentConfirmations && currentConfirmations.length) {
return mergeConfirmations(currentConfirmations, confirmations)
}
return confirmations
})
}
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
updateConfirmations(validatorConfirmations)
setSignatureCollected(hasEnoughSignatures)
// If signatures not collected, look for pending transactions
let pendingConfirmationsResult = false
if (successConfirmations.length !== requiredSignatures) {
if (!hasEnoughSignatures) {
// Check if confirmation is pending
const validatorPendingConfirmationsChecks = await Promise.all(
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
)
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
)
validatorPendingConfirmations.forEach(validatorData => {
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
validatorConfirmations[index] = validatorData
})
if (validatorPendingConfirmations.length > 0) {
pendingConfirmationsResult = true
}
updateConfirmations(validatorPendingConfirmations)
setPendingConfirmations(validatorPendingConfirmations.length > 0)
} else {
setPendingConfirmations(false)
}
const undefinedConfirmations = validatorConfirmations.filter(
@@ -84,7 +100,6 @@ export const getConfirmationsForTx = async (
)
// Check if confirmation failed
let failedConfirmationsResult = false
const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
@@ -93,68 +108,47 @@ export const getConfirmationsForTx = async (
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
)
validatorFailedConfirmations.forEach(validatorData => {
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
validatorConfirmations[index] = validatorData
})
const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures
if (messageConfirmationsFailed) {
failedConfirmationsResult = true
}
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
updateConfirmations(validatorFailedConfirmations)
const missingConfirmations = validatorConfirmations.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
)
if (successConfirmations.length !== requiredSignatures && missingConfirmations.length > 0) {
shouldRetry = true
}
let signatureCollectedResult = false
if (successConfirmations.length === requiredSignatures) {
if (hasEnoughSignatures) {
// 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
}))
updateConfirmations(notRequiredConfirmations)
notRequiredConfirmations.forEach(validatorData => {
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
validatorConfirmations[index] = validatorData
})
signatureCollectedResult = true
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(
validatorConfirmations
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
.map(getValidatorSuccessTransaction(bridgeContract, messageData, timestamp, getSuccessTransactions))
successConfirmations.map(
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions)
)
)
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
updateConfirmations(successConfirmationWithTxFound)
const updatedValidatorConfirmations = [...validatorConfirmations]
if (successConfirmationWithTxFound.length > 0) {
successConfirmationWithTxFound.forEach(validatorData => {
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator)
updatedValidatorConfirmations[index] = validatorData
})
}
// Set results
setResult(updatedValidatorConfirmations)
setFailedConfirmations(failedConfirmationsResult)
setPendingConfirmations(pendingConfirmationsResult)
setSignatureCollected(signatureCollectedResult)
// Retry if not all transaction were found for validator confirmations
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) {
shouldRetry = true
}
if (shouldRetry) {
// retry if not all signatures are collected and some confirmations are still missing
// or some success transactions were not fetched successfully
if (
(!hasEnoughSignatures && missingConfirmations.length > 0) ||
successConfirmationWithTxFound.length < successConfirmationWithData.length
) {
const timeoutId = setTimeout(
() =>
getConfirmationsForTx(
@@ -162,7 +156,7 @@ export const getConfirmationsForTx = async (
web3,
validatorList,
bridgeContract,
confirmationContractMethod,
fromHome,
setResult,
requiredSignatures,
setSignatureCollected,

View File

@@ -25,7 +25,8 @@ export const getFinalizationEvent = async (
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function
setPendingExecution: Function,
setExecutionEventsFetched: Function
) => {
if (!contract || !web3 || !waitingBlocksResolved) return
// Since it filters by the message id, only one event will be fetched
@@ -55,6 +56,7 @@ export const getFinalizationEvent = async (
executionResult: event.returnValues.status
})
} else {
setExecutionEventsFetched(true)
// If event is defined, it means it is a message from Home to Foreign
if (collectedSignaturesEvent) {
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
@@ -128,7 +130,8 @@ export const getFinalizationEvent = async (
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution
setPendingExecution,
setExecutionEventsFetched
),
interval
)

View File

@@ -0,0 +1,26 @@
import Web3 from 'web3'
function strip0x(s: string) {
return Web3.utils.isHexStrict(s) ? s.substr(2) : s
}
export interface Signature {
v: string
r: string
s: string
}
export function signatureToVRS(rawSignature: string): Signature {
const signature = strip0x(rawSignature)
const v = signature.substr(64 * 2)
const r = signature.substr(0, 32 * 2)
const s = signature.substr(32 * 2, 32 * 2)
return { v, r, s }
}
export function packSignatures(array: Array<Signature>): string {
const length = strip0x(Web3.utils.toHex(array.length))
const msgLength = length.length === 1 ? `0${length}` : length
const [v, r, s] = array.reduce(([vs, rs, ss], { v, r, s }) => [vs + v, rs + r, ss + s], ['', '', ''])
return `0x${msgLength}${v}${r}${s}`
}

View File

@@ -45,8 +45,10 @@ export const getValidatorConfirmation = (
}
}
export const getValidatorSuccessTransaction = (
export const getSuccessExecutionTransaction = (
web3: Web3,
bridgeContract: Contract,
fromHome: boolean,
messageData: string,
timestamp: number,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>

View File

@@ -5,7 +5,8 @@
],
"rules": {
"no-unused-expressions": "off",
"import/no-extraneous-dependencies": "off"
"import/no-extraneous-dependencies": "off",
"no-bitwise": "off"
},
"env": {
"mocha": true

View File

@@ -2,9 +2,15 @@ function strip0x(input) {
return input.replace(/^0x/, '')
}
function addTxHashToData({ encodedData, transactionHash }) {
return encodedData.slice(0, 2) + strip0x(transactionHash) + encodedData.slice(2)
}
/**
* Decodes the datatype byte from the AMB message.
* First (the most significant bit) denotes if the message should be forwarded to the manual lane.
* @param dataType: number datatype of the received AMB message.
* @return {{manualLane: boolean}}
*/
const decodeAMBDataType = dataType => ({
manualLane: (dataType & 128) === 128
})
function parseAMBMessage(message) {
message = strip0x(message)
@@ -12,16 +18,29 @@ function parseAMBMessage(message) {
const messageId = `0x${message.slice(0, 64)}`
const sender = `0x${message.slice(64, 104)}`
const executor = `0x${message.slice(104, 144)}`
const dataType = parseInt(message.slice(156, 158), 16)
return {
sender,
executor,
messageId
messageId,
dataType,
decodedDataType: decodeAMBDataType(dataType)
}
}
module.exports = {
addTxHashToData,
parseAMBMessage,
strip0x
const normalizeAMBMessageEvent = e => {
let msgData = e.returnValues.encodedData
if (!e.returnValues.messageId) {
// append tx hash to an old message, where message id was not used
// for old messages, e.messageId is a corresponding transactionHash
msgData = e.transactionHash + msgData.slice(2)
}
return parseAMBMessage(msgData)
}
module.exports = {
strip0x,
parseAMBMessage,
normalizeAMBMessageEvent
}

View File

@@ -1,6 +1,6 @@
const { BN } = require('web3-utils')
const { expect } = require('chai').use(require('bn-chai')(BN))
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message')
const { parseAMBMessage, strip0x } = require('../message')
describe('strip0x', () => {
it('should remove 0x from input', () => {
@@ -24,28 +24,6 @@ describe('strip0x', () => {
expect(result).to.be.equal(input)
})
})
describe('addTxHashToData', () => {
it('should add txHash to encoded data at position 2', () => {
// Given
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
const msgDataType = '00'
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
const encodedData = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
const transactionHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
const message = `0x${strip0x(transactionHash)}${strip0x(msgSender)}${strip0x(
msgExecutor
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
// When
const result = addTxHashToData({ encodedData, transactionHash })
// Then
expect(result).to.be.equal(message)
})
})
describe('parseAMBMessage', () => {
it('should parse data type 00', () => {
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'

View File

@@ -164,7 +164,7 @@ const getPastEvents = async (
} catch (e) {
if (e.message.includes('query returned more than') && toBlock !== 'latest') {
const middle = toBN(fromBlock)
.add(toBlock)
.add(toBN(toBlock))
.divRound(toBN(2))
const middlePlusOne = middle.add(toBN(1))

View File

@@ -32,8 +32,6 @@ provisioner:
inventory:
host_vars:
oracle-amb-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-amb-stake-erc-to-erc-host:

View File

@@ -21,8 +21,6 @@ provisioner:
inventory:
host_vars:
oracle-amb-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
verifier:

View File

@@ -1,5 +1,3 @@
---
- import_playbook: ../../../deployment/site.yml
# The docker-compose files have to be modified, in order to join the docker containers over network with the parity containers
- import_playbook: ./oracle-docker-compose.yml
- import_playbook: ./ui-docker-compose.yml
- import_playbook: ../../../deployment/site.yml

View File

@@ -1,26 +0,0 @@
---
- name: Slurp docker compose file
slurp:
src: "/home/poadocker/bridge/oracle/{{ file }}.yml"
register: docker_compose_slurp
- name: Parse docker compose file
set_fact:
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
- name: Add the external network used to connect to Parity nodes
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
- name: Add all Oracle containers to the network
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Expose Redis port to allow connecting from redis-cli
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
- name: Write updated docker file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/{{ file }}.yml"

View File

@@ -1,33 +1,22 @@
---
- name: Overwrite Oracle the docker-compose
- name: Prepare Oracle for ultimate tests
hosts: oracle
become: true
tasks:
- name: stop the service
shell: service poabridge stop
- name: ReTag current oracle image
shell: docker tag $(docker images --format '{{ '{{' }}.Repository{{ '}}' }}:{{ '{{' }}.Tag{{ '}}' }}' | grep -m 1 tokenbridge-e2e-oracle) oracle:ultimate-testing
- name: Connect parity to oracle networks
shell: "docker network create {{ item }} && docker network connect {{ item }} parity1 && docker network connect {{ item }} parity2"
with_items:
- oracle_net_db_bridge_request
- oracle_net_db_bridge_collected
- oracle_net_db_bridge_affirmation
- oracle_net_db_bridge_transfer
- oracle_net_db_bridge_senderhome
- oracle_net_db_bridge_senderforeign
- oracle_net_rabbit_bridge_request
- oracle_net_rabbit_bridge_collected
- oracle_net_rabbit_bridge_affirmation
- oracle_net_rabbit_bridge_transfer
- oracle_net_rabbit_bridge_senderhome
- oracle_net_rabbit_bridge_senderforeign
delegate_to: 127.0.0.1
become: false
- name: Replace oracle image
replace:
path: "/home/poadocker/bridge/oracle/{{ item }}.yml"
regexp: 'poanetwork/tokenbridge-oracle:latest'
replace: "oracle:ultimate-testing"
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-erc-native
- include_tasks: oracle-add-docker-external-network.yml
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-erc-native
loop_control:
loop_var: file
- name: start the service
shell: service poabridge start

View File

@@ -1,32 +0,0 @@
---
- name: Overwrite UI the docker-compose
hosts: ui
become: true
tasks:
- name: stop the service
shell: service tokenbridge-ui stop
- name: Slurp docker compose file
slurp:
src: "/home/poadocker/bridge/ui/docker-compose.yml"
register: docker_compose_slurp
- name: Parse docker compose file
set_fact:
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
- name: Add the external network used to connect to Parity nodes
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
- name: Add all UI containers to the network
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': ['ultimate']}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Write new docker-compose file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/ui/docker-compose.yml"
- name: start the service
shell: service tokenbridge-ui start

View File

@@ -32,8 +32,6 @@ provisioner:
inventory:
host_vars:
oracle-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-erc-host:

View File

@@ -32,8 +32,6 @@ provisioner:
inventory:
host_vars:
oracle-erc-to-native-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_HOME_START_BLOCK: 1

View File

@@ -32,8 +32,6 @@ provisioner:
inventory:
host_vars:
oracle-native-to-erc-host:
COMMON_HOME_RPC_URL: "http://parity1:8545"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-native-to-erc-host:

View File

@@ -5,13 +5,13 @@ ORACLE_ALLOW_HTTP_FOR_RPC: yes
ORACLE_LOG_LEVEL: debug
## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network"
COMMON_HOME_RPC_URL: "http://parity1:8545"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
UI_HOME_WITHOUT_EVENTS: false
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network"
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
UI_FOREIGN_WITHOUT_EVENTS: false
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
@@ -52,3 +52,7 @@ MONITOR_FOREIGN_START_BLOCK: 0
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100
# disable building and pulling of docker images from the Docker Hub
skip_pull: true
skip_build: true

View File

@@ -3,3 +3,4 @@
shell: docker-compose pull
args:
chdir: "{{ bridge_path }}/monitor"
when: skip_pull is undefined

View File

@@ -3,3 +3,4 @@
shell: docker-compose pull
args:
chdir: "{{ bridge_path }}/oracle"
when: skip_pull is undefined

View File

@@ -3,3 +3,4 @@
shell: docker-compose build
args:
chdir: "{{ bridge_path }}/ui"
when: skip_build is undefined

View File

@@ -1 +1,2 @@
0xc9e38bfdB9c635F0796ad83CC8705dc379F41c04
0xc9e38bfdB9c635F0796ad83CC8705dc379F41c04
0x612E8bd50A7b1F009F43f2b8679E9B8eD91eb5CE

View File

@@ -23,3 +23,4 @@ ORACLE_FOREIGN_RPC_POLLING_INTERVAL=500
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

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_PORT=3003
UI_STYLES=stake

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_PORT=3002
UI_STYLES=core

View File

@@ -19,5 +19,5 @@ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_PORT=3001
UI_STYLES=core

View File

@@ -63,6 +63,7 @@
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"homeBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"blockedHomeBox": "0x612E8bd50A7b1F009F43f2b8679E9B8eD91eb5CE",
"monitor": "http://monitor-amb:3013/bridge"
},
"ambStakeErcToErc": {

View File

@@ -4,12 +4,14 @@ networks:
external: true
services:
parity1:
container_name: parity1
build: ../parity
ports:
- "8541:8545"
networks:
- ultimate
parity2:
container_name: parity2
build:
context: ../parity
dockerfile: Dockerfile-foreign
@@ -61,6 +63,9 @@ services:
environment:
- NODE_ENV=production
command: "true"
volumes:
- '../e2e-commons/access-lists/block_list.txt:/mono/oracle/access-lists/block_list.txt'
- '../e2e-commons/access-lists/allowance_list.txt:/mono/oracle/access-lists/allowance_list.txt'
networks:
- ultimate
ui:

View File

@@ -53,3 +53,8 @@ node deploy.js
cd - > /dev/null
node setupStakeTokens.js
cd - > /dev/null
echo -e "\n\n############ Deploying one more test contract for amb ############\n"
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null

View File

@@ -17,21 +17,29 @@ docker-compose up -d parity1 parity2 e2e
startValidator () {
docker-compose $1 run -d --name $4 redis
docker-compose $1 run -d --name $5 rabbit
docker-compose $1 run $2 $3 -d oracle yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:transfer
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:transfer
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn worker:convert-to-chai
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request
if [[ -z "$MODE" || "$MODE" == native-to-erc ]]; then
docker-compose $1 run $2 $3 -d oracle yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle yarn watcher:affirmation-request
fi
if [[ -z "$MODE" || "$MODE" == erc-to-erc ]]; then
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-erc20 yarn watcher:transfer
fi
if [[ -z "$MODE" || "$MODE" == erc-to-native ]]; then
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn watcher:transfer
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn worker:convert-to-chai
fi
if [[ -z "$MODE" || "$MODE" == amb ]]; then
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:signature-request
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:collected-signatures
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request
fi
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn sender:home
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn sender:foreign
}
@@ -48,25 +56,7 @@ startAMBValidator () {
while [ "$1" != "" ]; do
if [ "$1" == "oracle" ]; then
docker-compose up -d redis rabbit
docker-compose run -d oracle yarn watcher:signature-request
docker-compose run -d oracle yarn watcher:collected-signatures
docker-compose run -d oracle yarn watcher:affirmation-request
docker-compose run -d oracle-erc20 yarn watcher:signature-request
docker-compose run -d oracle-erc20 yarn watcher:collected-signatures
docker-compose run -d oracle-erc20 yarn watcher:affirmation-request
docker-compose run -d oracle-erc20 yarn watcher:transfer
docker-compose run -d oracle-erc20-native yarn watcher:signature-request
docker-compose run -d oracle-erc20-native yarn watcher:collected-signatures
docker-compose run -d oracle-erc20-native yarn watcher:affirmation-request
docker-compose run -d oracle-erc20-native yarn watcher:transfer
docker-compose run -d oracle-erc20-native yarn worker:convert-to-chai
docker-compose run -d oracle-amb yarn watcher:signature-request
docker-compose run -d oracle-amb yarn watcher:collected-signatures
docker-compose run -d oracle-amb yarn watcher:affirmation-request
docker-compose run -d oracle yarn sender:home
docker-compose run -d oracle yarn sender:foreign
startValidator "" "" "" "redis" "rabbit"
fi
if [ "$1" == "oracle-validator-2" ]; then
@@ -90,9 +80,9 @@ while [ "$1" != "" ]; do
docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
docker-compose run -d -p 3000:3000 ui yarn start
docker-compose run -d -p 3001:3000 ui-erc20 yarn start
docker-compose run -d -p 3002:3000 ui-erc20-native yarn start
docker-compose run -d -p 3003:3000 ui-amb-stake-erc20-erc20 yarn start
docker-compose run -d -p 3001:3001 ui-erc20 yarn start
docker-compose run -d -p 3002:3002 ui-erc20-native yarn start
docker-compose run -d -p 3003:3003 ui-amb-stake-erc20-erc20 yarn start
fi
if [ "$1" == "alm" ]; then
@@ -110,7 +100,23 @@ while [ "$1" != "" ]; do
fi
if [ "$1" == "monitor" ]; then
docker-compose up -d monitor monitor-erc20 monitor-erc20-native monitor-amb
case "$MODE" in
amb)
docker-compose up -d monitor-amb
;;
native-to-erc)
docker-compose up -d monitor
;;
erc-to-erc)
docker-compose up -d monitor-erc20
;;
erc-to-native)
docker-compose up -d monitor-erc20-native
;;
*)
docker-compose up -d monitor monitor-erc20 monitor-erc20-native monitor-amb
;;
esac
fi
if [ "$1" == "alm-e2e" ]; then

View File

@@ -1,7 +1,15 @@
while true; do
sleep 5
docker-compose -f ../e2e-commons/docker-compose.yml exec -d monitor yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec -d monitor-erc20 yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec -d monitor-erc20-native yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec -d monitor-amb yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor yarn check-all
pid1=$!
docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20 yarn check-all
pid2=$!
docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20-native yarn check-all
pid3=$!
docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-amb yarn check-all
pid4=$!
wait $pid1
wait $pid2
wait $pid3
wait $pid4
done

View File

@@ -1,12 +1,28 @@
cd $(dirname $0)
../e2e-commons/up.sh deploy blocks monitor
mode="$1"
case "$mode" in
amb)
script=./test/amb.js
;;
native-to-erc)
script=./test/nativeToErc.js
;;
erc-to-erc)
script=./test/ercToErc.js
;;
erc-to-native)
script=./test/ercToNative.js
;;
esac
./wait-for-monitor.sh
MODE="$mode" ../e2e-commons/up.sh deploy blocks monitor
MODE="$mode" ./wait-for-monitor.sh
nohup ./periodically-check-all.sh < /dev/null > /dev/null 2>&1 &
checkPID=$!
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace monitor-e2e run start
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace monitor-e2e run start $script
rc=$?
../e2e-commons/down.sh

View File

@@ -25,6 +25,7 @@ describe('AMB', () => {
describe('general', async () => {
it('should contain fromHomeToForeignDiff', () => assert(data.fromHomeToForeignDiff === 0))
it('should contain fromHomeToForeignPBUDiff', () => assert(data.fromHomeToForeignPBUDiff === 0))
it('should contain fromForeignToHomeDiff', () => assert(data.fromForeignToHomeDiff === 0))
it('should contain lastChecked', () => assert(data.lastChecked >= 0))
it('should contain timeDiff', () => assert(data.timeDiff >= 0))
@@ -114,7 +115,16 @@ describe('AMB', () => {
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
return data.fromHomeToForeignDiff !== 0
return data.fromHomeToForeignDiff === 1 && data.fromHomeToForeignPBUDiff === 0
})
})
it('should change fromHomeToForeignPBUDiff', async () => {
// send message
await sendAMBMessage(homeRPC.URL, user, amb.homeBox, amb.home, amb.foreignBox, true)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
return data.fromHomeToForeignDiff === 1 && data.fromHomeToForeignPBUDiff === 1
})
})
it('should change validatorsMatch', async () => {

View File

@@ -54,16 +54,16 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
})
it('should consider chai token balance', async function() {
this.timeout(60000)
this.timeout(120000)
await initializeChaiToken(foreignRPC.URL, ercToNativeBridge.foreign)
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.foreignToken, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
if (!data.foreign) {
return false
}
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.02 &&
erc20Balance === '0.02' &&
@@ -77,10 +77,10 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
if (!data.foreign) {
return false
}
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.02 &&
erc20Balance === '0.01' &&
@@ -94,10 +94,10 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
if (!data.foreign) {
return false
}
const { erc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.02 &&
erc20Balance === '0.005' &&

View File

@@ -44,12 +44,16 @@ const sendTokens = async (rpcUrl, account, tokenAddress, recipientAddress) => {
})
}
const sendAMBMessage = async (rpcUrl, account, boxAddress, bridgeAddress, boxOtherSideAddress) => {
const sendAMBMessage = async (rpcUrl, account, boxAddress, bridgeAddress, boxOtherSideAddress, manualLane = false) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(account.privateKey)
const homeBox = new web3.eth.Contract(BOX_ABI, boxAddress)
await homeBox.methods.setValueOnOtherNetwork(3, bridgeAddress, boxOtherSideAddress).send({
await homeBox.methods[manualLane ? 'setValueOnOtherNetworkUsingManualLane' : 'setValueOnOtherNetwork'](
3,
bridgeAddress,
boxOtherSideAddress
).send({
from: account.address,
gas: '400000'
})

View File

@@ -6,10 +6,18 @@ check_files_exist() {
rc=0
for f in "${FILES[@]}"; do
command="test -f responses/bridge/$f"
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20 /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20-native /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-amb /bin/bash -c "$command") || rc=1
if [[ -z "$MODE" || "$MODE" == native-to-erc ]]; then
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor /bin/bash -c "$command") || rc=1
fi
if [[ -z "$MODE" || "$MODE" == erc-to-erc ]]; then
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20 /bin/bash -c "$command") || rc=1
fi
if [[ -z "$MODE" || "$MODE" == erc-to-native ]]; then
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-erc20-native /bin/bash -c "$command") || rc=1
fi
if [[ -z "$MODE" || "$MODE" == amb ]]; then
(docker-compose -f ../e2e-commons/docker-compose.yml exec -T monitor-amb /bin/bash -c "$command") || rc=1
fi
done
return $rc
}

View File

@@ -22,3 +22,9 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR=1
MONITOR_TX_NUMBER_THRESHOLD=100
MONITOR_PORT=3003
MONITOR_CACHE_EVENTS=true
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST=
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST=
# MONITOR_HOME_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...
# MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE=0x... 0x... 0x...

View File

@@ -1,18 +1,9 @@
require('dotenv').config()
const Web3 = require('web3')
const logger = require('./logger')('alerts')
const eventsInfo = require('./utils/events')
const { getBlockNumber } = require('./utils/contract')
const { processedMsgNotDelivered, eventWithoutReference } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { COMMON_HOME_RPC_URL, COMMON_FOREIGN_RPC_URL } = process.env
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
async function main() {
const {
@@ -33,7 +24,8 @@ async function main() {
xAffirmations = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
}
logger.debug('building misbehavior blocks')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const baseRange = [false, false, false, false, false]
const xSignaturesMisbehavior = buildRangesObject(
@@ -73,21 +65,21 @@ async function main() {
/**
* Finds the location for the blockNumber in a specific range starting from currentBlockNumber
* @param {BN} currentBlockNumber
* @param {Number} currentBlockNumber
* @returns {function({blockNumber?: *}): boolean[]}
*/
const findMisbehaviorRange = currentBlockNumber => ({ blockNumber }) => {
const minus60 = currentBlockNumber.sub(Web3.utils.toBN(60))
const minus180 = currentBlockNumber.sub(Web3.utils.toBN(180))
const minus720 = currentBlockNumber.sub(Web3.utils.toBN(720))
const minus17280 = currentBlockNumber.sub(Web3.utils.toBN(17280))
const minus60 = currentBlockNumber - 60
const minus180 = currentBlockNumber - 180
const minus720 = currentBlockNumber - 720
const minus17280 = currentBlockNumber - 17280
return [
minus60.lte(blockNumber),
minus180.lte(blockNumber) && minus60.gt(blockNumber),
minus720.lte(blockNumber) && minus180.gt(blockNumber),
minus17280.lte(blockNumber) && minus720.gt(blockNumber),
minus17280.gt(blockNumber)
minus60 <= blockNumber,
minus180 <= blockNumber && minus60 > blockNumber,
minus720 <= blockNumber && minus180 > blockNumber,
minus17280 <= blockNumber && minus720 > blockNumber,
minus17280 > blockNumber
]
}

0
monitor/cache/.gitkeep vendored Normal file
View File

View File

@@ -1,20 +1,16 @@
require('dotenv').config()
const Web3 = require('web3')
const BN = require('bignumber.js')
const logger = require('./logger')('checkWorker')
const { getBridgeMode } = require('../commons')
const getBalances = require('./getBalances')
const getShortEventStats = require('./getShortEventStats')
const validators = require('./validators')
const getEventsInfo = require('./utils/events')
const { writeFile, createDir } = require('./utils/file')
const { saveCache } = require('./utils/web3Cache')
const { web3Home } = require('./utils/web3')
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_RPC_URL, MONITOR_BRIDGE_NAME } = process.env
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const { COMMON_HOME_BRIDGE_ADDRESS, MONITOR_BRIDGE_NAME } = process.env
const { HOME_ERC_TO_ERC_ABI } = require('../commons')
@@ -24,42 +20,27 @@ async function checkWorker() {
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
const bridgeMode = await getBridgeMode(homeBridge)
logger.debug('Bridge mode:', bridgeMode)
logger.debug('calling getEventsInfo()')
const eventsInfo = await getEventsInfo(bridgeMode)
logger.debug('calling getBalances()')
const balances = await getBalances(bridgeMode)
const balances = await getBalances(bridgeMode, eventsInfo)
logger.debug('calling getShortEventStats()')
const events = await getShortEventStats(bridgeMode)
const events = await getShortEventStats(bridgeMode, eventsInfo)
const home = Object.assign({}, balances.home, events.home)
const foreign = Object.assign({}, balances.foreign, events.foreign)
const status = Object.assign({}, balances, events, { home }, { foreign })
if (status.balanceDiff && status.unclaimedBalance) {
status.balanceDiff = new BN(status.balanceDiff).minus(status.unclaimedBalance).toFixed()
}
if (!status) throw new Error('status is empty: ' + JSON.stringify(status))
status.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/getBalances.json`, status)
saveCache()
logger.debug('calling validators()')
const vBalances = await validators(bridgeMode)
if (!vBalances) throw new Error('vBalances is empty: ' + JSON.stringify(vBalances))
vBalances.homeOk = true
vBalances.foreignOk = true
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
for (const hv in vBalances.home.validators) {
if (vBalances.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
vBalances.homeOk = false
break
}
}
}
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
for (const hv in vBalances.foreign.validators) {
if (vBalances.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
vBalances.foreignOk = false
break
}
}
}
vBalances.ok = vBalances.homeOk && vBalances.foreignOk
vBalances.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)

View File

@@ -3,6 +3,7 @@ const logger = require('./logger')('checkWorker2')
const eventsStats = require('./eventsStats')
const alerts = require('./alerts')
const { writeFile, createDir } = require('./utils/file')
const { saveCache } = require('./utils/web3Cache')
const { MONITOR_BRIDGE_NAME } = process.env
@@ -26,6 +27,7 @@ async function checkWorker2() {
_alerts.ok = !_alerts.executeAffirmations.mostRecentTxHash && !_alerts.executeSignatures.mostRecentTxHash
_alerts.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/alerts.json`, _alerts)
saveCache()
logger.debug('Done x2')
} catch (e) {
logger.error(e)

View File

@@ -1,15 +1,14 @@
require('dotenv').config()
const Web3 = require('web3')
const logger = require('./logger')('checkWorker3')
const stuckTransfers = require('./stuckTransfers')
const detectMediators = require('./detectMediators')
const { writeFile, createDir } = require('./utils/file')
const { web3Home } = require('./utils/web3')
const { saveCache } = require('./utils/web3Cache')
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_RPC_URL } = process.env
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS } = process.env
const { getBridgeMode, HOME_NATIVE_TO_ERC_ABI, BRIDGE_MODES } = require('../commons')
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
async function checkWorker3() {
try {
const homeBridge = new web3Home.eth.Contract(HOME_NATIVE_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
@@ -23,6 +22,15 @@ async function checkWorker3() {
transfers.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
logger.debug('Done')
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling detectMediators()')
const mediators = await detectMediators(bridgeMode)
mediators.ok = true
mediators.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/mediators.json`, mediators)
saveCache()
logger.debug('Done')
}
} catch (e) {
logger.error('checkWorker3.js', e)

148
monitor/detectMediators.js Normal file
View File

@@ -0,0 +1,148 @@
require('dotenv').config()
const logger = require('./logger')('stuckTransfers.js')
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
const eventsInfo = require('./utils/events')
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
const { addExecutionStatus } = require('./utils/message')
const { normalizeAMBMessageEvent } = require('../commons')
function countInteractions(requests) {
const stats = {}
requests.forEach(msg => {
if (!stats[msg.sender]) {
stats[msg.sender] = {}
}
if (!stats[msg.sender][msg.executor]) {
stats[msg.sender][msg.executor] = 0
}
stats[msg.sender][msg.executor] += 1
})
return stats
}
const normalize = event => ({
...normalizeAMBMessageEvent(event),
txHash: event.transactionHash,
logIndex: event.transactionLogIndex
})
const flat = arrays => Array.prototype.concat.apply([], arrays)
function findPermanentMediators(homeToForeignC2C, foreignToHomeC2C) {
return flat(
Object.entries(homeToForeignC2C).map(([homeMediator, homeStats]) =>
Object.entries(foreignToHomeC2C)
.map(([foreignMediator, foreignStats]) => ({
homeMediator,
foreignMediator,
homeToForeignRequests: homeStats[foreignMediator],
foreignToHomeRequests: foreignStats[homeMediator]
}))
.filter(stats => stats.homeToForeignRequests && stats.foreignToHomeRequests)
)
)
}
function findFloatingMediators(homeToForeignC2C, foreignToHomeC2C) {
return Object.entries(homeToForeignC2C)
.map(([homeMediator, homeStats]) => {
const noResponses = ([executor]) => !foreignToHomeC2C[executor] || !foreignToHomeC2C[executor][homeMediator]
const executorRequestPairs = Object.entries(homeStats).filter(noResponses)
return {
mediator: homeMediator,
executors: executorRequestPairs.map(pair => pair[0]),
requests: executorRequestPairs.map(pair => pair[1])
}
})
.filter(stats => stats.executors.length > 0)
}
function findRemotelyControlledMediators(statsU2C) {
return Object.entries(statsU2C).map(([user, stats]) => ({
user,
executors: Object.keys(stats),
requests: Object.values(stats)
}))
}
function findUnknown(statsA2U) {
return Object.entries(statsA2U).map(([sender, stats]) => ({
sender,
executors: Object.keys(stats),
requests: Object.values(stats)
}))
}
async function main(mode) {
const {
homeToForeignRequests,
foreignToHomeRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations
} = await eventsInfo(mode)
const homeToForeign = homeToForeignRequests
.map(normalize)
.map(addExecutionStatus(homeToForeignConfirmations))
.filter(x => typeof x.status === 'boolean')
const foreignToHome = foreignToHomeRequests
.map(normalize)
.map(addExecutionStatus(foreignToHomeConfirmations))
.filter(x => typeof x.status === 'boolean')
for (const event of homeToForeign) {
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
// meaning that the sender was a contract.
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
event.isSenderAContract = event.logIndex !== '0x0' || (await getHomeTxSender(event.txHash)) !== event.sender
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isForeignContract(event.executor))
}
for (const event of foreignToHome) {
// AMB contract emits a single UserRequestForAffirmation event for every foreign->home request.
// If index of such event in logs is not equal to 0x0, then some other events occurred before it,
// meaning that the sender was a contract.
// Alternatively, the sender is a contract, if the message sender is not equal to tx.origin.
event.isSenderAContract = event.logIndex !== '0x0' || (await getForeignTxSender(event.txHash)) !== event.sender
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isHomeContract(event.executor))
}
const C2C = event => event.isSenderAContract && event.isExecutorAContract
const U2C = event => !event.isSenderAContract && event.isExecutorAContract
const A2U = event => !event.isExecutorAContract
const homeToForeignC2C = countInteractions(homeToForeign.filter(C2C))
const foreignToHomeC2C = countInteractions(foreignToHome.filter(C2C))
const homeToForeignU2C = countInteractions(homeToForeign.filter(U2C))
const foreignToHomeU2C = countInteractions(foreignToHome.filter(U2C))
const homeToForeignA2U = countInteractions(homeToForeign.filter(A2U))
const foreignToHomeA2U = countInteractions(foreignToHome.filter(A2U))
const permanentMediators = findPermanentMediators(homeToForeignC2C, foreignToHomeC2C)
const floatingMediators = {
home: findFloatingMediators(homeToForeignC2C, foreignToHomeC2C),
foreign: findFloatingMediators(foreignToHomeC2C, homeToForeignC2C)
}
const remotelyControlledMediators = {
home: findRemotelyControlledMediators(homeToForeignU2C),
foreign: findRemotelyControlledMediators(foreignToHomeU2C)
}
const unknown = {
home: findUnknown(homeToForeignA2U),
foreign: findUnknown(foreignToHomeA2U)
}
logger.debug('Done')
return {
permanentMediators,
floatingMediators,
remotelyControlledMediators,
unknown,
lastChecked: Math.floor(Date.now() / 1000)
}
}
module.exports = main

View File

@@ -4,11 +4,12 @@ services:
monitor:
image: poanetwork/tokenbridge-monitor:latest
ports:
- "${MONITOR_PORT}:${MONITOR_PORT}"
- "${MONITOR_PORT}:${MONITOR_PORT}"
env_file: ./.env
environment:
environment:
- NODE_ENV=production
volumes:
- ./responses:/mono/monitor/responses
- ./cache:/mono/monitor/cache
restart: unless-stopped
entrypoint: "yarn start"

View File

@@ -1,8 +1,20 @@
require('dotenv').config()
const eventsInfo = require('./utils/events')
const { processedMsgNotDelivered, deliveredMsgNotProcessed, eventWithoutReference } = require('./utils/message')
const {
processedMsgNotDelivered,
deliveredMsgNotProcessed,
eventWithoutReference,
unclaimedHomeToForeignRequests
} = require('./utils/message')
const { getHomeTxSender } = require('./utils/web3Cache')
const { BRIDGE_MODES } = require('../commons')
const {
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST,
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST,
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER
} = process.env
async function main() {
const {
homeToForeignRequests,
@@ -33,17 +45,30 @@ async function main() {
lastChecked: Math.floor(Date.now() / 1000)
}
} else {
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
let onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
const onlyInForeignDeposits = homeToForeignConfirmations.filter(eventWithoutReference(homeToForeignRequests))
const onlyInHomeWithdrawals = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
const onlyInForeignWithdrawals = foreignToHomeRequests.filter(eventWithoutReference(foreignToHomeConfirmations))
const unclaimedStats = {}
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST || MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const unclaimedFilter = unclaimedHomeToForeignRequests()
if (MONITOR_HOME_TO_FOREIGN_CHECK_SENDER === 'true') {
for (let i = 0; i < onlyInHomeDeposits.length; i++) {
onlyInHomeDeposits[i].sender = await getHomeTxSender(onlyInHomeDeposits[i].transactionHash)
}
}
unclaimedStats.unclaimedHomeDeposits = onlyInHomeDeposits.filter(unclaimedFilter)
onlyInHomeDeposits = onlyInHomeDeposits.filter(e => !unclaimedFilter(e))
}
return {
onlyInHomeDeposits,
onlyInForeignDeposits,
onlyInHomeWithdrawals,
onlyInForeignWithdrawals,
...unclaimedStats,
lastChecked: Math.floor(Date.now() / 1000)
}
}

View File

@@ -1,23 +1,11 @@
require('dotenv').config()
const BN = require('bignumber.js')
const Web3 = require('web3')
const Web3Utils = require('web3').utils
const logger = require('./logger')('getBalances')
const { BRIDGE_MODES } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber } = require('./utils/web3')
const Web3Utils = Web3.utils
const {
COMMON_HOME_RPC_URL,
COMMON_FOREIGN_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS
} = process.env
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const {
ERC20_ABI,
@@ -30,21 +18,50 @@ const {
FOREIGN_NATIVE_TO_ERC_ABI
} = require('../commons')
async function main(bridgeMode) {
async function main(bridgeMode, eventsInfo) {
const {
homeToForeignConfirmations,
foreignToHomeConfirmations,
homeDelayedBlockNumber,
foreignDelayedBlockNumber
} = eventsInfo
// Events in the ./utils/events.js are fetched for different block ranges,
// In order to be consistent with the balance values, the following values might be needed
// Foreign balance should represent all UserRequestForAffirmation events up to block `N - requiredBlockConfirmation()`
// and all RelayedMessage events up to block `N`.
// This constant tells the difference between bridge balance at block `N - requiredBlockConfirmation() + 1`
// and the actual value monitor is interested in.
const lateForeignConfirmationsTotalValue = BN.sum(
0,
...homeToForeignConfirmations.filter(e => e.blockNumber > foreignDelayedBlockNumber).map(e => e.value)
)
// Home balance should represent all UserRequestForSignature events up to block `M - requiredBlockConfirmation()`
// and all AffirmationCompleted events up to block `M`.
// This constant tells the difference between bridge balance at block `M - requiredBlockConfirmation() + 1`
// and the actual value monitor is interested in.
const lateHomeConfirmationsTotalValue = BN.sum(
0,
...foreignToHomeConfirmations.filter(e => e.blockNumber > homeDelayedBlockNumber).map(e => e.value)
)
if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
logger.debug('calling erc20Contract.methods.balanceOf')
const foreignErc20Balance = await erc20Contract.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const foreignErc20Balance = await erc20Contract.methods
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
.call({}, foreignDelayedBlockNumber)
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
logger.debug('calling homeBridge.methods.erc677token')
const tokenAddress = await homeBridge.methods.erc677token().call()
const tokenContract = new web3Home.eth.Contract(ERC677_ABI, tokenAddress)
logger.debug('calling tokenContract.methods.totalSupply()')
const totalSupply = await tokenContract.methods.totalSupply().call()
const foreignBalanceBN = new BN(foreignErc20Balance)
const foreignTotalSupplyBN = new BN(totalSupply)
const totalSupply = await tokenContract.methods.totalSupply().call({}, homeDelayedBlockNumber)
const foreignBalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
const foreignTotalSupplyBN = new BN(totalSupply).plus(lateHomeConfirmationsTotalValue)
const diff = foreignBalanceBN.minus(foreignTotalSupplyBN).toString(10)
logger.debug('Done')
return {
@@ -61,12 +78,12 @@ async function main(bridgeMode) {
logger.debug('calling web3Home.eth.getBalance')
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_NATIVE_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc677token().call()
const homeBalance = await web3Home.eth.getBalance(COMMON_HOME_BRIDGE_ADDRESS)
const homeBalance = await web3Home.eth.getBalance(COMMON_HOME_BRIDGE_ADDRESS, homeDelayedBlockNumber)
const tokenContract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
logger.debug('calling tokenContract.methods.totalSupply()')
const totalSupply = await tokenContract.methods.totalSupply().call()
const homeBalanceBN = new BN(homeBalance)
const foreignTotalSupplyBN = new BN(totalSupply)
const totalSupply = await tokenContract.methods.totalSupply().call({}, foreignDelayedBlockNumber)
const homeBalanceBN = new BN(homeBalance).plus(lateHomeConfirmationsTotalValue)
const foreignTotalSupplyBN = new BN(totalSupply).plus(lateForeignConfirmationsTotalValue)
const diff = homeBalanceBN.minus(foreignTotalSupplyBN).toString(10)
logger.debug('Done')
return {
@@ -103,21 +120,25 @@ async function main(bridgeMode) {
}
logger.debug('calling erc20Contract.methods.balanceOf')
const foreignErc20Balance = await erc20Contract.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const foreignErc20Balance = await erc20Contract.methods
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
.call({}, foreignDelayedBlockNumber)
const homeBridge = new web3Home.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME_BRIDGE_ADDRESS)
logger.debug('calling homeBridge.methods.blockRewardContract')
const blockRewardAddress = await homeBridge.methods.blockRewardContract().call()
const blockRewardContract = new web3Home.eth.Contract(BLOCK_REWARD_ABI, blockRewardAddress)
const homeBlockNumber = await getHomeBlockNumber()
logger.debug('calling blockReward.methods.mintedTotally')
const mintedCoins = await blockRewardContract.methods.mintedTotallyByBridge(COMMON_HOME_BRIDGE_ADDRESS).call()
const mintedCoins = await blockRewardContract.methods
.mintedTotallyByBridge(COMMON_HOME_BRIDGE_ADDRESS)
.call({}, homeBlockNumber)
logger.debug('calling homeBridge.methods.totalBurntCoins')
const burntCoins = await homeBridge.methods.totalBurntCoins().call()
const burntCoins = await homeBridge.methods.totalBurntCoins().call({}, homeDelayedBlockNumber)
const mintedCoinsBN = new BN(mintedCoins)
const burntCoinsBN = new BN(burntCoins)
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
const foreignErc20BalanceBN = new BN(foreignErc20Balance)
const foreignErc20BalanceBN = new BN(foreignErc20Balance).plus(lateForeignConfirmationsTotalValue)
const investedAmountInDaiBN = new BN(investedAmountInDai)
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)

View File

@@ -1,18 +1,36 @@
require('dotenv').config()
const eventsInfo = require('./utils/events')
const BN = require('bignumber.js')
const Web3Utils = require('web3').utils
const {
eventWithoutReference,
deliveredMsgNotProcessed,
unclaimedHomeToForeignRequests,
manuallyProcessedAMBHomeToForeignRequests
} = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { getHomeTxSender } = require('./utils/web3Cache')
async function main(bridgeMode) {
const {
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST,
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST,
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER
} = process.env
async function main(bridgeMode, eventsInfo) {
const {
homeToForeignConfirmations,
homeToForeignRequests,
foreignToHomeConfirmations,
foreignToHomeRequests
} = await eventsInfo(bridgeMode)
} = eventsInfo
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
const onlyInHomeRequests = homeToForeignRequests.filter(deliveredMsgNotProcessed(homeToForeignConfirmations))
const manuallyProcessedRequests = onlyInHomeRequests.filter(manuallyProcessedAMBHomeToForeignRequests())
return {
fromHomeToForeignDiff: homeToForeignRequests.length - homeToForeignConfirmations.length,
fromHomeToForeignDiff:
homeToForeignRequests.length - homeToForeignConfirmations.length - manuallyProcessedRequests.length,
fromHomeToForeignPBUDiff: manuallyProcessedRequests.length,
fromForeignToHomeDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
home: {
toForeign: homeToForeignRequests.length,
@@ -24,9 +42,26 @@ async function main(bridgeMode) {
}
}
} else {
return {
const stats = {
depositsDiff: homeToForeignRequests.length - homeToForeignConfirmations.length,
withdrawalDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length,
withdrawalDiff: foreignToHomeConfirmations.length - foreignToHomeRequests.length
}
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST || MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
if (MONITOR_HOME_TO_FOREIGN_CHECK_SENDER === 'true') {
for (let i = 0; i < onlyInHomeDeposits.length; i++) {
onlyInHomeDeposits[i].sender = await getHomeTxSender(onlyInHomeDeposits[i].transactionHash)
}
}
const unclaimedPool = onlyInHomeDeposits.filter(unclaimedHomeToForeignRequests())
stats.depositsDiff -= unclaimedPool.length
stats.unclaimedDiff = unclaimedPool.length
stats.unclaimedBalance = Web3Utils.fromWei(BN.sum(0, ...unclaimedPool.map(e => e.value)).toFixed())
}
return {
...stats,
home: {
deposits: homeToForeignRequests.length,
withdrawals: foreignToHomeConfirmations.length

View File

@@ -50,6 +50,16 @@ bridgeRouter.get('/alerts', async (req, res, next) => {
}
})
bridgeRouter.get('/mediators', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/mediators.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
next(e)
}
})
bridgeRouter.get('/stuckTransfers', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/stuckTransfers.json`)

View File

@@ -14,12 +14,12 @@
"author": "",
"license": "ISC",
"dependencies": {
"bignumber.js": "^6.0.0",
"bignumber.js": "^9.0.1",
"cors": "^2.8.5",
"dotenv": "^5.0.1",
"express": "^4.16.3",
"node-fetch": "^2.1.2",
"web3": "1.0.0-beta.34"
"web3": "^1.3.0"
},
"engines": {
"node": ">= 10.18"

View File

@@ -2,17 +2,21 @@
CONFIGDIR="configs"
RESPONSESDIR="responses"
ACLDIR="access-lists"
ALLOWANCEFILE="allowance_list.txt"
BLOCKFILE="block_list.txt"
CACHEDIR="cache"
IMAGETAG="latest"
cd $(dirname $0)/..
if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
tstart=`date +"%s"`
for file in ${CONFIGDIR}/*.env
do
echo "${file} handling..."
bridgename=`source ${file} && echo ${MONITOR_BRIDGE_NAME}`
reportdir=${RESPONSESDIR}"/"${bridgename}
if [ ! -d ${reportdir} ]; then
@@ -26,10 +30,18 @@ if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
fi
done
alist=`source ${file} && echo ${MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST}`
blist=`source ${file} && echo ${MONITOR_HOME_TO_FOREIGN_BLOCK_LIST}`
al_param="$(pwd)/${ACLDIR}/${bridgename}/${ALLOWANCEFILE}:/mono/monitor/access-lists/allowance_list.txt"
bl_param="$(pwd)/${ACLDIR}/${bridgename}/${BLOCKFILE}:/mono/monitor/access-lists/block_list.txt"
containername=${bridgename}"-checker"
docker container stats --no-stream ${containername} 2>/dev/null 1>&2
if [ ! "$?" == "0" ]; then
mkdir -p "$(pwd)/$CACHEDIR/$bridgename"
docker run --rm --env-file $file -v $(pwd)/${RESPONSESDIR}:/mono/monitor/responses \
${alist:+"-v"} ${alist:+"$al_param"} ${blist:+"-v"} ${blist:+"$bl_param"} \
-v $(pwd)/${CACHEDIR}/${bridgename}:/mono/monitor/cache/${bridgename} \
--name ${containername} poanetwork/tokenbridge-monitor:${IMAGETAG} \
/bin/bash -c 'yarn check-all'
shasum -a 256 -s -c ${checksumfile}
@@ -46,15 +58,15 @@ if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
else
echo "${containername} have not finished yet" >&2
fi
rm ${checksumfile}
echo "========================================"
done
tend=`date +"%s"`
tdiff=`expr ${tend} - ${tstart}`
echo "Total time to run: ${tdiff}"
else
echo "Monitor is not running, skipping checks."
fi
fi

View File

@@ -1,14 +1,12 @@
require('dotenv').config()
const Web3 = require('web3')
const logger = require('./logger')('stuckTransfers.js')
const { FOREIGN_V1_ABI } = require('../commons/abis')
const { web3Foreign, getForeignBlockNumber } = require('./utils/web3')
const { getPastEvents } = require('./utils/web3Cache')
const { COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const { COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const MONITOR_FOREIGN_START_BLOCK = Number(process.env.MONITOR_FOREIGN_START_BLOCK) || 0
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const ABITransferWithoutData = [
{
anonymous: false,
@@ -64,38 +62,39 @@ const ABIWithData = [
}
]
function compareTransfers(transfersNormal) {
return withData => {
return (
transfersNormal.filter(normal => {
return normal.transactionHash === withData.transactionHash
}).length === 0
)
}
function transferWithoutCallback(transfersNormal) {
const txHashes = new Set()
transfersNormal.forEach(transfer => txHashes.add(transfer.transactionHash))
return withData => !txHashes.has(withData.transactionHash)
}
async function main() {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_V1_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
logger.debug('calling foreignBridge.methods.erc677token')
const erc20Address = await foreignBridge.methods.erc677token().call()
const tokenContract = new web3Foreign.eth.Contract(ABITransferWithoutData, erc20Address)
const tokenContractWithData = new web3Foreign.eth.Contract(ABIWithData, erc20Address)
logger.debug('getting last block number')
const foreignBlockNumber = await getForeignBlockNumber()
const foreignConfirmations = await foreignBridge.methods.requiredBlockConfirmations().call()
const foreignDelayedBlockNumber = foreignBlockNumber - foreignConfirmations
logger.debug('calling tokenContract.getPastEvents Transfer')
const transfersNormal = await tokenContract.getPastEvents('Transfer', {
filter: {
to: COMMON_FOREIGN_BRIDGE_ADDRESS
const options = {
event: 'Transfer',
options: {
filter: {
to: COMMON_FOREIGN_BRIDGE_ADDRESS
}
},
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: 'latest'
})
toBlock: foreignBlockNumber,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
}
const transfersNormal = await getPastEvents(tokenContract, options)
logger.debug('calling tokenContractWithData.getPastEvents Transfer')
const transfersWithData = await tokenContractWithData.getPastEvents('Transfer', {
filter: {
to: COMMON_FOREIGN_BRIDGE_ADDRESS
},
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: 'latest'
})
const stuckTransfers = transfersNormal.filter(compareTransfers(transfersWithData))
const transfersWithData = await getPastEvents(tokenContractWithData, options)
const stuckTransfers = transfersNormal.filter(transferWithoutCallback(transfersWithData))
logger.debug('Done')
return {
stuckTransfers,

View File

@@ -1,11 +0,0 @@
const { toBN } = require('web3').utils
const getBlockNumberCall = web3 => web3.eth.getBlockNumber()
async function getBlockNumber(web3Home, web3Foreign) {
return (await Promise.all([web3Home, web3Foreign].map(getBlockNumberCall))).map(toBN)
}
module.exports = {
getBlockNumber
}

View File

@@ -1,6 +1,4 @@
require('dotenv').config()
const Web3 = require('web3')
const { toBN } = require('web3').utils
const logger = require('../logger')('eventsUtils')
const {
BRIDGE_MODES,
@@ -11,7 +9,6 @@ const {
ERC20_ABI,
ERC677_BRIDGE_TOKEN_ABI,
getTokenType,
getPastEvents,
ZERO_ADDRESS,
OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI,
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI
@@ -19,24 +16,12 @@ const {
const { normalizeEventInformation } = require('./message')
const { filterTransferBeforeES } = require('./tokenUtils')
const { writeFile, readCacheFile } = require('./file')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./web3')
const { getPastEvents } = require('./web3Cache')
const {
COMMON_HOME_RPC_URL,
COMMON_FOREIGN_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS,
MONITOR_CACHE_EVENTS
} = process.env
const MONITOR_HOME_START_BLOCK = toBN(Number(process.env.MONITOR_HOME_START_BLOCK) || 0)
const MONITOR_FOREIGN_START_BLOCK = toBN(Number(process.env.MONITOR_FOREIGN_START_BLOCK) || 0)
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const { getBlockNumber } = require('./contract')
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS, MONITOR_CACHE_EVENTS } = process.env
const MONITOR_HOME_START_BLOCK = Number(process.env.MONITOR_HOME_START_BLOCK) || 0
const MONITOR_FOREIGN_START_BLOCK = Number(process.env.MONITOR_FOREIGN_START_BLOCK) || 0
const cacheFilePath = '/tmp/cachedEvents.json'
async function main(mode) {
@@ -73,11 +58,12 @@ async function main(mode) {
}
logger.debug('getting last block numbers')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
const homeConfirmations = toBN(await homeBridge.methods.requiredBlockConfirmations().call())
const foreignConfirmations = toBN(await foreignBridge.methods.requiredBlockConfirmations().call())
const homeDelayedBlockNumber = homeBlockNumber.sub(homeConfirmations)
const foreignDelayedBlockNumber = foreignBlockNumber.sub(foreignConfirmations)
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const homeConfirmations = await homeBridge.methods.requiredBlockConfirmations().call()
const foreignConfirmations = await foreignBridge.methods.requiredBlockConfirmations().call()
const homeDelayedBlockNumber = homeBlockNumber - homeConfirmations
const foreignDelayedBlockNumber = foreignBlockNumber - foreignConfirmations
let homeToForeignRequests = []
let foreignToHomeRequests = []
@@ -95,22 +81,24 @@ async function main(mode) {
homeToForeignRequests = (await getPastEvents(oldHomeBridge, {
event: 'UserRequestForSignature',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeDelayedBlockNumber
toBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
logger.debug(`found ${homeToForeignRequests.length} events`)
if (homeToForeignRequests.length > 0) {
homeMigrationBlock = toBN(Math.max(...homeToForeignRequests.map(x => x.blockNumber)))
homeMigrationBlock = Math.max(...homeToForeignRequests.map(x => x.blockNumber))
}
logger.debug("calling oldForeignBridge.getPastEvents('UserRequestForAffirmation(bytes)')")
foreignToHomeRequests = (await getPastEvents(oldForeignBridge, {
event: 'UserRequestForAffirmation',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignDelayedBlockNumber
toBlock: foreignDelayedBlockNumber,
chain: 'foreign'
})).map(normalizeEvent)
logger.debug(`found ${foreignToHomeRequests.length} events`)
if (foreignToHomeRequests.length > 0) {
foreignMigrationBlock = toBN(Math.max(...foreignToHomeRequests.map(x => x.blockNumber)))
foreignMigrationBlock = Math.max(...foreignToHomeRequests.map(x => x.blockNumber))
}
}
@@ -118,7 +106,8 @@ async function main(mode) {
const homeToForeignRequestsNew = (await getPastEvents(homeBridge, {
event: v1Bridge ? 'Deposit' : 'UserRequestForSignature',
fromBlock: homeMigrationBlock,
toBlock: homeDelayedBlockNumber
toBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
homeToForeignRequests = [...homeToForeignRequests, ...homeToForeignRequestsNew]
@@ -126,21 +115,26 @@ async function main(mode) {
const homeToForeignConfirmations = (await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Deposit' : 'RelayedMessage',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber
toBlock: foreignBlockNumber,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
})).map(normalizeEvent)
logger.debug("calling homeBridge.getPastEvents('AffirmationCompleted')")
const foreignToHomeConfirmations = (await getPastEvents(homeBridge, {
event: v1Bridge ? 'Withdraw' : 'AffirmationCompleted',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber
toBlock: homeBlockNumber,
chain: 'home',
safeToBlock: homeDelayedBlockNumber
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('UserRequestForAffirmation')")
const foreignToHomeRequestsNew = (await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
fromBlock: foreignMigrationBlock,
toBlock: foreignDelayedBlockNumber
toBlock: foreignDelayedBlockNumber,
chain: 'foreign'
})).map(normalizeEvent)
foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew]
@@ -152,7 +146,8 @@ async function main(mode) {
toBlock: foreignDelayedBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
},
chain: 'foreign'
})).map(normalizeEvent)
let directTransfers = transferEvents
@@ -163,7 +158,9 @@ async function main(mode) {
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
event: 'TokensSwapped',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber
toBlock: foreignBlockNumber,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
})
// Get token swap events emitted by foreign bridge
@@ -202,7 +199,8 @@ async function main(mode) {
toBlock: foreignDelayedBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
},
chain: 'foreign'
})).map(normalizeEvent)
// Remove events after the ES
@@ -237,7 +235,9 @@ async function main(mode) {
foreignToHomeConfirmations,
foreignToHomeRequests,
isExternalErc20,
bridgeMode
bridgeMode,
homeDelayedBlockNumber,
foreignDelayedBlockNumber
}
if (MONITOR_CACHE_EVENTS === 'true') {

View File

@@ -38,9 +38,25 @@ function readCacheFile(filePath) {
}
}
function writeCacheFile(filePath, object) {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFileSync(filePath, JSON.stringify(object))
}
function readAccessListFile(filePath) {
const data = fs.readFileSync(filePath)
return data
.toString()
.split('\n')
.map(addr => addr.trim().toLowerCase())
.filter(addr => addr.length === 42)
}
module.exports = {
readFile,
writeFile,
createDir,
readCacheFile
readCacheFile,
writeCacheFile,
readAccessListFile
}

View File

@@ -0,0 +1,93 @@
const { REWARDABLE_VALIDATORS_ABI, processValidatorsEvents } = require('../../commons')
const { getPastEvents } = require('./web3Cache')
const VALIDATORS_INDEXED_EVENTS_ABI = [
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'validator',
type: 'address'
}
],
name: 'ValidatorRemoved',
type: 'event'
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'validator',
type: 'address'
}
],
name: 'ValidatorAdded',
type: 'event'
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'validator',
type: 'address'
},
{
indexed: true,
name: 'reward',
type: 'address'
}
],
name: 'ValidatorAdded',
type: 'event'
}
]
const tryCall = async (method, fallbackValue) => {
try {
return await method.call()
} catch (e) {
return fallbackValue
}
}
const getValidatorList = async (address, eth, options) => {
const { logger } = options
logger.debug('getting validatorList')
const validatorsContract = new eth.Contract(REWARDABLE_VALIDATORS_ABI, address) // in monitor, BRIDGE_VALIDATORS_ABI was used
const validators = await tryCall(validatorsContract.methods.validatorList(), [])
if (validators.length) {
return validators
}
logger.debug('getting validatorsEvents')
options.fromBlock = Number(await tryCall(validatorsContract.methods.deployedAtBlock(), 0))
const contract = new eth.Contract(VALIDATORS_INDEXED_EVENTS_ABI, address)
const validatorsEvents = [
...(await getPastEvents(contract, {
event: 'ValidatorAdded(address)',
...options
})),
...(await getPastEvents(contract, {
event: 'ValidatorAdded(address,address)',
...options
})),
...(await getPastEvents(contract, {
event: 'ValidatorRemoved(address)',
...options
}))
].sort((a, b) => a.blockNumber - b.blockNumber || a.transactionIndex - b.transactionIndex)
return processValidatorsEvents(validatorsEvents)
}
module.exports = {
getValidatorList
}

View File

@@ -1,44 +1,31 @@
const web3Utils = require('web3').utils
const { parseAMBMessage } = require('../../commons')
const { normalizeAMBMessageEvent } = require('../../commons')
const { readAccessListFile } = require('./file')
const { MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST, MONITOR_HOME_TO_FOREIGN_BLOCK_LIST } = process.env
const keyAMB = e => [e.messageId, e.sender, e.executor].join(',').toLowerCase()
function deliveredMsgNotProcessed(processedList) {
return deliveredMsg => {
let msgData = deliveredMsg.returnValues.encodedData
if (!deliveredMsg.returnValues.messageId) {
// append tx hash to an old message, where message id was not used
msgData = deliveredMsg.transactionHash + msgData.slice(2)
}
const msg = parseAMBMessage(msgData)
return (
processedList.filter(processedMsg => {
return messageEqualsEvent(msg, processedMsg.returnValues)
}).length === 0
)
}
const keys = new Set()
processedList.forEach(processedMsg => keys.add(keyAMB(processedMsg.returnValues)))
return deliveredMsg => !keys.has(keyAMB(normalizeAMBMessageEvent(deliveredMsg)))
}
function processedMsgNotDelivered(deliveredList) {
return processedMsg => {
return (
deliveredList.filter(deliveredMsg => {
let msgData = deliveredMsg.returnValues.encodedData
if (!deliveredMsg.returnValues.messageId) {
// append tx hash to an old message, where message id was not used
msgData = deliveredMsg.transactionHash + msgData.slice(2)
}
const msg = parseAMBMessage(msgData)
return messageEqualsEvent(msg, processedMsg.returnValues)
}).length === 0
)
}
const keys = new Set()
deliveredList.forEach(deliveredMsg => keys.add(keyAMB(normalizeAMBMessageEvent(deliveredMsg))))
return processedMsg => !keys.has(keyAMB(processedMsg.returnValues))
}
function messageEqualsEvent(parsedMsg, event) {
return (
web3Utils.toChecksumAddress(parsedMsg.sender) === event.sender &&
web3Utils.toChecksumAddress(parsedMsg.executor) === event.executor &&
parsedMsg.messageId === event.messageId // for an old messages, event.messageId is actually a transactionHash
)
function addExecutionStatus(processedList) {
const statuses = {}
processedList.forEach(processedMsg => {
statuses[keyAMB(processedMsg.returnValues)] = processedMsg.returnValues.status
})
return deliveredMsg => {
deliveredMsg.status = statuses[keyAMB(deliveredMsg)]
return deliveredMsg
}
}
/**
@@ -60,13 +47,50 @@ const normalizeEventInformation = event => ({
value: event.returnValues.value
})
const eventWithoutReference = otherSideEvents => e =>
otherSideEvents.filter(a => a.referenceTx === e.referenceTx && a.recipient === e.recipient && a.value === e.value)
.length === 0
const key = e => [e.referenceTx, e.recipient, e.value].join(',').toLowerCase()
const eventWithoutReference = otherSideEvents => {
const keys = new Set()
otherSideEvents.forEach(e => keys.add(key(e)))
return e => !keys.has(key(e))
}
const unclaimedHomeToForeignRequests = () => {
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST)
return e => !allowanceList.includes(e.recipient.toLowerCase()) && !(e.sender && allowanceList.includes(e.sender))
} else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST)
return e => blockList.includes(e.recipient.toLowerCase()) || (e.sender && blockList.includes(e.sender))
} else {
return () => false
}
}
const manuallyProcessedAMBHomeToForeignRequests = () => {
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST) {
const allowanceList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST)
return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return (!allowanceList.includes(sender) && !allowanceList.includes(executor)) || decodedDataType.manualLane
}
} else if (MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
const blockList = readAccessListFile(MONITOR_HOME_TO_FOREIGN_BLOCK_LIST)
return e => {
const { sender, executor, decodedDataType } = normalizeAMBMessageEvent(e)
return blockList.includes(sender) || blockList.includes(executor) || decodedDataType.manualLane
}
} else {
return e => normalizeAMBMessageEvent(e).decodedDataType.manualLane
}
}
module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered,
addExecutionStatus,
normalizeEventInformation,
eventWithoutReference
eventWithoutReference,
unclaimedHomeToForeignRequests,
manuallyProcessedAMBHomeToForeignRequests
}

27
monitor/utils/web3.js Normal file
View File

@@ -0,0 +1,27 @@
require('dotenv').config()
const Web3 = require('web3')
const { COMMON_HOME_RPC_URL, COMMON_FOREIGN_RPC_URL } = process.env
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
function blockNumberWrapper(web3) {
let blockNumber = null
return async () => {
if (!blockNumber) {
blockNumber = await web3.eth.getBlockNumber()
}
return blockNumber
}
}
module.exports = {
web3Home,
web3Foreign,
getHomeBlockNumber: blockNumberWrapper(web3Home),
getForeignBlockNumber: blockNumberWrapper(web3Foreign)
}

171
monitor/utils/web3Cache.js Normal file
View File

@@ -0,0 +1,171 @@
const logger = require('../logger')('web3Cache')
const { readCacheFile, writeCacheFile } = require('./file')
const { web3Home, web3Foreign } = require('./web3')
const { getPastEvents: commonGetPastEvents } = require('../../commons')
const { MONITOR_BRIDGE_NAME, MONITOR_CACHE_EVENTS } = process.env
let isDirty = false
const homeTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/txSenders.json`
const cachedHomeTxSenders = readCacheFile(homeTxSendersCacheFile) || {}
const foreignTxSendersCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/txSenders.json`
const cachedForeignTxSenders = readCacheFile(foreignTxSendersCacheFile) || {}
const homeIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/home/isContract.json`
const cachedHomeIsContract = readCacheFile(homeIsContractCacheFile) || {}
const foreignIsContractCacheFile = `./cache/${MONITOR_BRIDGE_NAME}/foreign/isContract.json`
const cachedForeignIsContract = readCacheFile(foreignIsContractCacheFile) || {}
async function getHomeTxSender(txHash) {
if (!cachedHomeTxSenders[txHash]) {
logger.debug(`Fetching sender for home tx ${txHash}`)
cachedHomeTxSenders[txHash] = (await web3Home.eth.getTransaction(txHash)).from.toLowerCase()
isDirty = true
}
return cachedHomeTxSenders[txHash]
}
async function getForeignTxSender(txHash) {
if (!cachedForeignTxSenders[txHash]) {
logger.debug(`Fetching sender for foreign tx ${txHash}`)
cachedForeignTxSenders[txHash] = (await web3Foreign.eth.getTransaction(txHash)).from.toLowerCase()
isDirty = true
}
return cachedForeignTxSenders[txHash]
}
async function isHomeContract(address) {
if (typeof cachedHomeIsContract[address] !== 'boolean') {
logger.debug(`Fetching home contract code size for tx ${address}`)
cachedHomeIsContract[address] = (await web3Home.eth.getCode(address)).length > 2
isDirty = true
}
return cachedHomeIsContract[address]
}
async function isForeignContract(address) {
if (typeof cachedForeignIsContract[address] !== 'boolean') {
logger.debug(`Fetching foreign contract code size for tx ${address}`)
cachedForeignIsContract[address] = (await web3Foreign.eth.getCode(address)).length > 2
isDirty = true
}
return cachedForeignIsContract[address]
}
async function getPastEvents(contract, options) {
if (MONITOR_CACHE_EVENTS !== 'true') {
return commonGetPastEvents(contract, options)
}
const contractAddr = contract.options.address
let eventSignature
if (options.event.includes('(')) {
eventSignature = options.event
options.event = web3Home.utils.sha3(eventSignature)
const eventABI = contract.options.jsonInterface.find(e => e.type === 'event' && e.signature === options.event)
if (!eventABI) {
throw new Error(`Event ${eventSignature} not found`)
}
} else {
const eventABI = contract.options.jsonInterface.find(
e => e.type === 'event' && (e.name === options.event || e.signature === options.event)
)
if (!eventABI) {
throw new Error(`Event ${options.event} not found`)
}
eventSignature = `${eventABI.name}(${eventABI.inputs.map(i => i.type).join(',')})`
}
const cacheFile = `./cache/${MONITOR_BRIDGE_NAME}/${options.chain}/${contractAddr}/${eventSignature}.json`
const { fromBlock, toBlock } = options
const { fromBlock: cachedFromBlock, toBlock: cachedToBlock, events: cachedEvents } = readCacheFile(cacheFile) || {
fromBlock: 0,
toBlock: 0,
events: []
}
let result
if (cachedFromBlock > toBlock || fromBlock > cachedToBlock) {
// requested: A...B
// cached: C...D
// OR
// requested: A...B
// cached: C...D
logger.debug(`Fetching events for blocks ${fromBlock}...${toBlock}`)
result = await commonGetPastEvents(contract, options)
} else if (fromBlock < cachedFromBlock && toBlock <= cachedToBlock) {
// requested: A...B
// cached: C...D
logger.debug(`Cache hit for blocks ${cachedFromBlock}...${toBlock}`)
logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`)
result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents.filter(e => e.blockNumber <= toBlock)
]
} else if (fromBlock < cachedFromBlock && cachedToBlock < toBlock) {
// requested: A.....B
// cached: C.D
logger.debug(`Cache hit for blocks ${cachedFromBlock}...${cachedToBlock}`)
logger.debug(`Fetching events for blocks ${fromBlock}...${cachedFromBlock - 1}`)
logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`)
result = [
...(await commonGetPastEvents(contract, { ...options, toBlock: cachedFromBlock - 1 })),
...cachedEvents,
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 }))
]
} else if (cachedFromBlock <= fromBlock && toBlock <= cachedToBlock) {
// requested: A.B
// cached: C.....D
logger.debug(`Cache hit for blocks ${fromBlock}...${toBlock}`)
result = cachedEvents.filter(e => fromBlock <= e.blockNumber && e.blockNumber <= toBlock)
} else if (fromBlock >= cachedFromBlock && toBlock > cachedToBlock) {
// requested: A...B
// cached: C...D
logger.debug(`Cache hit for blocks ${fromBlock}...${cachedToBlock}`)
logger.debug(`Fetching events for blocks ${cachedToBlock + 1}...${toBlock}`)
result = [
...cachedEvents.filter(e => e.blockNumber >= fromBlock),
...(await commonGetPastEvents(contract, { ...options, fromBlock: cachedToBlock + 1 }))
]
} else {
throw new Error(
`Something is broken with cache resolution for getPastEvents,
requested blocks ${fromBlock}...${toBlock},
cached blocks ${cachedFromBlock}...${cachedToBlock}`
)
}
// it is not safe to cache events with too low block confirmations
// so, only events from finalized blocks are included into the cache
const safeToBlock = options.safeToBlock || toBlock
const cacheToSave = result.filter(e => e.blockNumber <= safeToBlock)
logger.debug(
`Saving events cache for ${MONITOR_BRIDGE_NAME}/${options.chain}/${contractAddr}/${eventSignature} on disk`
)
writeCacheFile(cacheFile, {
fromBlock,
toBlock: safeToBlock,
events: cacheToSave
})
return result
}
function saveCache() {
if (isDirty) {
logger.debug('Saving cache on disk')
writeCacheFile(homeTxSendersCacheFile, cachedHomeTxSenders)
writeCacheFile(homeIsContractCacheFile, cachedHomeIsContract)
writeCacheFile(foreignIsContractCacheFile, cachedForeignIsContract)
}
}
module.exports = {
getHomeTxSender,
getForeignTxSender,
isHomeContract,
isForeignContract,
getPastEvents,
saveCache
}

View File

@@ -1,13 +1,12 @@
require('dotenv').config()
const Web3 = require('web3')
const Web3Utils = require('web3').utils
const fetch = require('node-fetch')
const logger = require('./logger')('validators')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, getValidatorList, gasPriceFromSupplier } = require('../commons')
const { getBlockNumber } = require('./utils/contract')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, gasPriceFromSupplier } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
const { getValidatorList } = require('./utils/getValidatorsList')
const {
COMMON_HOME_RPC_URL,
COMMON_FOREIGN_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS,
COMMON_HOME_GAS_PRICE_SUPPLIER_URL,
@@ -17,20 +16,13 @@ const {
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL,
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE,
COMMON_FOREIGN_GAS_PRICE_FALLBACK,
COMMON_FOREIGN_GAS_PRICE_FACTOR
COMMON_FOREIGN_GAS_PRICE_FACTOR,
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE,
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE
} = process.env
const MONITOR_HOME_START_BLOCK = Number(process.env.MONITOR_HOME_START_BLOCK) || 0
const MONITOR_FOREIGN_START_BLOCK = Number(process.env.MONITOR_FOREIGN_START_BLOCK) || 0
const MONITOR_VALIDATOR_HOME_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_HOME_TX_LIMIT) || 0
const MONITOR_VALIDATOR_FOREIGN_TX_LIMIT = Number(process.env.MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) || 0
const Web3Utils = Web3.utils
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
const homeGasPriceSupplierOpts = {
speedType: COMMON_HOME_GAS_PRICE_SPEED_TYPE,
@@ -44,12 +36,6 @@ const foreignGasPriceSupplierOpts = {
logger
}
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
async function main(bridgeMode) {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, COMMON_HOME_BRIDGE_ADDRESS)
@@ -58,7 +44,12 @@ async function main(bridgeMode) {
const homeBridgeValidators = new web3Home.eth.Contract(BRIDGE_VALIDATORS_ABI, homeValidatorsAddress)
logger.debug('getting last block numbers')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const homeConfirmations = await homeBridge.methods.requiredBlockConfirmations().call()
const foreignConfirmations = await foreignBridge.methods.requiredBlockConfirmations().call()
const homeDelayedBlockNumber = homeBlockNumber - homeConfirmations
const foreignDelayedBlockNumber = foreignBlockNumber - foreignConfirmations
logger.debug('calling foreignBridge.methods.validatorContract().call()')
const foreignValidatorsAddress = await foreignBridge.methods.validatorContract().call()
@@ -66,16 +57,18 @@ async function main(bridgeMode) {
logger.debug('calling foreignBridgeValidators getValidatorList()')
const foreignValidators = (await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
from: MONITOR_FOREIGN_START_BLOCK,
to: foreignBlockNumber,
logger
toBlock: foreignBlockNumber,
logger,
chain: 'foreign',
safeToBlock: foreignDelayedBlockNumber
})).map(web3Foreign.utils.toChecksumAddress)
logger.debug('calling homeBridgeValidators getValidatorList()')
const homeValidators = (await getValidatorList(homeValidatorsAddress, web3Home.eth, {
from: MONITOR_HOME_START_BLOCK,
to: homeBlockNumber,
logger
toBlock: homeBlockNumber,
logger,
chain: 'home',
safeToBlock: homeDelayedBlockNumber
})).map(web3Home.utils.toChecksumAddress)
const foreignVBalances = {}
@@ -113,53 +106,61 @@ async function main(bridgeMode) {
}
let validatorsMatch = true
logger.debug('calling asyncForEach foreignValidators foreignVBalances')
await asyncForEach(foreignValidators, async v => {
const balance = await web3Foreign.eth.getBalance(v)
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
const leftTx = Web3Utils.toBN(balance)
.div(foreignTxCost)
.toString(10)
foreignVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(foreignGasPriceGwei)
const foreignValidatorsWithBalanceCheck =
typeof MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE === 'string'
? MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE.split(' ')
: foreignValidators
logger.debug('getting foreignValidators balances')
await Promise.all(
foreignValidators.map(async v => {
foreignVBalances[v] = {}
if (foreignValidatorsWithBalanceCheck.includes(v)) {
const balance = await web3Foreign.eth.getBalance(v)
foreignVBalances[v].balance = Web3Utils.fromWei(balance)
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
foreignVBalances[v].leftTx = Number(
Web3Utils.toBN(balance)
.div(foreignTxCost)
.toString(10)
)
foreignVBalances[v].gasPrice = parseFloat(foreignGasPriceGwei)
}
}
} else {
foreignVBalances[v] = {
balance: Web3Utils.fromWei(balance)
}
}
if (!homeValidators.includes(v)) {
validatorsMatch = false
foreignVBalances[v].onlyOnForeign = true
}
})
logger.debug('calling asyncForEach homeValidators homeVBalances')
await asyncForEach(homeValidators, async v => {
const balance = await web3Home.eth.getBalance(v)
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
const leftTx = Web3Utils.toBN(balance)
.div(homeTxCost)
.toString(10)
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(homeGasPriceGwei)
if (!homeValidators.includes(v)) {
validatorsMatch = false
foreignVBalances[v].onlyOnForeign = true
}
} else {
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance)
}
}
})
)
if (!foreignValidators.includes(v)) {
validatorsMatch = false
homeVBalances[v].onlyOnHome = true
}
})
const homeValidatorsWithBalanceCheck =
typeof MONITOR_HOME_VALIDATORS_BALANCE_ENABLE === 'string'
? MONITOR_HOME_VALIDATORS_BALANCE_ENABLE.split(' ')
: homeValidators
logger.debug('calling homeValidators balances')
await Promise.all(
homeValidators.map(async v => {
homeVBalances[v] = {}
if (homeValidatorsWithBalanceCheck.includes(v)) {
const balance = await web3Home.eth.getBalance(v)
homeVBalances[v].balance = Web3Utils.fromWei(balance)
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
homeVBalances[v].leftTx = Number(
Web3Utils.toBN(balance)
.div(homeTxCost)
.toString(10)
)
homeVBalances[v].gasPrice = parseFloat(homeGasPriceGwei)
}
}
if (!foreignValidators.includes(v)) {
validatorsMatch = false
homeVBalances[v].onlyOnHome = true
}
})
)
logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()')
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
@@ -168,20 +169,22 @@ async function main(bridgeMode) {
logger.debug('Done')
return {
home: {
validators: {
...homeVBalances
},
validators: homeVBalances,
requiredSignatures: Number(reqSigHome)
},
foreign: {
validators: {
...foreignVBalances
},
validators: foreignVBalances,
requiredSignatures: Number(reqSigForeign)
},
requiredSignaturesMatch: reqSigHome === reqSigForeign,
validatorsMatch,
lastChecked: Math.floor(Date.now() / 1000)
lastChecked: Math.floor(Date.now() / 1000),
homeOk: Object.values(homeVBalances)
.filter(vb => typeof vb.leftTx === 'number')
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD),
foreignOk: Object.values(foreignVBalances)
.filter(vb => typeof vb.leftTx === 'number')
.every(vb => vb.leftTx >= MONITOR_TX_NUMBER_THRESHOLD)
}
}

View File

@@ -1,8 +1,24 @@
cd $(dirname $0)
../e2e-commons/up.sh deploy blocks oracle oracle-validator-2 oracle-validator-3
mode="$1"
case "$mode" in
amb)
script=./test/amb.js
;;
native-to-erc)
script=./test/nativeToErc.js
;;
erc-to-erc)
script=./test/ercToErc.js
;;
erc-to-native)
script=./test/ercToNative.js
;;
esac
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run start
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=$?
../e2e-commons/down.sh

View File

@@ -3,7 +3,7 @@ const assert = require('assert')
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 } = require('../../commons')
const { setRequiredSignatures } = require('./utils')
const { delay, setRequiredSignatures } = require('./utils')
const { toBN } = Web3.utils
@@ -19,14 +19,17 @@ foreignWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
const homeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.homeBox)
const blockHomeBox = new homeWeb3.eth.Contract(BOX_ABI, amb.blockedHomeBox)
const foreignBox = new foreignWeb3.eth.Contract(BOX_ABI, amb.foreignBox)
const homeBridge = new homeWeb3.eth.Contract(HOME_AMB_ABI, COMMON_HOME_BRIDGE_ADDRESS)
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
describe('arbitrary message bridging', () => {
let requiredSignatures = 1
before(async () => {
// Only 1 validator is used in ultimate tests
if (process.env.ULTIMATE !== 'true') {
requiredSignatures = 2
// Set 2 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
@@ -76,6 +79,85 @@ describe('arbitrary message bridging', () => {
}
})
})
// allowance/block lists files are not mounted to the host during the ultimate test
if (process.env.ULTIMATE !== 'true') {
it('should confirm but not relay message from blocked contract', async () => {
const newValue = 4
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'
})
await blockHomeBox.methods
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
.send({
from: user.address,
gas: '400000'
})
.catch(e => {
console.error(e)
})
await delay(5000)
const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,
toBlock: 'latest'
})
assert(
newSignatures.length === signatures.length + requiredSignatures,
`Incorrect amount of signatures submitted, got ${newSignatures.length}, expected ${signatures.length +
requiredSignatures}`
)
const value = await foreignBox.methods.value().call()
assert(!toBN(value).eq(toBN(newValue)), 'Message should not be relayed by oracle automatically')
})
}
it('should confirm but not relay message from manual lane', async () => {
const newValue = 5
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'
})
await homeBox.methods
.setValueOnOtherNetworkUsingManualLane(newValue, amb.home, amb.foreignBox)
.send({
from: user.address,
gas: '400000'
})
.catch(e => {
console.error(e)
})
await delay(10000)
const newSignatures = await homeBridge.getPastEvents('SignedForUserRequest', {
fromBlock: 0,
toBlock: 'latest'
})
assert(
newSignatures.length === signatures.length + requiredSignatures,
`Incorrect amount of signatures submitted, got ${newSignatures.length}, expected ${signatures.length +
requiredSignatures}`
)
const value = await foreignBox.methods.value().call()
assert(!toBN(value).eq(toBN(newValue)), 'Message should not be relayed by oracle automatically')
})
})
})
describe('Foreign to Home', () => {

View File

@@ -1,5 +1,9 @@
const { BRIDGE_VALIDATORS_ABI } = require('../../commons')
async function delay(ms) {
return new Promise(res => setTimeout(res, ms))
}
const setRequiredSignatures = async ({ bridgeContract, web3, requiredSignatures, options }) => {
const validatorAddress = await bridgeContract.methods.validatorContract().call()
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
@@ -8,5 +12,6 @@ const setRequiredSignatures = async ({ bridgeContract, web3, requiredSignatures,
}
module.exports = {
delay,
setRequiredSignatures
}

View File

@@ -29,6 +29,7 @@ ORACLE_FOREIGN_START_BLOCK=
ORACLE_LOG_LEVEL=debug
ORACLE_MAX_PROCESSING_TIME=20000
ORACLE_RPC_REQUEST_TIMEOUT=5000
ORACLE_HOME_TO_FOREIGN_ALLOWANCE_LIST=access-lists/allowance_list.txt
ORACLE_HOME_TO_FOREIGN_BLOCK_LIST=access-lists/block_list.txt

View File

@@ -1,6 +1,6 @@
const baseConfig = require('./base.config')
const { web3Foreign } = require('../src/services/web3')
const { web3Foreign, web3ForeignRedundant, web3ForeignFallback } = require('../src/services/web3')
module.exports = {
...baseConfig.bridgeConfig,
@@ -8,5 +8,7 @@ module.exports = {
oldQueue: 'foreign',
id: 'foreign',
name: 'sender-foreign',
web3: web3Foreign
web3: web3Foreign,
web3Redundant: web3ForeignRedundant,
web3Fallback: web3ForeignFallback
}

View File

@@ -1,6 +1,6 @@
const baseConfig = require('./base.config')
const { web3Home } = require('../src/services/web3')
const { web3Home, web3HomeRedundant, web3HomeFallback } = require('../src/services/web3')
module.exports = {
...baseConfig.bridgeConfig,
@@ -8,5 +8,7 @@ module.exports = {
oldQueue: 'home',
id: 'home',
name: 'sender-home',
web3: web3Home
web3: web3Home,
web3Redundant: web3HomeRedundant,
web3Fallback: web3HomeFallback
}

View File

@@ -26,15 +26,13 @@
"amqplib": "^0.5.2",
"bignumber.js": "^7.2.1",
"dotenv": "^5.0.1",
"http-list-provider": "0.0.5",
"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.0.0-beta.34",
"web3-utils": "1.0.0-beta.34"
"web3": "^1.3.0"
},
"devDependencies": {
"bn-chai": "^1.0.1",

View File

@@ -1,8 +1,7 @@
require('../../env')
const Web3 = require('web3')
const Web3Utils = require('web3-utils')
const rpcUrlsManager = require('../../src/services/getRpcUrlsManager')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { toWei } = require('web3').utils
const { web3Foreign } = require('../../src/services/web3')
const { sendTx } = require('../../src/tx/sendTx')
const { ERC20_ABI } = require('../../../commons')
const {
@@ -17,37 +16,23 @@ const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || process.env.NUMBER_OF_DEPO
const { FOREIGN_ERC_TO_ERC_ABI } = require('../../../commons')
const foreignRpcUrl = rpcUrlsManager.foreignUrls[0]
const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl)
const web3Foreign = new Web3(foreignProvider)
async function main() {
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const bridgeableTokenAddress = await bridge.methods.erc20token().call()
const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, bridgeableTokenAddress)
try {
const foreignChainId = await sendRawTx({
chain: 'foreign',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'foreign',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const foreignChainId = await web3Foreign.eth.getChainId()
let nonce = await web3Foreign.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) {
const gasLimit = await poa20.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.estimateGas({ from: USER_ADDRESS })
const data = await poa20.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.encodeABI({ from: USER_ADDRESS })
const txHash = await sendTx({
chain: 'foreign',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data,
nonce,

View File

@@ -1,8 +1,7 @@
require('../../env')
const Web3 = require('web3')
const Web3Utils = require('web3-utils')
const rpcUrlsManager = require('../../src/services/getRpcUrlsManager')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { toWei } = require('web3').utils
const { web3Home } = require('../../src/services/web3')
const { sendTx } = require('../../src/tx/sendTx')
const { isValidAmount } = require('../utils/utils')
const { HOME_ERC_TO_ERC_ABI } = require('../../../commons')
@@ -46,10 +45,6 @@ const BRIDGEABLE_TOKEN_ABI = [
}
]
const homeRpcUrl = rpcUrlsManager.homeUrls[0]
const homeProvider = new Web3.providers.HttpProvider(homeRpcUrl)
const web3Home = new Web3(homeProvider)
async function main() {
const bridge = new web3Home.eth.Contract(HOME_ERC_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
const BRIDGEABLE_TOKEN_ADDRESS = await bridge.methods.erc677token().call()
@@ -58,27 +53,17 @@ async function main() {
try {
await isValidAmount(HOME_MIN_AMOUNT_PER_TX, bridge)
const homeChainId = await sendRawTx({
chain: 'home',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'home',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const homeChainId = await web3Home.eth.getChainId()
let nonce = await web3Home.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) {
const gasLimit = await erc677.methods
.transferAndCall(COMMON_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x')
.transferAndCall(COMMON_HOME_BRIDGE_ADDRESS, toWei(HOME_MIN_AMOUNT_PER_TX), '0x')
.estimateGas({ from: USER_ADDRESS })
const data = await erc677.methods
.transferAndCall(COMMON_HOME_BRIDGE_ADDRESS, Web3Utils.toWei(HOME_MIN_AMOUNT_PER_TX), '0x')
.transferAndCall(COMMON_HOME_BRIDGE_ADDRESS, toWei(HOME_MIN_AMOUNT_PER_TX), '0x')
.encodeABI({ from: USER_ADDRESS })
const txHash = await sendTx({
chain: 'home',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data,
nonce,

View File

@@ -1,8 +1,7 @@
require('../../env')
const Web3 = require('web3')
const Web3Utils = require('web3-utils')
const rpcUrlsManager = require('../../src/services/getRpcUrlsManager')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { toWei } = require('web3').utils
const { web3Foreign } = require('../../src/services/web3')
const { sendTx } = require('../../src/tx/sendTx')
const {
USER_ADDRESS,
@@ -16,37 +15,23 @@ const NUMBER_OF_DEPOSITS_TO_SEND = process.argv[2] || process.env.NUMBER_OF_DEPO
const { ERC20_ABI, FOREIGN_ERC_TO_NATIVE_ABI } = require('../../../commons')
const foreignRpcUrl = rpcUrlsManager.foreignUrls[0]
const foreignProvider = new Web3.providers.HttpProvider(foreignRpcUrl)
const web3Foreign = new Web3(foreignProvider)
async function main() {
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const bridgeableTokenAddress = await bridge.methods.erc20token().call()
const poa20 = new web3Foreign.eth.Contract(ERC20_ABI, bridgeableTokenAddress)
try {
const foreignChainId = await sendRawTx({
chain: 'foreign',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'foreign',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const foreignChainId = await web3Foreign.eth.getChainId()
let nonce = await web3Foreign.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) {
const gasLimit = await poa20.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.estimateGas({ from: USER_ADDRESS })
const data = await poa20.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX))
.encodeABI({ from: USER_ADDRESS })
const txHash = await sendTx({
chain: 'foreign',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data,
nonce,

View File

@@ -1,7 +1,6 @@
require('../../env')
const Web3Utils = require('web3-utils')
const { web3Home } = require('../../src/services/web3')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { sendTx } = require('../../src/tx/sendTx')
const { isValidAmount } = require('../utils/utils')
const { HOME_ERC_TO_NATIVE_ABI } = require('../../../commons')
@@ -21,21 +20,11 @@ async function main() {
try {
await isValidAmount(HOME_MIN_AMOUNT_PER_TX, bridge)
const homeChainId = await sendRawTx({
chain: 'home',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'home',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const homeChainId = await web3Home.eth.getChainId()
let nonce = await web3Home.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) {
const txHash = await sendTx({
chain: 'home',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data: '0x',
nonce,

View File

@@ -1,23 +1,20 @@
require('../env')
const Web3 = require('web3')
const { BRIDGE_VALIDATORS_ABI } = require('../../commons')
const rpcUrlsManager = require('../src/services/getRpcUrlsManager')
const { web3Home, web3Foreign } = require('../src/services/web3')
const { bridgeConfig } = require('../config/base.config')
const homeABI = bridgeConfig.homeBridgeAbi
const foreignABI = bridgeConfig.foreignBridgeAbi
async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) {
async function getStartBlock(web3, bridgeAddress, bridgeAbi) {
try {
const web3Provider = new Web3.providers.HttpProvider(rpcUrl)
const web3Instance = new Web3(web3Provider)
const bridgeContract = new web3Instance.eth.Contract(bridgeAbi, bridgeAddress)
const bridgeContract = new web3.eth.Contract(bridgeAbi, bridgeAddress)
const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call()
const validatorContractAddress = await bridgeContract.methods.validatorContract().call()
const validatorContract = new web3Instance.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
const validatorDeployedAtBlock = await validatorContract.methods.deployedAtBlock().call()
@@ -35,10 +32,8 @@ async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) {
async function main() {
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const homeRpcUrl = rpcUrlsManager.homeUrls[0]
const foreignRpcUrl = rpcUrlsManager.foreignUrls[0]
const homeStartBlock = await getStartBlock(homeRpcUrl, COMMON_HOME_BRIDGE_ADDRESS, homeABI)
const foreignStartBlock = await getStartBlock(foreignRpcUrl, COMMON_FOREIGN_BRIDGE_ADDRESS, foreignABI)
const homeStartBlock = await getStartBlock(web3Home, COMMON_HOME_BRIDGE_ADDRESS, homeABI)
const foreignStartBlock = await getStartBlock(web3Foreign, COMMON_FOREIGN_BRIDGE_ADDRESS, foreignABI)
const result = {
homeStartBlock,
foreignStartBlock

View File

@@ -1,5 +1,4 @@
require('../env')
const Web3 = require('web3')
const { getTokensState } = require('../src/utils/tokenState')
const {
ERC677_BRIDGE_TOKEN_ABI,
@@ -7,6 +6,7 @@ const {
FOREIGN_ERC_TO_NATIVE_ABI,
getTokenType
} = require('../../commons')
const { web3Foreign } = require('../src/services/web3')
const emptyLogger = {
debug: () => {},
@@ -14,26 +14,25 @@ const emptyLogger = {
}
async function initialChecks() {
const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const { ORACLE_BRIDGE_MODE, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
let result = {}
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(COMMON_FOREIGN_RPC_URL))
if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') {
const bridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
result.bridgeableTokenAddress = await bridge.methods.erc20token().call()
} else if (ORACLE_BRIDGE_MODE === 'ERC_TO_NATIVE') {
const bridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const bridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
result = await getTokensState(bridge, emptyLogger)
}
if (ORACLE_BRIDGE_MODE === 'ERC_TO_ERC') {
const bridgeTokenContract = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, result.bridgeableTokenAddress)
const bridgeTokenContract = new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, result.bridgeableTokenAddress)
result.foreignERC = await getTokenType(bridgeTokenContract, COMMON_FOREIGN_BRIDGE_ADDRESS)
}
console.log(JSON.stringify(result))
return result
}
initialChecks()
const result = initialChecks()
module.exports = initialChecks
module.exports = result

View File

@@ -1,7 +1,7 @@
require('../../env')
const Web3Utils = require('web3-utils')
const { toWei } = require('web3').utils
const { web3Foreign } = require('../../src/services/web3')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { sendTx } = require('../../src/tx/sendTx')
const { isValidAmount } = require('../utils/utils')
const { FOREIGN_NATIVE_TO_ERC_ABI } = require('../../../commons')
@@ -53,27 +53,17 @@ async function main() {
try {
await isValidAmount(FOREIGN_MIN_AMOUNT_PER_TX, bridge)
const foreignChainId = await sendRawTx({
chain: 'foreign',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'foreign',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const foreignChainId = await web3Foreign.eth.getChainId()
let nonce = await web3Foreign.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_WITHDRAWALS_TO_SEND); i++) {
const gasLimit = await poa20.methods
.transferAndCall(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x')
.transferAndCall(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x')
.estimateGas({ from: USER_ADDRESS })
const data = await poa20.methods
.transferAndCall(COMMON_FOREIGN_BRIDGE_ADDRESS, Web3Utils.toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x')
.transferAndCall(COMMON_FOREIGN_BRIDGE_ADDRESS, toWei(FOREIGN_MIN_AMOUNT_PER_TX), '0x')
.encodeABI({ from: USER_ADDRESS })
const txHash = await sendTx({
chain: 'foreign',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data,
nonce,

View File

@@ -1,7 +1,6 @@
require('../../env')
const Web3Utils = require('web3-utils')
const { web3Home } = require('../../src/services/web3')
const { sendTx, sendRawTx } = require('../../src/tx/sendTx')
const { sendTx } = require('../../src/tx/sendTx')
const { isValidAmount } = require('../utils/utils')
const { HOME_NATIVE_TO_ERC_ABI } = require('../../../commons')
@@ -21,21 +20,11 @@ async function main() {
try {
await isValidAmount(HOME_MIN_AMOUNT_PER_TX, bridge)
const homeChainId = await sendRawTx({
chain: 'home',
params: [],
method: 'net_version'
})
let nonce = await sendRawTx({
chain: 'home',
method: 'eth_getTransactionCount',
params: [USER_ADDRESS, 'latest']
})
nonce = Web3Utils.hexToNumber(nonce)
const homeChainId = await web3Home.eth.getChainId()
let nonce = await web3Home.eth.getTransactionCount(USER_ADDRESS)
let actualSent = 0
for (let i = 0; i < Number(NUMBER_OF_DEPOSITS_TO_SEND); i++) {
const txHash = await sendTx({
chain: 'home',
privateKey: USER_ADDRESS_PRIVATE_KEY,
data: '0x',
nonce,

View File

@@ -1,8 +1,8 @@
const Web3Utils = require('web3-utils')
const { fromWei } = require('web3').utils
async function getMinPerTxLimit(bridge) {
const minPerTx = await bridge.methods.minPerTx().call()
return Web3Utils.fromWei(minPerTx)
return fromWei(minPerTx)
}
async function isValidAmount(amount, bridge) {

View File

@@ -3,7 +3,6 @@ const path = require('path')
const { isAttached, connectWatcherToQueue, connection } = require('./services/amqpClient')
const logger = require('./services/logger')
const GasPrice = require('./services/gasPrice')
const rpcUrlsManager = require('./services/getRpcUrlsManager')
const { getNonce, getChainId, getEventsFromTx } = require('./tx/web3')
const { sendTx } = require('./tx/sendTx')
const { checkHTTPS, watchdog, syncForEach, addExtraGas } = require('./utils/utils')
@@ -37,8 +36,7 @@ async function initialize() {
try {
const checkHttps = checkHTTPS(ORACLE_ALLOW_HTTP_FOR_RPC, logger)
rpcUrlsManager.homeUrls.forEach(checkHttps('home'))
rpcUrlsManager.foreignUrls.forEach(checkHttps('foreign'))
web3Instance.currentProvider.urls.forEach(checkHttps(config.chain))
attached = await isAttached()
if (attached) {
@@ -139,7 +137,7 @@ async function main({ sendJob, txHash }) {
async function sendJobTx(jobs) {
const gasPrice = await GasPrice.start(config.chain, true)
const chainId = await getChainId(config.chain)
const chainId = await getChainId(web3Instance)
let nonce = await getNonce(web3Instance, ORACLE_VALIDATOR_ADDRESS)
await syncForEach(jobs, async job => {
@@ -153,7 +151,6 @@ async function sendJobTx(jobs) {
try {
logger.info(`Sending transaction with nonce ${nonce}`)
const txHash = await sendTx({
chain: config.chain,
data: job.data,
nonce,
gasPrice: gasPrice.toString(10),

View File

@@ -1,4 +1,4 @@
const { HttpListProviderError } = require('http-list-provider')
const { HttpListProviderError } = require('../../services/HttpListProvider')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processAffirmationRequests:estimateGas'

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