Compare commits

...

77 Commits

Author SHA1 Message Date
Alexander Kolotov
5b4b01a79b Merge the develop branch to the master branch, preparation to v2.0.0 2020-03-23 16:43:32 +03:00
Alexander Kolotov
881fafe9a8 Update the contract's submodule to the release 4.0.1 (#301) 2020-03-14 00:22:52 +03:00
Kirill Fedoseev
c1ed6f21e6 Correct handling of Chai to Dai swaps in oracle and monitor components (#302) 2020-03-13 01:12:48 +03:00
Alexander Kolotov
8ae0fa82d5 Merge the develop branch to the master branch, preparation to v2.0.0-rc1 2020-02-21 18:42:19 +03:00
Alexander Kolotov
dbf3d3d90d Composer files to build oracle and monitor docker images (#299) 2020-02-21 16:58:05 +03:00
Alexander Kolotov
8977ed6d3b Removal of putting .env file into the docker image for monitor (#289) 2020-02-21 16:56:55 +03:00
Alexander Kolotov
df0dc1c313 Update the contract's submodule to the release 4.0.0-rc1 (#298) 2020-02-21 10:17:18 +03:00
Gerardo Nardelli
7b2bebbcf0 Update metamask extension in ui e2e tests (#297) 2020-02-20 22:13:18 +03:00
Kirill Fedoseev
85104d67c0 Support of Chai token by monitor in erc-to-native (#287) 2020-02-18 19:17:01 +03:00
Kirill Fedoseev
2a3d7c8e08 Oracle support for erc-to-native with Chai integrated (#286) 2020-02-17 23:48:43 +03:00
Kirill Fedoseev
e6052f162a Fix some of the possibles failure reasons in e2e tests (#294) 2020-02-11 20:50:34 +03:00
Gerardo Nardelli
52ed4e85e2 Upgrade pip version in deployment common (#296) 2020-02-10 19:31:36 +03:00
Alexander Kolotov
bc6dd13193 Merge the develop branch to the master branch, preparation to v2.0.0-rc0 2020-02-04 14:33:16 +03:00
Gerardo Nardelli
bce1e6509e Use monitor docker hub image for monitor deployment (#284) 2020-02-04 08:16:27 +03:00
Gerardo Nardelli
52358d477b Use oracle docker hub image for oracle deployment (#280) 2020-02-04 00:48:28 +03:00
Gerardo Nardelli
232f807e9d Add ultimate e2e tests for AMB (#285) 2020-02-03 19:39:21 +03:00
Gerardo Nardelli
fe4a569e34 Update monitor check-all script to generate stuckTransfers statistics for v1 bridges (#279) 2020-02-03 15:50:18 +03:00
Gerardo Nardelli
c408d57716 Add custom response for favicon resource in monitor (#278) 2020-02-03 15:48:28 +03:00
Gerardo Nardelli
7fcb118c8c Add deployment test scenario 3 components on 1 host (#277) 2020-02-03 15:47:44 +03:00
Alexander Kolotov
1eaf774e33 Update the contract's submodule to the release 4.0.0-rc0 (#276) 2020-01-22 22:51:28 +03:00
Kirill Fedoseev
8650ba4d21 Updated oracle watchers to use blob version of execute signatures (#269) 2020-01-21 00:11:26 +03:00
Gerardo Nardelli
edc51c78e2 Use 3 validators in oracle e2e tests (#264) 2020-01-20 23:00:04 +03:00
Gerardo Nardelli
612f130544 Fix remote server loop var name in deployment (#275) 2020-01-20 22:53:20 +03:00
Alexander Kolotov
73d5002105 Merge the develop branch to the master branch, preparation to v1.3.0-rc0 2020-01-13 18:09:19 +03:00
Gerardo Nardelli
65dd131107 Support multiple bridges in one monitor (#262) 2020-01-10 15:55:34 +03:00
Alexander Kolotov
4de24efc01 Security audit report provided by Quantstamp for TokenBridge contracts (#263) 2020-01-10 15:48:41 +03:00
Alexander Kolotov
727371f251 Merge the develop branch to the master branch, preparation to v1.2.0 2020-01-06 22:09:12 +03:00
Gerardo Nardelli
9cb1a2041d Add support to disable validator balances check in monitor (#259)
* Add support to disable validator balance check in monitor
* Convert validator address to checksum address
2020-01-04 15:44:57 +04:00
Igor Barinov
b6d96d7f62 Update README.md (#260) 2020-01-03 01:04:39 +04:00
Alexander Kolotov
dc06ee8ceb Update the contract's submodule to the release 3.3.0 (#258) 2019-12-30 23:45:27 +04:00
Alexander Kolotov
8c2f58b06f Oracle upgrade script to migrate from 1.1.1 to 1.2.0-rc0 (#257) 2019-12-30 17:15:49 +04:00
dependabot[bot]
3ad62d6a7f Bump handlebars from 4.1.2 to 4.5.3 (#255) 2019-12-30 17:14:56 +04:00
Alexander Kolotov
9f9638970a Add handling of error case with RPC links in getTokensState (#252)
* add handling for error case and extend logging
2019-12-23 18:59:03 +03:00
Gerardo Nardelli
7054ff26a0 Add rabbit and redis networks to new workers (#249) 2019-12-22 15:44:06 +03:00
Alexander Kolotov
afb601b7f5 Merge the develop branch to the master branch, preparation to v1.2.0-rc0 2019-12-19 19:30:30 +03:00
Alexander Kolotov
1736fd615d Update the contract's submodule to the release 3.3.0-rc0 (#247) 2019-12-19 18:53:00 +03:00
Gerardo Nardelli
ef0a734650 Support two tokens deposits in monitor (#245)
* Support two tokens deposits in monitor
* update chrome version
2019-12-19 12:39:41 +03:00
Alexander Kolotov
6e2238fc9b Merge branch 'master' into develop 2019-12-05 23:46:13 +03:00
Alexander Kolotov
0f3bea5a41 Merge pull request #244 from poanetwork/support-two-tokens-oracle
Support two tokens deposit requests in Oracle
2019-12-05 23:42:30 +03:00
Gerardo Nardelli
0829c95561 Move tokenState file to utils 2019-12-05 13:50:55 -03:00
Gerardo Nardelli
5bb99a7e95 Update token used in erc-native monitor-e2e 2019-12-05 09:48:55 -03:00
Gerardo Nardelli
3cd53f7bda Update watcher to be able to skip events 2019-12-04 17:18:14 -03:00
Gerardo Nardelli
0eeae74ffa Update log
Co-Authored-By: Alexander Kolotov <alexandr.kolotov@gmail.com>
2019-12-04 09:59:34 -03:00
Gerardo Nardelli
8fa715089b Add watcher idle option 2019-12-04 09:58:27 -03:00
Gerardo Nardelli
ab2c0ea120 Log block timestamp 2019-12-03 17:17:27 -03:00
Gerardo Nardelli
5583ea8b6b Catch zero balance error in swap tokens worker 2019-12-03 17:17:24 -03:00
Gerardo Nardelli
a4eb446f7b Rename syslog logging cofig file 2019-12-03 17:17:16 -03:00
Gerardo Nardelli
2d526a1454 Fix wording
Co-Authored-By: Alexander Kolotov <alexandr.kolotov@gmail.com>
2019-12-03 11:38:37 -03:00
Gerardo Nardelli
9811c13a04 Typo fix 2019-12-03 10:16:23 -03:00
Gerardo Nardelli
406ede9352 Add e2e tests two tokens support 2019-12-02 17:20:53 -03:00
Gerardo Nardelli
1360c79e69 Fix half duplex transfer watcher 2019-12-02 17:19:28 -03:00
Gerardo Nardelli
12229e5e0b Use pre-deployed token for erc to native e2e tests 2019-11-29 15:32:53 -03:00
Gerardo Nardelli
588b289bb9 remove comment attribute in chain spec 2019-11-29 13:16:31 -03:00
Gerardo Nardelli
b3419ccca6 Add parity hardcoded addresses for erc to native 2019-11-29 11:57:45 -03:00
Gerardo Nardelli
ecd20890c8 Add sai contract bytecode in foreign parity chain config 2019-11-29 09:56:48 -03:00
Gerardo Nardelli
b6588ff3c5 Add erc to native docker config in deployment playbooks 2019-11-29 09:28:50 -03:00
Gerardo Nardelli
ed2de112a2 fixes 2019-11-28 16:56:18 -03:00
Gerardo Nardelli
c19f48ef3f Add swap-tokens worker 2019-11-28 16:31:27 -03:00
Gerardo Nardelli
eb8de323ee Add half duplex transfer watcher 2019-11-26 17:00:56 -03:00
Gerardo Nardelli
c42b2f03b7 Update submodule to phase 2 contracts 2019-11-26 16:59:46 -03:00
Alexander Kolotov
303b02f3ca Merge the develop branch to the master branch, preparation to v1.1.1 (#241) 2019-11-19 19:03:06 +03:00
Alexander Kolotov
98e0f8e998 Merge branch 'master' into develop 2019-11-19 16:49:27 +03:00
Alexander Kolotov
ecf613954a Changes in initialization of array to iterate getting signatures (#240) 2019-11-19 16:47:02 +03:00
Alexander Kolotov
f2a6a64637 Merge the develop branch to the master branch, preparation to v1.1.0 (#238) 2019-11-15 22:13:03 +01:00
Gerardo Nardelli
8d4eb86a19 Fix logs and start block parameters in oracle deployment (#235)
* Update start block env vars in oracle deployment
* Set logger to remote server for docker-compose-transfer.yml
* avoid calculating start block parameter if provided in oracle deployment config
* set home and foreign start block in erc20-native e2e
* set home and foreign start block in erc20-native ultimate test
2019-11-15 21:31:13 +01:00
phahulin
cc6afb3736 Fix monitor path in crontab example (#236)
This is a minor PR which fixes path in crontab example for monorepo
2019-11-15 06:16:21 +01:00
phahulin
84ecfc30d9 Optional ability to point ORACLE_LOG_LEVEL in the deployment configuration (#234) 2019-11-13 20:18:46 +01:00
Gerardo Nardelli
5d770e8607 Add AMB monitor e2e tests (#231)
* Add amb monitor e2e tests
* Fix eventsStats endpoint monitor
2019-11-13 07:51:10 +01:00
Alexander Kolotov
db89d1c12e Merge the develop branch to the master branch, preparation to v1.0.0 #230
* Support alternative receiver in Oracle (#221)
* Support alternative receiver feature in Monitor (#223)
* Support token migration (#224)
* Fix suggested gas price in transaction for ui production build (#222)
* Updated links to new repo with tokenbridge contracts (#226)
* Update the contract's submodule to the release 3.2.0 (#228)
2019-11-12 06:37:23 +01:00
Gerardo Nardelli
4fd4ac3d73 Merge branch 'master' into develop
# Conflicts:
#	commons/constants.js
#	e2e-commons/up.sh
#	monitor/alerts.js
#	monitor/eventsStats.js
#	monitor/utils/events.js
#	monitor/utils/message.js
2019-11-08 09:53:06 -03:00
Alexander Kolotov
cbd9d607ce Update the contract's submodule to the release 3.2.0 (#228) 2019-11-07 22:50:37 +01:00
Gerardo Nardelli
346fa1e732 Support token migration (#224)
* Filter transfer event in token swap in Oracle
* Support token swap in monitor
2019-11-06 22:49:01 +03:00
Alexander Kolotov
f6fa83d7ea Updated links to new repo with tokenbridge contracts (#226) 2019-11-05 15:53:47 +03:00
Gerardo Nardelli
1564ccc580 Support alternative receiver feature in Monitor (#223)
* Update monitor to support changes from alternative receiver
* Add monitor event processing unit tests
* update chrome version used en e2e tests
* update chromedriver version
2019-10-29 22:22:02 +03:00
Gerardo Nardelli
7a54e584d5 Support alternative receiver in Oracle (#221)
* Add transfer watcher
* Filter userRequestForAffirmation events in Transfer events
* Add extended oracle composer file
2019-10-29 17:55:47 +03:00
Gerardo Nardelli
1d79cf82f3 Fix suggested gas price in trasaction for ui production build (#222)
* use string version of bignumber when converting gasPrice to hex
* update chromedriver version
2019-10-25 15:38:30 +03:00
Gerardo Nardelli
d577a71096 Add support for AMB contracts (#199) 2019-09-18 22:45:13 +03:00
143 changed files with 4185 additions and 6987 deletions

View File

@@ -11,10 +11,10 @@ orbs:
sudo apt-get clean
sudo apt-get update
sudo apt-get install dpkg
- run:
- run:
name: Install Chrome
command: |
wget -O chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
wget -O chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_77.0.3865.120-1_amd64.deb
sudo dpkg -i chrome.deb
install-node:
steps:
@@ -89,29 +89,29 @@ jobs:
- checkout
- run: git submodule update --init
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- run: git submodule status > submodule.status
name: Restore Yarn Package Cache
keys:
- yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- run: git submodule status > submodule.status
- restore_cache:
name: Restore contracts submodule with compiled contracts
keys:
- contracts-{{ checksum "submodule.status" }}
name: Restore contracts submodule with compiled contracts
keys:
- contracts-{{ checksum "submodule.status" }}
- run: yarn install --frozen-lockfile
- save_cache:
name: Save Yarn Package Cache
key: yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
name: Save Yarn Package Cache
key: yarn-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: touch install_deploy.log; test -d contracts/build/contracts || yarn install:deploy &> install_deploy.log
- store_artifacts:
path: install_deploy.log
- run: test -d contracts/build/contracts || yarn compile:contracts
- save_cache:
name: Save contracts submodule with compiled contracts
key: contracts-{{ checksum "submodule.status" }}
paths:
- contracts
name: Save contracts submodule with compiled contracts
key: contracts-{{ checksum "submodule.status" }}
paths:
- contracts
- save_cache:
name: Save initialized project for subsequent jobs
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
@@ -143,11 +143,11 @@ jobs:
oracle-e2e:
executor: tokenbridge-orb/docker-node
steps:
- checkout
- run: git submodule update --init
- setup_remote_docker:
docker_layer_caching: true
- run: yarn run oracle-e2e
- checkout
- run: git submodule update --init
- setup_remote_docker:
docker_layer_caching: true
- run: yarn run oracle-e2e
ui-e2e:
executor: tokenbridge-orb/machine-with-docker-caching
steps:
@@ -161,9 +161,9 @@ jobs:
monitor-e2e:
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: git submodule update --init
- run: ./monitor-e2e/run-tests.sh
- checkout
- run: git submodule update --init
- run: ./monitor-e2e/run-tests.sh
cover:
executor: tokenbridge-orb/docker-node
steps:
@@ -194,7 +194,7 @@ jobs:
steps:
- checkout
- run: git submodule update --init
- run:
- run:
name: Run the scenario
command: deployment-e2e/molecule.sh monitor
no_output_timeout: 40m
@@ -210,7 +210,15 @@ jobs:
name: Run the scenario
command: deployment-e2e/molecule.sh repo
no_output_timeout: 40m
deployment-multiple:
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: git submodule update --init
- run:
name: Run the scenario
command: deployment-e2e/molecule.sh multiple
no_output_timeout: 40m
ultimate:
executor: tokenbridge-orb/machine-with-docker-caching
parameters:
@@ -222,6 +230,11 @@ jobs:
type: string
ui-e2e-grep:
description: "Mocha grep string used to run ui-e2e tests specific to given type of bridge"
default: ''
type: string
oracle-e2e-script:
description: "Yarn script string used to run oracle-e2e tests specific to given type of bridge"
default: ''
type: string
steps:
- checkout
@@ -232,16 +245,24 @@ jobs:
- tokenbridge-orb/yarn-install-cached-on-machine
- run:
name: Prepare the infrastructure
command: e2e-commons/up.sh deploy << parameters.scenario-name >>
command: e2e-commons/up.sh deploy << parameters.scenario-name >> blocks
no_output_timeout: 50m
- tokenbridge-orb/wait-for-oracle:
redis-key: << parameters.redis-key >>
- run:
name: Run the ui-e2e tests
command: |
nvm use default;
node ./e2e-commons/scripts/blocks.js &
cd ui-e2e; yarn mocha -g "<< parameters.ui-e2e-grep >>" -b ./test.js
- when:
condition: << parameters.ui-e2e-grep >>
steps:
- run:
name: Run the ui-e2e tests
command: |
nvm use default;
cd ui-e2e; yarn mocha -g "<< parameters.ui-e2e-grep >>" -b ./test.js
- when:
condition: << parameters.oracle-e2e-script >>
steps:
- run:
name: Run the oracle-e2e tests
command: cd e2e-commons && docker-compose run e2e yarn workspace oracle-e2e run << parameters.oracle-e2e-script >>
workflows:
tokenbridge:
jobs:
@@ -272,18 +293,24 @@ workflows:
- deployment-ui
- deployment-monitor
- deployment-repo
- deployment-multiple
- ultimate:
name: "ultimate: native to erc"
name: "ultimate: native to erc"
scenario-name: native-to-erc
redis-key: native-erc-collected-signatures:lastProcessedBlock
ui-e2e-grep: "NATIVE TO ERC"
- ultimate:
name: "ultimate: erc to native"
name: "ultimate: erc to native"
scenario-name: erc-to-native
redis-key: erc-native-collected-signatures:lastProcessedBlock
ui-e2e-grep: "ERC TO NATIVE"
- ultimate:
name: "ultimate: erc to erc"
name: "ultimate: erc to erc"
scenario-name: erc-to-erc
redis-key: erc-erc-collected-signatures:lastProcessedBlock
ui-e2e-grep: "ERC TO ERC"
- ultimate:
name: "ultimate: amb"
scenario-name: amb
redis-key: amb-collected-signatures:lastProcessedBlock
oracle-e2e-script: "amb"

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "contracts"]
path = contracts
url = https://github.com/poanetwork/poa-bridge-contracts.git
url = https://github.com/poanetwork/tokenbridge-contracts.git

View File

@@ -69,3 +69,4 @@ MONITOR_VALIDATOR_HOME_TX_LIMIT | Average gas usage of a transaction sent by a v
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT | Average gas usage of a transaction sent by a validator, it is used to estimate the number of transaction that can be paid by the validator. | integer
MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or below this value, the monitor will report that the validator has less funds than it is required. | integer
MONITOR_PORT | The port for the Monitor. | integer
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string

View File

@@ -30,7 +30,7 @@ Sub-repositories maintained within this monorepo are listed below.
| [Commons](commons/README.md) | Interfaces, constants and utilities shared between the sub-repositories |
| [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
Additionally there are [Smart Contracts](https://github.com/poanetwork/poa-bridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.
Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.
## Available deployments
@@ -66,11 +66,13 @@ Clone the repository:
git clone https://github.com/poanetwork/tokenbridge
```
Initialize submodules, install dependencies, compile the Smart Contracts:
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts:
```
yarn initialize
```
Then refer to the corresponding README files to get information about particular TokenBridge component.
## Linting
Running linter for all JS projects:
@@ -106,5 +108,4 @@ This project is licensed under the GNU Lesser General Public License v3.0. See t
## References
* [Additional Documentation](https://forum.poa.network/c/tokenbridge)
* [POA20 Bridge FAQ](https://forum.poa.network/c/tokenbridge/poa20-bridge)
* [TokenBridge Documentation](http://www.tokenbridge.net/)

View File

@@ -13,6 +13,7 @@ const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/Rewardab
const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi
const FOREIGN_AMB_ABI = require('../contracts/build/contracts/ForeignAMB').abi
const BOX_ABI = require('../contracts/build/contracts/Box').abi
const SAI_TOP = require('../contracts/build/contracts/SaiTopMock').abi
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
const { BRIDGE_MODES } = require('./constants')
@@ -92,5 +93,6 @@ module.exports = {
ERC20_BYTES32_ABI,
HOME_AMB_ABI,
FOREIGN_AMB_ABI,
BOX_ABI
BOX_ABI,
SAI_TOP
}

View File

@@ -17,8 +17,11 @@ const FEE_MANAGER_MODE = {
UNDEFINED: 'UNDEFINED'
}
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
module.exports = {
BRIDGE_MODES,
ERC_TYPES,
FEE_MANAGER_MODE
FEE_MANAGER_MODE,
ZERO_ADDRESS
}

View File

@@ -0,0 +1,3 @@
---
- import_playbook: ../../../deployment/site.yml
- import_playbook: ./run-checks.yml

View File

@@ -29,10 +29,11 @@ provisioner:
r: ["bug"]
playbooks:
prepare: ../prepare.yml
converge: ../../../deployment/site.yml
converge: ./converge.yml
inventory:
host_vars:
monitor-host:
MONITOR_PORT: 3003
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra

View File

@@ -0,0 +1,7 @@
---
- name: Generate initial data for monitor
hosts: monitor
become: true
tasks:
- name: Run monitor checks
shell: /bin/bash -c 'cd /home/poadocker/bridge/monitor/scripts; ./getBridgeStats.sh >cronWorker.out 2>cronWorker.err'

View File

@@ -34,14 +34,14 @@ def test_logging(host, filename):
def test_home_exists(host):
assert host.run_test(
'curl -s http://localhost:3003 | '
'curl -s http://localhost:3003/bridge | '
'grep -q -i "home"'
)
def test_foreign_exists(host):
assert host.run_test(
'curl -s http://localhost:3003 | '
'curl -s http://localhost:3003/bridge | '
'grep -q -i "foreign"'
)
@@ -49,6 +49,6 @@ def test_foreign_exists(host):
def test_no_error(host):
assert host.run_expect(
[1],
'curl -s http://localhost:3003 | '
'curl -s http://localhost:3003/bridge | '
'grep -i -q "error"'
)

View File

@@ -0,0 +1,14 @@
# Molecule managed
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi

View File

@@ -0,0 +1,59 @@
---
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
enabled: True
options:
config-data:
ignore: ../../hosts.yml
platforms:
- name: multiple-host
groups:
- example
children:
- oracle
- monitor
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
lint:
name: ansible-lint
enabled: True
options:
r: ["bug"]
playbooks:
prepare: ../prepare.yml
converge: ../monitor/converge.yml
inventory:
host_vars:
multiple-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
MONITOR_PORT: 3003
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra
lint:
name: flake8
additional_files_or_dirs:
- ../../tests/*
scenario:
name: multiple
test_sequence:
- lint
- cleanup
- destroy
- dependency
- syntax
- create
- prepare
- converge
- verify
- destroy

View File

@@ -0,0 +1,32 @@
import os
import pytest
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
@pytest.mark.parametrize("service", [
("poabridge"),
("tokenbridge-ui"),
("tokenbridge-monitor")
])
def test_services(host, service):
assert host.service(service).is_enabled
assert host.service(service).is_running
@pytest.mark.parametrize("name", [
("oracle_rabbit_1"),
("oracle_redis_1"),
("oracle_bridge_request_1"),
("oracle_bridge_collected_1"),
("oracle_bridge_affirmation_1"),
("oracle_bridge_senderhome_1"),
("oracle_bridge_senderforeign_1"),
("ui_ui_1"),
("monitor_monitor_1")
])
def test_docker_containers(host, name):
container = host.docker(name)
assert container.is_running

View File

@@ -9,7 +9,6 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
def test_repo(host):
assert host.file('/home/poadocker/bridge').exists
assert host.file('/home/poadocker/bridge').is_directory
assert host.file('/home/poadocker/bridge/package.json').exists
def test_docker_group(host):

View File

@@ -0,0 +1,14 @@
# Molecule managed
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi

View File

@@ -0,0 +1,40 @@
---
driver:
name: docker
platforms:
- name: oracle-amb-host
groups:
- ultimate
- amb
children:
- oracle
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
prepare: ../prepare.yml
converge: ../ultimate-commons/converge.yml
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:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-amb
test_sequence:
- cleanup
- destroy
- syntax
- create
- prepare
- converge

View File

@@ -0,0 +1,26 @@
---
- 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

@@ -5,32 +5,31 @@
tasks:
- name: stop the service
shell: service poabridge stop
- name: Slurp docker compose file
slurp:
src: "/home/poadocker/bridge/oracle/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: Build current oracle image
shell: docker build -t oracle:ultimate-testing --file oracle/Dockerfile .
delegate_to: 127.0.0.1
become: false
args:
chdir: "{{ lookup('env', 'PWD') }}/.."
- 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: 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
- name: Write new docker-compose file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/docker-compose.yml"
- 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

@@ -36,6 +36,8 @@ provisioner:
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_HOME_START_BLOCK: 1
ORACLE_FOREIGN_START_BLOCK: 1
ui-erc-to-native-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"

View File

@@ -78,6 +78,10 @@ Example config for installing only UI:
1. Go to the group_vars folder.
`cd group_vars`
2. Note the <bridge_name> and add it to the hosts.yml configuration. For example, if a bridge file is named sokol-kovan.yml, you would change the <bridge_name> value in hosts.yml to sokol-kovan.
## Examples
[Deploy a monitor for multiple bridges](./MONITOR.md)
## Administrator Configurations

104
deployment/MONITOR.md Normal file
View File

@@ -0,0 +1,104 @@
## Deploy multiple bridge monitor on the same host
If you want to deploy a monitor for different bridges, the [monitor variables](../monitor/.env.example) should be configured in `group_vars/<bridge_name>.yml` for each bridge.
For example, let's say we are going to deploy a monitor for xDai bridge and for WETC bridge.
#### Setup ansible configuration for xDai Bridge
First we create `hosts.yml` file to deploy the monitor for xdai bridge
```yaml
---
xdai:
children:
monitor:
hosts:
<host_ip_A>:
ansible_user: ubuntu
```
In `group_vars/xdai.yml`
```
---
MONITOR_BRIDGE_NAME: "xdai"
MONITOR_PORT: 3003
COMMON_HOME_RPC_URL: "https://dai.poa.network"
COMMON_HOME_BRIDGE_ADDRESS: "0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6"
COMMON_FOREIGN_RPC_URL: "https://mainnet.infura.io/v3/INFURA_KEY"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016"
COMMON_HOME_GAS_PRICE_FALLBACK: 0
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE: "standard"
COMMON_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
MONITOR_HOME_START_BLOCK: 759
MONITOR_FOREIGN_START_BLOCK: 6478417
MONITOR_VALIDATOR_HOME_TX_LIMIT: 0
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100
```
Run the playbook to deploy the monitor for xdai bridge
```
ansible-playbook -i hosts.yml site.yml
```
This command will deploy the monitor component and enable statistics for xdai bridge.
#### Setup ansible configuration for WETC Bridge
Update `hosts.yml` file to deploy the monitor for WETC Bridge
```yaml
---
wetc:
children:
monitor:
hosts:
<host_ip_A>:
ansible_user: ubuntu
```
In `group_vars/wetc.yml`
```
---
MONITOR_BRIDGE_NAME: "wetc"
COMMON_HOME_RPC_URL: "https://ethereumclassic.network"
COMMON_HOME_BRIDGE_ADDRESS: "0x073081832B4Ecdce79d4D6753565c85Ba4b3BeA9"
COMMON_FOREIGN_RPC_URL: "https://mainnet.infura.io/v3/32e8e252699a4ac1b5dd5c1ef53cc301"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x0cB781EE62F815bdD9CD4c2210aE8600d43e7040"
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice-etc.poa.network/"
COMMON_HOME_GAS_PRICE_SPEED_TYPE: "standard"
COMMON_HOME_GAS_PRICE_FALLBACK: 15000000000
COMMON_HOME_GAS_PRICE_FACTOR: 1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE: "standard"
COMMON_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
MONITOR_HOME_START_BLOCK: 7703292
MONITOR_FOREIGN_START_BLOCK: 7412459
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100
```
Given that there is a monitor component deployed in the system, the `MONITOR_PORT` variable is not needed.
Run the playbook to deploy the monitor for WETC Bridge
```
ansible-playbook -i hosts.yml site.yml
```
They playbook will detect that the monitor component is already deployed in the system, so it will only generate the configuration needed to enable the WETC Bridge statistics.
##### Get Monitor results
The monitor output will be available at `http://host_ip_A:MONITOR_PORT/MONITOR_BRIDGE_NAME`.
Given that in `xdai.env` the variable `MONITOR_BRIDGE_NAME` is set to `xdai`, the results are in the url `http://host_ip_A:3003/xdai/`.
Similar to the xdai case, in `wetc.env` the variable `MONITOR_BRIDGE_NAME` is set to `wetc`, so the results are in the url `http://host_ip_A:3003/wetc/`.

View File

@@ -0,0 +1,5 @@
---
ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE"
COMMON_HOME_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0"
MONITOR_PORT: 3013

View File

@@ -42,6 +42,7 @@ UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
## Monitor
MONITOR_BRIDGE_NAME: "xdai"
MONITOR_PORT: 3003
MONITOR_HOME_START_BLOCK: 759
MONITOR_FOREIGN_START_BLOCK: 6478417

View File

@@ -44,9 +44,10 @@ UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
## Monitor
MONITOR_BRIDGE_NAME: "bridge"
MONITOR_PORT: 3003
MONITOR_HOME_START_BLOCK: 0
MONITOR_FOREIGN_START_BLOCK: 0
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_LEFT_TX_THRESHOLD: 100
MONITOR_TX_NUMBER_THRESHOLD: 100

View File

@@ -41,9 +41,10 @@ UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
#montior
#monitor
MONITOR_BRIDGE_NAME: "bridge"
MONITOR_HOME_START_BLOCK: 0
MONITOR_FOREIGN_START_BLOCK: 0
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_LEFT_TX_THRESHOLD: 100
MONITOR_TX_NUMBER_THRESHOLD: 100

View File

@@ -43,6 +43,7 @@ UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
## Monitor
MONITOR_BRIDGE_NAME: "wetc"
MONITOR_PORT: 3003
MONITOR_HOME_START_BLOCK: 7703292
MONITOR_FOREIGN_START_BLOCK: 7412459

View File

@@ -32,6 +32,9 @@
group: "root"
mode: "0755"
- name: Upgrade pip version
shell: pip3 install --upgrade pip
- name: Install python docker library
shell: pip3 install docker docker-compose setuptools

View File

@@ -1,4 +1,21 @@
---
- include_tasks: dependencies.yml
- include_tasks: repo.yml
- include_tasks: logging.yml
- name: Check if component is already deployed
shell: "test -f {{ bridge_path }}/{{ component }}/.env && echo 'true'"
ignore_errors: True
register: already_deployed
when: check_deployed is defined
- name: Set if tasks should be skipped
set_fact: skip_task="{{ already_deployed.stdout | default('false') }}"
- name: Include dependencies tasks
include_tasks: dependencies.yml
when: skip_task != true
- name: Include repo tasks
include_tasks: repo.yml
when: skip_task != true and skip_repo is undefined
- name: Include logging tasks
include_tasks: logging.yml
when: skip_task != true

View File

@@ -1,3 +1,3 @@
---
dependencies:
- role: common
- { role: common, skip_repo: true, check_deployed: true, component: 'monitor' }

View File

@@ -6,7 +6,7 @@
day: "{{ monitor_cron_schedule.split(' ')[2] }}"
month: "{{ monitor_cron_schedule.split(' ')[3] }}"
weekday: "{{ monitor_cron_schedule.split(' ')[4] }}"
job: "/bin/bash -c 'cd {{ bridge_path }}/monitor/scripts; ./checkDocker.sh >cronWorker.out 2>cronWorker.err'"
job: "/bin/bash -c 'cd {{ bridge_path }}/monitor/scripts; ./getBridgeStats.sh >cronWorker.out 2>cronWorker.err'"
- name: Add cron entry
cron:
name: "RUN_MONITOR_CHECKS"

View File

@@ -1,5 +1,5 @@
---
- name: Build the containers
shell: docker-compose build
- name: Pull the containers images
shell: docker-compose pull
args:
chdir: "{{ bridge_path }}/monitor"

View File

@@ -1,6 +1,18 @@
---
- include_tasks: pre_config.yml
- include_tasks: logging.yml
- include_tasks: jumpbox.yml
- include_tasks: servinstall.yml
- include_tasks: cron.yml
- name: Include logging tasks
include_tasks: logging.yml
when: skip_task != true
- name: Include jumpbox tasks
include_tasks: jumpbox.yml
when: skip_task != true
- name: Include servinstall tasks
include_tasks: servinstall.yml
when: skip_task != true
- name: Include cron tasks
include_tasks: cron.yml
when: skip_task != true

View File

@@ -1,5 +1,47 @@
---
- name: Create configs directory
file:
path: "{{ bridge_path }}/monitor/configs"
state: directory
mode: '0755'
when: skip_task != true
- name: Create responses directory
file:
path: "{{ bridge_path }}/monitor/responses"
state: directory
mode: '0755'
when: skip_task != true
- name: Create scripts directory
file:
path: "{{ bridge_path }}/monitor/scripts"
state: directory
mode: '0755'
when: skip_task != true
- name: Install .env config
template:
src: .env.j2
dest: "{{ bridge_path }}/monitor/.env"
when: skip_task != true
- name: Copy docker-compose file
copy:
src: ../../../../monitor/docker-compose.yml
dest: "{{ bridge_path }}/monitor/docker-compose.yml"
mode: '0755'
when: skip_task != true
- name: Copy script file
copy:
src: ../../../../monitor/scripts/getBridgeStats.sh
dest: "{{ bridge_path }}/monitor/scripts/getBridgeStats.sh"
owner: "{{ compose_service_user }}"
mode: '0755'
when: skip_task != true
- name: Install bridge config env
template:
src: config.env.j2
dest: "{{ bridge_path }}/monitor/configs/{{ MONITOR_BRIDGE_NAME }}.env"

View File

@@ -1,6 +1,5 @@
# This role creates a tokenbridge-monitor service which is designed to manage docker-compose monitor deployment.
# /etc/init.d/tokenbridge-monitor start, status, stop, restart - does what the services usually do in such cases.
# /etc/init.d/tokenbridge-monitor rebuild - builds a new monitor deployment from scratch.
---
- name: "Set the service"
template:

View File

@@ -1,24 +1 @@
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
COMMON_HOME_BRIDGE_ADDRESS={{ COMMON_HOME_BRIDGE_ADDRESS }}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
MONITOR_HOME_START_BLOCK={{ MONITOR_HOME_START_BLOCK }}
MONITOR_FOREIGN_START_BLOCK={{ MONITOR_FOREIGN_START_BLOCK }}
MONITOR_VALIDATOR_HOME_TX_LIMIT={{ MONITOR_VALIDATOR_HOME_TX_LIMIT }}
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
{% endif %}
{% if COMMON_HOME_GAS_PRICE_SPEED_TYPE | default('') != '' %}
COMMON_HOME_GAS_PRICE_SPEED_TYPE={{ COMMON_HOME_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_HOME_GAS_PRICE_FALLBACK={{ COMMON_HOME_GAS_PRICE_FALLBACK }}
{% if COMMON_HOME_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_HOME_GAS_PRICE_FACTOR={{ COMMON_HOME_GAS_PRICE_FACTOR }}
{% endif %}
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT={{ MONITOR_VALIDATOR_FOREIGN_TX_LIMIT }}
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL={{ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL }}
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE={{ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE }}
COMMON_FOREIGN_GAS_PRICE_FALLBACK={{ COMMON_FOREIGN_GAS_PRICE_FALLBACK }}
COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
MONITOR_LEFT_TX_THRESHOLD={{ MONITOR_LEFT_TX_THRESHOLD }}
MONITOR_PORT={{ MONITOR_PORT }}

View File

@@ -0,0 +1,34 @@
MONITOR_BRIDGE_NAME={{ MONITOR_BRIDGE_NAME }}
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
COMMON_HOME_BRIDGE_ADDRESS={{ COMMON_HOME_BRIDGE_ADDRESS }}
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
{% endif %}
{% if COMMON_HOME_GAS_PRICE_SPEED_TYPE | default('') != '' %}
COMMON_HOME_GAS_PRICE_SPEED_TYPE={{ COMMON_HOME_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_HOME_GAS_PRICE_FALLBACK={{ COMMON_HOME_GAS_PRICE_FALLBACK }}
{% if COMMON_HOME_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_HOME_GAS_PRICE_FACTOR={{ COMMON_HOME_GAS_PRICE_FACTOR }}
{% endif %}
{% if COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL={{ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL }}
{% endif %}
{% if COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE={{ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_FOREIGN_GAS_PRICE_FALLBACK={{ COMMON_FOREIGN_GAS_PRICE_FALLBACK }}
{% if COMMON_FOREIGN_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
{% endif %}
MONITOR_HOME_START_BLOCK={{ MONITOR_HOME_START_BLOCK }}
MONITOR_FOREIGN_START_BLOCK={{ MONITOR_FOREIGN_START_BLOCK }}
MONITOR_VALIDATOR_HOME_TX_LIMIT={{ MONITOR_VALIDATOR_HOME_TX_LIMIT }}
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT={{ MONITOR_VALIDATOR_FOREIGN_TX_LIMIT }}
MONITOR_TX_NUMBER_THRESHOLD={{ MONITOR_TX_NUMBER_THRESHOLD }}

View File

@@ -18,6 +18,7 @@ start(){
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose up --detach
sudo -u "{{ compose_service_user }}" /bin/bash -c 'cd scripts; ./getBridgeStats.sh >cronWorker.out 2>cronWorker.err'
}
stop(){
@@ -33,14 +34,6 @@ status(){
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose ps
}
rebuild(){
echo "Rebuild TokenBridge Monitor.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose up --detach --force-recreate --no-deps --build
}
case "$1" in
@@ -62,12 +55,8 @@ case "$1" in
start
;;
rebuild)
rebuild
;;
*)
echo $"Usage: $0 {start|stop|restart|rebuild|status}"
echo $"Usage: $0 {start|stop|restart|status}"
exit 1
;;

View File

@@ -1,3 +1,3 @@
---
dependencies:
- role: common
- { role: common, skip_repo: true }

View File

@@ -1,5 +1,5 @@
---
- name: Build the containers
shell: docker-compose build
- name: Pull the containers images
shell: docker-compose pull
args:
chdir: "{{ bridge_path }}/oracle"

View File

@@ -1,22 +1,11 @@
---
- name: Slurp docker compose file
slurp:
src: "{{ bridge_path }}/oracle/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: Set logger to remote server
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Write new docker-compose file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "{{ bridge_path }}/oracle/docker-compose.yml"
- include_tasks: logging_by_syslog.yml
with_items:
- docker-compose
- docker-compose-transfer
- docker-compose-erc-native
loop_control:
loop_var: file
- name: Set the local container logs configuration file
template:

View File

@@ -0,0 +1,19 @@
---
- name: Slurp docker compose file
slurp:
src: "{{ bridge_path }}/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: Set logger to remote server
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Write updated docker file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "{{ bridge_path }}/oracle/{{ file }}.yml"

View File

@@ -1,22 +1,24 @@
---
- name: Get blocks
become_user: "{{ compose_service_user }}"
shell: docker-compose run --entrypoint "node scripts/getValidatorStartBlocks.js" bridge_affirmation
shell: docker-compose run --rm --entrypoint "node scripts/getValidatorStartBlocks.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: BLOCKS
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
- name: Write blocks
blockinfile:
path: "{{ bridge_path }}/oracle/.env"
marker: "## {mark} Calculated by scripts/getValidatorStartBlocks.js"
block: |
HOME_START_BLOCK={{ (BLOCKS.stdout | from_json).homeStartBlock }}
FOREIGN_START_BLOCK={{ (BLOCKS.stdout | from_json).foreignStartBlock }}
ORACLE_HOME_START_BLOCK={{ (BLOCKS.stdout | from_json).homeStartBlock }}
ORACLE_FOREIGN_START_BLOCK={{ (BLOCKS.stdout | from_json).foreignStartBlock }}
when: (ORACLE_HOME_START_BLOCK is not defined) or (ORACLE_FOREIGN_START_BLOCK is not defined)
- name: Get validator address
become_user: "{{ compose_service_user }}"
shell: docker-compose run -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY="{{ ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY }}" --entrypoint "node scripts/privateKeyToAddress.js" bridge_affirmation
shell: docker-compose run --rm -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY="{{ ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY }}" --entrypoint "node scripts/privateKeyToAddress.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: VADDRESS
@@ -25,6 +27,25 @@
set_fact:
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
- name: Get foreign erc type
become_user: "{{ compose_service_user }}"
shell: docker-compose run --rm --entrypoint "node scripts/initialChecks.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: ERCTYPE
- name: Set FOREIGN_ERC_TYPE variable
set_fact:
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
- name: Extend docker compose file
set_fact: composefileoverride="-f docker-compose-transfer.yml"
when: ORACLE_BRIDGE_MODE == "ERC_TO_ERC" and FOREIGN_ERC_TYPE != "ERC677"
- name: Extend docker compose file for erc to native
set_fact: composefileoverride="-f docker-compose-erc-native.yml"
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"
- name: Install .key config
template:
src: key.j2

View File

@@ -1,5 +1,21 @@
---
- name: Create oracle directory
file:
path: "{{ bridge_path }}/oracle"
state: directory
mode: '0755'
- name: Install .env config
template:
src: .env.j2
dest: "{{ bridge_path }}/oracle/.env"
- name: Copy docker-compose files
copy:
src: ../../../../oracle/{{ item }}
dest: "{{ bridge_path }}/oracle/"
mode: '0755'
with_items:
- docker-compose.yml
- docker-compose-transfer.yml
- docker-compose-erc-native.yml

View File

@@ -1,6 +1,5 @@
# This role creates a poabridge service which is designed to manage docker-compose bridge deployment.
# /etc/init.d/poabridge start, status, stop, restart - does what the services usually do in such cases.
# /etc/init.d/poabridge rebuild - builds a new bridge deployment from scratch.
---
- name: "Set poabridge service"
template:

View File

@@ -1,6 +1,8 @@
## General settings
ORACLE_BRIDGE_MODE={{ ORACLE_BRIDGE_MODE }}
{% if ORACLE_LOG_LEVEL | default('') != '' %}
ORACLE_LOG_LEVEL={{ ORACLE_LOG_LEVEL }}
{% endif %}
## Home contract
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
@@ -45,3 +47,10 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}
ORACLE_HOME_START_BLOCK={{ ORACLE_HOME_START_BLOCK }}
{% endif %}
{% if ORACLE_FOREIGN_START_BLOCK | default('') != '' %}
ORACLE_FOREIGN_START_BLOCK={{ ORACLE_FOREIGN_START_BLOCK }}
{% endif %}

View File

@@ -16,6 +16,7 @@ WORKDIR="{{ '/home/' + compose_service_user | default('poadocker') + '/' + bridg
keyfile="{{ keyfile_path }}"
vaddr="ORACLE_VALIDATOR_ADDRESS="
vkey="ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY="
composefileoverride="{{ composefileoverride | default('') }}"
#Parsing file content and add key to variable
while read -r line
@@ -33,30 +34,22 @@ done < $keyfile
start(){
echo "Starting bridge.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose up --detach
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride rm -fv
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose $composefileoverride up --detach
}
stop(){
echo "Stopping bridge.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride down -v
sleep 2
}
status(){
echo "Bridge status:"
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose ps
}
rebuild(){
echo "Rebuild bridge.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" ORACLE_VALIDATOR_ADDRESS=$vaddr ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=$vkey /usr/local/bin/docker-compose up --detach --force-recreate --no-deps --build
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride ps
}
@@ -80,12 +73,8 @@ case "$1" in
start
;;
rebuild)
rebuild
;;
*)
echo $"Usage: $0 {start|stop|restart|rebuild|status}"
echo $"Usage: $0 {start|stop|restart|status}"
exit 1
;;

View File

@@ -0,0 +1,19 @@
COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
COMMON_FOREIGN_BRIDGE_ADDRESS=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
MONITOR_HOME_START_BLOCK=0
MONITOR_FOREIGN_START_BLOCK=0
MONITOR_VALIDATOR_HOME_TX_LIMIT=300000
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
COMMON_HOME_GAS_PRICE_FACTOR=1
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT=300000
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
MONITOR_TX_NUMBER_THRESHOLD=100
MONITOR_PORT=3013
MONITOR_BRIDGE_NAME=bridge

View File

@@ -16,3 +16,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
MONITOR_TX_NUMBER_THRESHOLD=100
MONITOR_PORT=3012
MONITOR_BRIDGE_NAME=bridge

View File

@@ -16,3 +16,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
MONITOR_TX_NUMBER_THRESHOLD=100
MONITOR_PORT=3011
MONITOR_BRIDGE_NAME=bridge

View File

@@ -16,3 +16,4 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK=1000000000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
MONITOR_TX_NUMBER_THRESHOLD=100
MONITOR_PORT=3010
MONITOR_BRIDGE_NAME=bridge

View File

@@ -21,3 +21,5 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR=0.1
ORACLE_HOME_RPC_POLLING_INTERVAL=500
ORACLE_FOREIGN_RPC_POLLING_INTERVAL=500
ORACLE_ALLOW_HTTP_FOR_RPC=yes
ORACLE_HOME_START_BLOCK=1
ORACLE_FOREIGN_START_BLOCK=1

View File

@@ -7,10 +7,26 @@
"address": "0xcca2fb44C8C36E51f743269d6F484Fd027B9F9Aa",
"privateKey": "0xcf954e07e6a439faf392eb474e95ddb444c2ca444847f2ad6ecc79e1a585e2b8"
},
"thirdUser": {
"address": "0x441cc8537aB6cE63d060b63F3A44eE021d62e6cF",
"privateKey": "0xd3915199f27691d7784cb01ab0c7220308053b229f95d592e97493326314a8d0"
},
"fourthUser": {
"address": "0x3CC5baAB679eC0732C175760079Bf48F564ad26B",
"privateKey": "0xedb53ee050631b7914d5f1a66c2f0d2df3ec85a9ed2a9616b16a7b1b7a10b8d1"
},
"validator": {
"address": "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b",
"privateKey": "0x8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
},
"secondValidator": {
"address": "0xdCC784657C78054aa61FbcFFd2605F32374816A4",
"privateKey": "0x5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
},
"thirdValidator": {
"address": "0xDcef88209a20D52165230104B245803C3269454d",
"privateKey": "0xf877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
},
"blockGenerator": {
"address": "0xB4579fd5AfEaB60B03Db3F408AAdD315035943f7",
"privateKey": "0xd6143d390d8b28c33601bb0fe29392fb1c35c24ccfe8722c09c2bdd6ada2699f"
@@ -20,7 +36,7 @@
"foreign": "0x2B6871b9B02F73fa24F4864322CdC78604207769",
"foreignToken": "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B",
"ui": "http://localhost:3000",
"monitor": "http://monitor:3010"
"monitor": "http://monitor:3010/bridge"
},
"ercToErcBridge": {
"home": "0x1feB40aD9420b186F019A717c37f5546165d411E",
@@ -28,20 +44,24 @@
"homeToken": "0x792455a6bCb62Ed4C4362D323E0590654CA4765c",
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
"ui": "http://localhost:3001",
"monitor": "http://monitor-erc20:3011"
"monitor": "http://monitor-erc20:3011/bridge"
},
"ercToNativeBridge": {
"home": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
"foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
"halfDuplexToken": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359",
"saiTop": "0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3",
"chaiToken": "0x06af07097c9eeb7fd685c692751d5c66db49c215",
"ui": "http://localhost:3002",
"monitor": "http://monitor-erc20-native:3012"
"monitor": "http://monitor-erc20-native:3012/bridge"
},
"amb": {
"home": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"homeBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1"
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"monitor": "http://monitor-amb:3013/bridge"
},
"homeRPC": {
"URL": "http://parity1:8545",

View File

@@ -22,4 +22,4 @@ FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1
FOREIGN_GAS_PRICE=10000000000
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"

View File

@@ -34,8 +34,7 @@ FOREIGN_REWARDABLE=false
ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATORS_REWARD_ACCOUNTS=0x0000000000000000000000000000000000000000
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"
BLOCK_REWARD_ADDRESS=0x0000000000000000000000000000000000000000
DPOS_STAKING_ADDRESS=0x0000000000000000000000000000000000000000
ERC20_EXTENDED_BY_ERC677=false

View File

@@ -32,8 +32,7 @@ FOREIGN_GAS_PRICE=10000000000
FOREIGN_REWARDABLE=false
BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977
ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f
ERC20_TOKEN_ADDRESS=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATORS_REWARD_ACCOUNTS=0x0000000000000000000000000000000000000000
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"

View File

@@ -34,6 +34,5 @@ FOREIGN_GAS_PRICE=10000000000
FOREIGN_REWARDABLE=false
REQUIRED_NUMBER_OF_VALIDATORS=1
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATORS_REWARD_ACCOUNTS=0x0000000000000000000000000000000000000000
VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b 0xdCC784657C78054aa61FbcFFd2605F32374816A4 0xDcef88209a20D52165230104B245803C3269454d"
BLOCK_REWARD_ADDRESS=0x0000000000000000000000000000000000000000

View File

@@ -24,16 +24,13 @@ services:
- ultimate
rabbit:
image: "rabbitmq:3-management"
ports:
- "15672:15672"
networks:
- ultimate
oracle:
build:
context: ..
dockerfile: oracle/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/oracle.env
env_file: ../e2e-commons/components-envs/oracle.env
environment:
- NODE_ENV=production
command: "true"
@@ -43,8 +40,7 @@ services:
build:
context: ..
dockerfile: oracle/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/oracle-erc20.env
env_file: ../e2e-commons/components-envs/oracle-erc20.env
environment:
- NODE_ENV=production
command: "true"
@@ -54,8 +50,7 @@ services:
build:
context: ..
dockerfile: oracle/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/oracle-erc20-native.env
env_file: ../e2e-commons/components-envs/oracle-erc20-native.env
environment:
- NODE_ENV=production
command: "true"
@@ -65,8 +60,7 @@ services:
build:
context: ..
dockerfile: oracle/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/oracle-amb.env
env_file: ../e2e-commons/components-envs/oracle-amb.env
environment:
- NODE_ENV=production
command: "true"
@@ -103,8 +97,7 @@ services:
build:
context: ..
dockerfile: monitor/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/monitor.env
env_file: ../e2e-commons/components-envs/monitor.env
entrypoint: yarn check-and-start
ports:
- "3010:3010"
@@ -114,8 +107,7 @@ services:
build:
context: ..
dockerfile: monitor/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/monitor-erc20.env
env_file: ../e2e-commons/components-envs/monitor-erc20.env
entrypoint: yarn check-and-start
ports:
- "3011:3011"
@@ -125,13 +117,22 @@ services:
build:
context: ..
dockerfile: monitor/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/monitor-erc20-native.env
env_file: ../e2e-commons/components-envs/monitor-erc20-native.env
entrypoint: yarn check-and-start
ports:
- "3012:3012"
networks:
- ultimate
monitor-amb:
build:
context: ..
dockerfile: monitor/Dockerfile
env_file: ../e2e-commons/components-envs/monitor-amb.env
entrypoint: yarn check-and-start
ports:
- "3013:3013"
networks:
- ultimate
e2e:
build:
context: ..
@@ -139,3 +140,10 @@ services:
command: "true"
networks:
- ultimate
blocks:
build:
context: ..
dockerfile: Dockerfile.e2e
entrypoint: node e2e-commons/scripts/blocks.js
networks:
- ultimate

View File

@@ -5,4 +5,6 @@ if [ $CI ]; then exit $rc; fi
ps | grep node | grep -v grep | awk '{print "kill " $1}' | /bin/bash
docker-compose down
docker-compose -p validator2 down
docker-compose -p validator3 down
docker network rm ultimate || true

View File

@@ -1,8 +1,7 @@
const Web3 = require('web3')
const { generateNewBlock } = require('../utils')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8541'))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8542'))
const homeWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity1:8545'))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider('http://parity2:8545'))
const {user, blockGenerator} = require('../constants.json');
homeWeb3.eth.accounts.wallet.add(user.privateKey)
@@ -10,10 +9,24 @@ foreignWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(blockGenerator.privateKey)
foreignWeb3.eth.accounts.wallet.add(blockGenerator.privateKey)
function generateNewBlock(web3, address) {
return web3.eth.sendTransaction({
from: address,
to: '0x0000000000000000000000000000000000000000',
gasPrice: '1',
gas: '21000',
value: '1'
})
}
function main() {
setTimeout(async () => {
generateNewBlock(homeWeb3, blockGenerator.address)
generateNewBlock(foreignWeb3, blockGenerator.address)
try {
generateNewBlock(homeWeb3, blockGenerator.address)
} catch {} // in case of Transaction with the same hash was already imported.
try {
generateNewBlock(foreignWeb3, blockGenerator.address)
} catch {} // in case of Transaction with the same hash was already imported.
main()
}, 1000)
}

View File

@@ -6,6 +6,9 @@ CONTRACTS_PATH="../../contracts"
DEPLOY_PATH="$CONTRACTS_PATH/deploy"
ENVS_PATH="../contracts-envs"
# mock bridge validators contract with the one with deterministic isValidatorDuty
mv "$CONTRACTS_PATH/build/contracts/BridgeValidatorsDeterministic.json" "$CONTRACTS_PATH/build/contracts/BridgeValidators.json"
echo -e "\n\n############ Deploying native-to-erc ############\n"
cp "$ENVS_PATH/native-to-erc.env" "$DEPLOY_PATH/.env"
cd "$DEPLOY_PATH"

View File

@@ -7,6 +7,30 @@ docker-compose build
docker network create --driver bridge ultimate || true
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 watcher:half-duplex-transfer
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn worker:swap-tokens
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
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
}
while [ "$1" != "" ]; do
if [ "$1" == "oracle" ]; then
docker-compose up -d redis rabbit oracle oracle-erc20 oracle-erc20-native oracle-amb
@@ -17,9 +41,14 @@ while [ "$1" != "" ]; do
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 watcher:half-duplex-transfer
docker-compose run -d oracle-erc20-native yarn worker:swap-tokens
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
@@ -27,6 +56,20 @@ while [ "$1" != "" ]; do
docker-compose run -d oracle yarn sender:foreign
fi
if [ "$1" == "oracle-validator-2" ]; then
oracle2name="-p validator2"
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"
oracle2comp="-e ORACLE_QUEUE_URL=amqp://rabbit2 -e ORACLE_REDIS_URL=redis://redis2"
startValidator "$oracle2name" "$oracle2Values" "$oracle2comp" "redis2" "rabbit2"
fi
if [ "$1" == "oracle-validator-3" ]; then
oracle3name="-p validator3"
oracle3Values="-e ORACLE_VALIDATOR_ADDRESS=0xDcef88209a20D52165230104B245803C3269454d -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=f877f62a1c19f852cff1d29f0fb1ecac18821c0080d4cc0520c60c098293dca1"
oracle3comp="-e ORACLE_QUEUE_URL=amqp://rabbit3 -e ORACLE_REDIS_URL=redis://redis3"
startValidator "$oracle3name" "$oracle3Values" "$oracle3comp" "redis3" "rabbit3"
fi
if [ "$1" == "ui" ]; then
docker-compose up -d ui ui-erc20 ui-erc20-native
@@ -40,11 +83,11 @@ while [ "$1" != "" ]; do
fi
if [ "$1" == "blocks" ]; then
node ./scripts/blocks.js &
docker-compose up -d blocks
fi
if [ "$1" == "monitor" ]; then
docker-compose up -d monitor monitor-erc20 monitor-erc20-native
docker-compose up -d monitor monitor-erc20 monitor-erc20-native monitor-amb
fi
if [ "$1" == "native-to-erc" ]; then
@@ -59,5 +102,9 @@ while [ "$1" != "" ]; do
../deployment-e2e/molecule.sh ultimate-erc-to-erc
fi
if [ "$1" == "amb" ]; then
../deployment-e2e/molecule.sh ultimate-amb
fi
shift # Shift all the parameters down by one
done

View File

@@ -1,13 +1,18 @@
function generateNewBlock(web3, address) {
return web3.eth.sendTransaction({
from: address,
to: '0x0000000000000000000000000000000000000000',
gasPrice: '1',
gas: '21000',
value: '1'
const promiseRetry = require('promise-retry')
async function uniformRetry(f) {
return promiseRetry(f, {
forever: true,
factor: 1,
minTimeout: 500
})
}
module.exports = {
generateNewBlock
async function sleep(timeout) {
return new Promise(res => setTimeout(res, timeout))
}
module.exports = {
uniformRetry,
sleep
}

View File

@@ -3,4 +3,5 @@ while true; do
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native yarn check-all
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb yarn check-all
done

View File

@@ -1,6 +1,6 @@
cd $(dirname $0)
../e2e-commons/up.sh deploy monitor
../e2e-commons/up.sh deploy blocks monitor
./wait-for-monitor.sh
nohup ./periodically-check-all.sh < /dev/null > /dev/null 2>&1 &

128
monitor-e2e/test/amb.js Normal file
View File

@@ -0,0 +1,128 @@
const assert = require('assert')
const axios = require('axios')
const { amb, user, foreignRPC, homeRPC, validator } = require('../../e2e-commons/constants.json')
const { waitUntil, sendAMBMessage, addValidator } = require('../utils')
const baseUrl = amb.monitor
describe('AMB', () => {
describe('balances', async () => {
let data
before(async () => {
;({ data } = await axios.get(`${baseUrl}`))
})
describe('home', async () => {
it('should contain toForeign:', () => assert(data.home.toForeign === 0))
it('should contain fromForeign', () => assert(data.home.fromForeign === 0))
})
describe('foreign', async () => {
it('should contain fromHome:', () => assert(data.foreign.fromHome === 0))
it('should contain toHome', () => assert(data.foreign.toHome === 0))
})
describe('general', async () => {
it('should contain fromHomeToForeignDiff', () => assert(data.fromHomeToForeignDiff === 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))
it('should contain lastChecked', () => assert(data.lastChecked >= 0))
})
})
describe('validators', async () => {
let data
before(async () => {
;({ data } = await axios.get(`${baseUrl}/validators`))
})
it('home', () => {
assert(typeof data.home.validators === 'object')
assert(data.home.validators[validator.address].balance > 0)
assert(data.home.validators[validator.address].leftTx > 0)
assert(data.home.validators[validator.address].gasPrice > 0)
})
it('foreign', () => {
assert(typeof data.foreign.validators === 'object')
assert(data.foreign.validators[validator.address].balance > 0)
assert(data.foreign.validators[validator.address].leftTx > 0)
assert(data.foreign.validators[validator.address].gasPrice > 0)
})
it('requiredSignaturesMatch', () => assert(data.requiredSignaturesMatch, 1))
it('validatorsMatch', () => assert(data.validatorsMatch))
it('lastChecked', () => assert(data.lastChecked >= 0))
it('timeDiff', () => assert(data.timeDiff >= 0))
it('homeOk', () => assert(data.homeOk))
it('foreignOk', () => assert(data.foreignOk))
it('ok', () => assert(data.ok))
})
describe('eventsStats', async () => {
let data
before(async () => {
;({ data } = await axios.get(`${baseUrl}/eventsStats`))
})
it('ok', () => assert(data.ok))
it('lastChecked', () => assert(data.lastChecked >= 0))
it('timeDiff', () => assert(data.timeDiff >= 0))
it('home-deliveredMsgNotProcessedInForeign', () =>
assert(typeof data.home.deliveredMsgNotProcessedInForeign === 'object'))
it('home-processedMsgNotDeliveredInForeign', () =>
assert(typeof data.home.processedMsgNotDeliveredInForeign === 'object'))
it('foreign-deliveredMsgNotProcessedInHome', () =>
assert(typeof data.foreign.deliveredMsgNotProcessedInHome === 'object'))
it('foreign-processedMsgNotDeliveredInHome', () =>
assert(typeof data.foreign.processedMsgNotDeliveredInHome === 'object'))
})
describe('alerts', async () => {
let data
before(async () => {
;({ data } = await axios.get(`${baseUrl}/alerts`))
})
it('ok', () => assert(data.ok))
it('lastChecked', () => assert(data.lastChecked >= 0))
it('timeDiff', () => assert(data.timeDiff >= 0))
it('executeSignatures', () => assert(typeof data.executeSignatures === 'object'))
it('executeAffirmations', () => assert(typeof data.executeAffirmations === 'object'))
})
describe('changing state of contracts', () => {
let data
before(async () => {
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
})
it('should change fromForeignToHomeDiff', async () => {
// send message
await sendAMBMessage(foreignRPC.URL, user, amb.foreignBox, amb.foreign, amb.homeBox)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
return data.fromForeignToHomeDiff !== 0
})
})
it('should change fromHomeToForeignDiff', async () => {
// send message
await sendAMBMessage(homeRPC.URL, user, amb.homeBox, amb.home, amb.foreignBox)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
return data.fromHomeToForeignDiff !== 0
})
})
it('should change validatorsMatch', async () => {
await addValidator(foreignRPC.URL, validator, amb.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}/validators`))
return data.validatorsMatch === false
})
})
})
})

View File

@@ -1,7 +1,15 @@
const assert = require('assert')
const axios = require('axios')
const { ercToNativeBridge, user, foreignRPC, validator } = require('../../e2e-commons/constants.json')
const { waitUntil, sendTokens, addValidator } = require('../utils')
const {
waitUntil,
sendTokens,
addValidator,
initializeChaiToken,
convertDaiToChai,
setMinDaiTokenBalance,
migrateToMCD
} = require('../utils')
const baseUrl = ercToNativeBridge.monitor
@@ -24,12 +32,45 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
assert((await axios.get(`${baseUrl}/validators`)).data.validatorsMatch === true)
})
it('should change balanceDiff', async () => {
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.foreignToken, ercToNativeBridge.foreign)
it('should change balanceDiff', async function() {
this.timeout(60000)
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.halfDuplexToken, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
return data.balanceDiff !== 0
const { erc20Balance, halfDuplexErc20Balance, investedErc20Balance } = data.foreign
return (
data.balanceDiff === 0.01 &&
erc20Balance === '0.01' &&
halfDuplexErc20Balance === undefined &&
investedErc20Balance === undefined
)
})
await migrateToMCD(foreignRPC.URL, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, halfDuplexErc20Balance, investedErc20Balance } = data.foreign
return (
data.balanceDiff === 0.01 &&
erc20Balance === '0.01' &&
halfDuplexErc20Balance === '0' &&
investedErc20Balance === undefined
)
})
await sendTokens(foreignRPC.URL, user, ercToNativeBridge.halfDuplexToken, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, halfDuplexErc20Balance, investedErc20Balance } = data.foreign
return (
data.balanceDiff === 0.02 &&
erc20Balance === '0.01' &&
halfDuplexErc20Balance === '0.01' &&
investedErc20Balance === undefined
)
})
})
@@ -40,4 +81,52 @@ describe('ERC TO NATIVE with changing state of contracts', () => {
return data.validatorsMatch === false
})
})
it('should consider chai token balance', async function() {
this.timeout(60000)
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, halfDuplexErc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.03 &&
erc20Balance === '0.02' &&
halfDuplexErc20Balance === '0.01' &&
investedErc20Balance === '0' &&
accumulatedInterest === '0.001' // value of dsrBalance() is initially defined in genesis block as 0.001
)
})
await setMinDaiTokenBalance(foreignRPC.URL, ercToNativeBridge.foreign, '0.01')
await convertDaiToChai(foreignRPC.URL, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, halfDuplexErc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.03 &&
erc20Balance === '0.01' &&
halfDuplexErc20Balance === '0.01' &&
investedErc20Balance === '0.01' &&
accumulatedInterest === '0.001'
)
})
await setMinDaiTokenBalance(foreignRPC.URL, ercToNativeBridge.foreign, '0.005')
await convertDaiToChai(foreignRPC.URL, ercToNativeBridge.foreign)
await waitUntil(async () => {
;({ data } = await axios.get(`${baseUrl}`))
const { erc20Balance, halfDuplexErc20Balance, investedErc20Balance, accumulatedInterest } = data.foreign
return (
data.balanceDiff === 0.03 &&
erc20Balance === '0.005' &&
halfDuplexErc20Balance === '0.01' &&
investedErc20Balance === '0.015' &&
accumulatedInterest === '0.001'
)
})
})
})

View File

@@ -1,7 +1,14 @@
const Web3 = require('web3')
const { ERC677_BRIDGE_TOKEN_ABI, BRIDGE_VALIDATORS_ABI, FOREIGN_NATIVE_TO_ERC_ABI } = require('../commons')
const {
ERC677_BRIDGE_TOKEN_ABI,
BRIDGE_VALIDATORS_ABI,
FOREIGN_NATIVE_TO_ERC_ABI,
FOREIGN_ERC_TO_NATIVE_ABI,
BOX_ABI
} = require('../commons')
const { validator } = require('../e2e-commons/constants')
const waitUntil = async (predicate, step = 100, timeout = 10000) => {
const waitUntil = async (predicate, step = 100, timeout = 20000) => {
const stopTime = Date.now() + timeout
while (Date.now() <= stopTime) {
const result = await predicate()
@@ -37,6 +44,17 @@ const sendTokens = async (rpcUrl, account, tokenAddress, recipientAddress) => {
})
}
const sendAMBMessage = async (rpcUrl, account, boxAddress, bridgeAddress, boxOtherSideAddress) => {
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({
from: account.address,
gas: '400000'
})
}
const addValidator = async (rpcUrl, account, bridgeAddress) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(account.privateKey)
@@ -49,9 +67,57 @@ const addValidator = async (rpcUrl, account, bridgeAddress) => {
})
}
const migrateToMCD = async (rpcUrl, bridgeAddress) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(validator.privateKey)
const bridgeContract = new web3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, bridgeAddress)
await bridgeContract.methods.migrateToMCD().send({
from: validator.address,
gas: '4000000'
})
}
const initializeChaiToken = async (rpcUrl, bridgeAddress) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(validator.privateKey)
const bridgeContract = new web3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, bridgeAddress)
await bridgeContract.methods.initializeChaiToken().send({
from: validator.address,
gas: '1000000'
})
}
const setMinDaiTokenBalance = async (rpcUrl, bridgeAddress, limit) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(validator.privateKey)
const bridgeContract = new web3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, bridgeAddress)
await bridgeContract.methods.setMinDaiTokenBalance(web3.utils.toWei(limit)).send({
from: validator.address,
gas: '1000000'
})
}
const convertDaiToChai = async (rpcUrl, bridgeAddress) => {
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl))
web3.eth.accounts.wallet.add(validator.privateKey)
const bridgeContract = new web3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, bridgeAddress)
await bridgeContract.methods.convertDaiToChai().send({
from: validator.address,
gas: '1000000'
})
}
module.exports = {
waitUntil,
sendEther,
sendTokens,
addValidator
addValidator,
sendAMBMessage,
migrateToMCD,
initializeChaiToken,
setMinDaiTokenBalance,
convertDaiToChai
}

View File

@@ -3,10 +3,11 @@ FILES=(getBalances.json validators.json eventsStats.json alerts.json)
check_files_exist() {
rc=0
for f in "${FILES[@]}"; do
command="test -f responses/$f"
command="test -f responses/bridge/$f"
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "$command") || rc=1
(docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-amb /bin/bash -c "$command") || rc=1
done
return $rc
}

View File

@@ -1,3 +1,5 @@
MONITOR_BRIDGE_NAME=bridge
COMMON_HOME_RPC_URL=https://sokol.poa.network
COMMON_FOREIGN_RPC_URL=https://kovan.infura.io/mew
COMMON_HOME_BRIDGE_ADDRESS=0xABb4C1399DcC28FBa3Beb76CAE2b50Be3e087353

View File

@@ -8,5 +8,8 @@
"rules": {
"no-use-before-define": "off",
"node/no-unpublished-require": "off"
},
"env": {
"mocha": true
}
}

View File

@@ -15,9 +15,9 @@ RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./b
COPY ./commons ./commons
COPY ./monitor ./monitor
ARG DOT_ENV_PATH=./monitor/.env
COPY ${DOT_ENV_PATH} ./monitor/.env
WORKDIR /mono/monitor
CMD echo "To start the monitor run:" \
"yarn check-and-start"
CMD echo "To start the monitor web service run:" \
"yarn start" \
"To run monitor scripts run:" \
"yarn check-all"

View File

@@ -4,7 +4,12 @@ Tool for checking balances and unprocessed events in bridged networks.
## Overview
Please refer to the [POA TokenBridge](../README.md) overview first of all.
- Deployed version: https://bridge-monitoring.poa.net/
- Deployed version serves several monitor configurations:
* https://bridge-monitoring.poa.net/poa
* https://bridge-monitoring.poa.net/xdai
* https://bridge-monitoring.poa.net/wetc
* https://bridge-monitoring.poa.net/amb-dai
* https://bridge-monitoring.poa.net/amb-poa
This tool allows you to spin up a NODE.JS server to monitor for health of the TokenBridge contracts: check for the balance difference, discover inconsistency in the validators list, catch unhandled transactions.
@@ -122,14 +127,19 @@ yarn start
You can run web interface via [pm2](https://www.npmjs.com/package/pm2) or similar supervisor program.
Using Docker:
```
docker-compose up -d
```
* to run it very first time (or after changes related to the monitor code):
```
docker-compose -f docker-compose-build.yml -f docker-compose.yml up -d --build
```
* next time (or in case of usage of an official docker image)
```
docker-compose up -d
```
- The application will run on `http://localhost:PORT`, where `PORT` is specified in your `.env` file.
- The application will run on `http://localhost:MONITOR_PORT/MONITOR_BRIDGE_NAME`, where `MONITOR_PORT` and `MONITOR_BRIDGE_NAME` are specified in your `.env` file.
- To enabled debug logging, set `DEBUG=1` variable in `.env`.
## Check balances of contracts and validators, get unprocessed events
## Preparing statistic about balances of bridge contracts and validators, get unprocessed events
Using Yarn:
```
@@ -138,13 +148,32 @@ yarn check-all
Using Docker:
```
docker-compose exec monitor yarn check-all
docker run --rm --env-file .env -v $(pwd)/responses:/mono/monitor/responses \
poanetwork/tokenbridge-monitor:latest /bin/bash -c 'yarn check-all'
```
As soon as the process finishes, use the URL described above to get the statistic.
### Cron
You can create cron job to run workers (see `crontab.example` for reference):
## Ad-hoc monitoring
There is a possibility to get bridge statistics without running the web interface use the commands provided above. In this case the results will be located in the `responses` directory.
## Build the image without running the monitor
To build the image change the directory:
```
cd monitor
```
And run the docker composer:
```
docker-compose -f docker-compose-build.yml build
```
## Linting
Running linter:

View File

@@ -3,7 +3,7 @@ const Web3 = require('web3')
const logger = require('./logger')('alerts')
const eventsInfo = require('./utils/events')
const { getBlockNumber } = require('./utils/contract')
const { processedMsgNotDelivered } = require('./utils/message')
const { processedMsgNotDelivered, eventWithoutReference } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { COMMON_HOME_RPC_URL, COMMON_FOREIGN_RPC_URL } = process.env
@@ -29,10 +29,9 @@ async function main() {
xSignatures = homeToForeignConfirmations.filter(processedMsgNotDelivered(homeToForeignRequests))
xAffirmations = foreignToHomeConfirmations.filter(processedMsgNotDelivered(foreignToHomeRequests))
} else {
xSignatures = homeToForeignConfirmations.filter(findDifferences(homeToForeignRequests))
xAffirmations = foreignToHomeConfirmations.filter(findDifferences(foreignToHomeRequests))
xSignatures = homeToForeignConfirmations.filter(eventWithoutReference(homeToForeignRequests))
xAffirmations = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
}
logger.debug('building misbehavior blocks')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
@@ -52,8 +51,8 @@ async function main() {
const foreignValidators = await Promise.all(xSignatures.map(event => findTxSender(web3Foreign)(event)))
const homeValidators = await Promise.all(xAffirmations.map(event => findTxSender(web3Home)(event)))
const xSignaturesTxs = xSignatures.map(normalizeEventInformation).reduce(buildTxList(foreignValidators), {})
const xAffirmationsTxs = xAffirmations.map(normalizeEventInformation).reduce(buildTxList(homeValidators), {})
const xSignaturesTxs = xSignatures.reduce(buildTxList(foreignValidators), {})
const xAffirmationsTxs = xAffirmations.reduce(buildTxList(homeValidators), {})
logger.debug('Done')
@@ -150,7 +149,7 @@ const findTxSender = web3 => async ({ transactionHash }) => {
* }}}
*/
const buildTxList = validatorsList => (acc, event, index) => {
acc[event.txHash] = {
acc[event.transactionHash] = {
value: event.value,
block: event.blockNumber,
referenceTx: event.referenceTx,
@@ -160,38 +159,4 @@ const buildTxList = validatorsList => (acc, event, index) => {
return acc
}
/**
* Finds a missing destDeposit in src list if there's any
* @param {Array} src
* @returns {function(*=): boolean}
*/
const findDifferences = src => dest => {
const b = normalizeEventInformation(dest)
return (
src
.map(normalizeEventInformation)
.filter(a => a.referenceTx === b.referenceTx && a.recipient === b.recipient && a.value === b.value).length === 0
)
}
/**
* Normalizes the different event objects to facilitate data processing
* @param {Object} event
* @returns {{
* txHash: string,
* blockNumber: number,
* referenceTx: string,
* recipient: string | *,
* value: *
* }}
*/
const normalizeEventInformation = event => ({
txHash: event.transactionHash,
blockNumber: event.blockNumber,
referenceTx: event.returnValues.transactionHash || event.transactionHash,
recipient: event.returnValues.recipient || event.returnValues.from,
value: event.returnValues.value
})
module.exports = main

View File

@@ -1,13 +1,17 @@
const fs = require('fs')
const path = require('path')
const Web3 = require('web3')
const logger = require('./logger')('checkWorker')
const { getBridgeMode } = require('../commons')
const getBalances = require('./getBalances')
const getShortEventStats = require('./getShortEventStats')
const validators = require('./validators')
const { writeFile, createDir } = require('./utils/file')
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 { COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_RPC_URL } = process.env
const homeProvider = new Web3.providers.HttpProvider(COMMON_HOME_RPC_URL)
const web3Home = new Web3(homeProvider)
@@ -15,6 +19,7 @@ const { HOME_ERC_TO_ERC_ABI } = require('../commons')
async function checkWorker() {
try {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
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)
@@ -26,12 +31,35 @@ async function checkWorker() {
const foreign = Object.assign({}, balances.foreign, events.foreign)
const status = Object.assign({}, balances, events, { home }, { foreign })
if (!status) throw new Error('status is empty: ' + JSON.stringify(status))
fs.writeFileSync(path.join(__dirname, '/responses/getBalances.json'), JSON.stringify(status, null, 4))
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/getBalances.json`, status)
logger.debug('calling validators()')
const vBalances = await validators(bridgeMode)
if (!vBalances) throw new Error('vBalances is empty: ' + JSON.stringify(vBalances))
fs.writeFileSync(path.join(__dirname, '/responses/validators.json'), JSON.stringify(vBalances, null, 4))
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
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/validators.json`, vBalances)
logger.debug('Done')
} catch (e) {
logger.error(e)

View File

@@ -1,19 +1,28 @@
const fs = require('fs')
const path = require('path')
const logger = require('./logger')('checkWorker2')
const eventsStats = require('./eventsStats')
const alerts = require('./alerts')
const { writeFile, createDir } = require('./utils/file')
const { MONITOR_BRIDGE_NAME } = process.env
async function checkWorker2() {
try {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling eventsStats()')
const evStats = await eventsStats()
if (!evStats) throw new Error('evStats is empty: ' + JSON.stringify(evStats))
fs.writeFileSync(path.join(__dirname, '/responses/eventsStats.json'), JSON.stringify(evStats, null, 4))
evStats.ok =
(evStats.onlyInHomeDeposits || evStats.home.deliveredMsgNotProcessedInForeign).length === 0 &&
(evStats.onlyInForeignDeposits || evStats.home.processedMsgNotDeliveredInForeign).length === 0 &&
(evStats.onlyInHomeWithdrawals || evStats.foreign.deliveredMsgNotProcessedInHome).length === 0 &&
(evStats.onlyInForeignWithdrawals || evStats.foreign.processedMsgNotDeliveredInHome).length === 0
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/eventsStats.json`, evStats)
logger.debug('calling alerts()')
const _alerts = await alerts()
if (!_alerts) throw new Error('alerts is empty: ' + JSON.stringify(_alerts))
fs.writeFileSync(path.join(__dirname, '/responses/alerts.json'), JSON.stringify(_alerts, null, 4))
_alerts.ok = !_alerts.executeAffirmations.mostRecentTxHash && !_alerts.executeSignatures.mostRecentTxHash
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/alerts.json`, _alerts)
logger.debug('Done x2')
} catch (e) {
logger.error(e)

View File

@@ -1,16 +1,27 @@
const fs = require('fs')
const path = require('path')
const Web3 = require('web3')
const logger = require('./logger')('checkWorker3')
const stuckTransfers = require('./stuckTransfers')
const { writeFile, createDir } = require('./utils/file')
const { MONITOR_BRIDGE_NAME, COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_RPC_URL } = 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 {
logger.debug('calling stuckTransfers()')
const transfers = await stuckTransfers()
// console.log(transfers)
if (!transfers) throw new Error('transfers is empty: ' + JSON.stringify(transfers))
fs.writeFileSync(path.join(__dirname, '/responses/stuckTransfers.json'), JSON.stringify(transfers, null, 4))
logger.debug('Done')
const homeBridge = new web3Home.eth.Contract(HOME_NATIVE_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
const bridgeMode = await getBridgeMode(homeBridge)
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling stuckTransfers()')
const transfers = await stuckTransfers()
if (!transfers) throw new Error('transfers is empty: ' + JSON.stringify(transfers))
transfers.ok = transfers.total.length === 0
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/stuckTransfers.json`, transfers)
logger.debug('Done')
}
} catch (e) {
logger.error('checkWorker3.js', e)
}

0
monitor/configs/.gitkeep Normal file
View File

View File

@@ -1,5 +1,5 @@
# Yarn:
*/4 * * * * cd $HOME/bridge-monitor; yarn check-all >>cronWorker.out 2>>cronWorker.err
*/4 * * * * cd $HOME/tokenbridge/monitor; yarn check-all >>cronWorker.out 2>>cronWorker.err
# Docker:
*/4 * * * * cd $HOME/bridge-monitor; docker-compose exec monitor yarn check-all >>cronWorker.out 2>>cronWorker.err
*/4 * * * * cd $HOME/tokenbridge/monitor; docker run --rm --env-file .env -v $(pwd)/responses:/mono/monitor/responses poanetwork/tokenbridge-monitor:latest /bin/bash -c 'yarn check-all' >>cronWorker.out 2>>cronWorker.err

View File

@@ -0,0 +1,6 @@
---
version: '2.4'
services:
monitor:
image: poanetwork/tokenbridge-monitor
build: .

View File

@@ -2,13 +2,13 @@
version: '2.4'
services:
monitor:
build:
context: ..
dockerfile: monitor/Dockerfile
image: poanetwork/tokenbridge-monitor:latest
ports:
- "${MONITOR_PORT}:${MONITOR_PORT}"
env_file: ./.env
environment:
- NODE_ENV=production
volumes:
- ./responses:/mono/monitor/responses
restart: unless-stopped
entrypoint: "yarn check-and-start"
entrypoint: "yarn start"

View File

@@ -1,69 +1,14 @@
require('dotenv').config()
const eventsInfo = require('./utils/events')
const { processedMsgNotDelivered, deliveredMsgNotProcessed } = require('./utils/message')
const { processedMsgNotDelivered, deliveredMsgNotProcessed, eventWithoutReference } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
function compareDepositsHome(foreign) {
return homeDeposit => {
return (
foreign.filter(foreignDeposit => {
return (
foreignDeposit.returnValues.transactionHash === homeDeposit.transactionHash &&
foreignDeposit.returnValues.recipient === homeDeposit.returnValues.recipient &&
foreignDeposit.returnValues.value === homeDeposit.returnValues.value
)
}).length === 0
)
}
}
function compareDepositsForeign(home) {
return foreignDeposit => {
return (
home.filter(homeDeposit => {
return (
homeDeposit.transactionHash === foreignDeposit.returnValues.transactionHash &&
homeDeposit.returnValues.recipient === foreignDeposit.returnValues.recipient &&
homeDeposit.returnValues.value === foreignDeposit.returnValues.value
)
}).length === 0
)
}
}
function compareTransferHome(foreign) {
return homeDeposit => {
return (
foreign.filter(foreignDeposit => {
return (
homeDeposit.returnValues.transactionHash === foreignDeposit.transactionHash &&
homeDeposit.returnValues.recipient === foreignDeposit.returnValues.from &&
homeDeposit.returnValues.value === foreignDeposit.returnValues.value
)
}).length === 0
)
}
}
function compareTransferForeign(home) {
return foreignDeposit => {
return (
home.filter(homeDeposit => {
return (
foreignDeposit.transactionHash === homeDeposit.returnValues.transactionHash &&
foreignDeposit.returnValues.from === homeDeposit.returnValues.recipient &&
foreignDeposit.returnValues.value === homeDeposit.returnValues.value
)
}).length === 0
)
}
}
async function main() {
const {
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
isExternalErc20,
bridgeMode
} = await eventsInfo()
@@ -88,17 +33,11 @@ async function main() {
lastChecked: Math.floor(Date.now() / 1000)
}
} else {
const onlyInHomeDeposits = homeToForeignRequests.filter(compareDepositsHome(homeToForeignConfirmations))
const onlyInForeignDeposits = homeToForeignConfirmations
.concat([])
.filter(compareDepositsForeign(homeToForeignRequests))
const onlyInHomeDeposits = homeToForeignRequests.filter(eventWithoutReference(homeToForeignConfirmations))
const onlyInForeignDeposits = homeToForeignConfirmations.filter(eventWithoutReference(homeToForeignRequests))
const onlyInHomeWithdrawals = isExternalErc20
? foreignToHomeConfirmations.filter(compareTransferHome(foreignToHomeRequests))
: foreignToHomeConfirmations.filter(compareDepositsForeign(foreignToHomeRequests))
const onlyInForeignWithdrawals = isExternalErc20
? foreignToHomeRequests.filter(compareTransferForeign(foreignToHomeConfirmations))
: foreignToHomeRequests.filter(compareDepositsHome(foreignToHomeConfirmations))
const onlyInHomeWithdrawals = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
const onlyInForeignWithdrawals = foreignToHomeRequests.filter(eventWithoutReference(foreignToHomeConfirmations))
return {
onlyInHomeDeposits,

View File

@@ -83,6 +83,44 @@ async function main(bridgeMode) {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
let investedAmountInDai = 0
let bridgeDsrBalance = 0
let foreignHalfDuplexErc20Balance = 0
let displayChaiToken = false
let displayHalfDuplexToken = false
let tokenSwapAllowed = false
try {
const halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call()
if (halfDuplexTokenAddress !== erc20Address) {
const halfDuplexToken = new web3Foreign.eth.Contract(ERC20_ABI, halfDuplexTokenAddress)
logger.debug('calling halfDuplexToken.methods.balanceOf')
foreignHalfDuplexErc20Balance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
logger.debug('getting last block numbers')
const block = await web3Foreign.eth.getBlock('latest')
logger.debug(`Checking if SCD Emergency Shutdown has happened`)
tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
displayHalfDuplexToken = true
}
} catch (e) {
logger.debug('Methods for half duplex token are not present')
}
try {
logger.debug('calling foreignBridge.methods.isChaiTokenEnabled')
if (await foreignBridge.methods.isChaiTokenEnabled().call()) {
displayChaiToken = true
logger.debug('calling foreignBridge.methods.investedAmountInDai')
investedAmountInDai = await foreignBridge.methods.investedAmountInDai().call()
logger.debug('calling foreignBridge.methods.dsrBalance')
bridgeDsrBalance = await foreignBridge.methods.dsrBalance().call()
} else {
logger.debug('Chai token is currently disabled')
}
} catch (e) {
logger.debug('Methods for chai token are not present')
}
logger.debug('calling erc20Contract.methods.balanceOf')
const foreignErc20Balance = await erc20Contract.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
@@ -91,7 +129,7 @@ async function main(bridgeMode) {
const blockRewardAddress = await homeBridge.methods.blockRewardContract().call()
const blockRewardContract = new web3Home.eth.Contract(BLOCK_REWARD_ABI, blockRewardAddress)
logger.debug('calling blockReward.methods.mintedTotally')
const mintedCoins = await blockRewardContract.methods.mintedTotally().call()
const mintedCoins = await blockRewardContract.methods.mintedTotallyByBridge(COMMON_HOME_BRIDGE_ADDRESS).call()
logger.debug('calling homeBridge.methods.totalBurntCoins')
const burntCoins = await homeBridge.methods.totalBurntCoins().call()
@@ -99,16 +137,44 @@ async function main(bridgeMode) {
const burntCoinsBN = new BN(burntCoins)
const totalSupplyBN = mintedCoinsBN.minus(burntCoinsBN)
const foreignErc20BalanceBN = new BN(foreignErc20Balance)
const investedAmountInDaiBN = new BN(investedAmountInDai)
const bridgeDsrBalanceBN = new BN(bridgeDsrBalance)
const halfDuplexErc20BalanceBN =
displayHalfDuplexToken && tokenSwapAllowed ? new BN(foreignHalfDuplexErc20Balance) : new BN(0)
const diff = foreignErc20BalanceBN
.plus(halfDuplexErc20BalanceBN)
.plus(investedAmountInDaiBN)
.minus(totalSupplyBN)
.toFixed()
let foreign = {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
}
if (displayHalfDuplexToken && tokenSwapAllowed) {
foreign = {
...foreign,
halfDuplexErc20Balance: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
}
} else if (displayHalfDuplexToken && !tokenSwapAllowed) {
foreign = {
...foreign,
halfDuplexErc20BalanceAfterES: Web3Utils.fromWei(foreignHalfDuplexErc20Balance)
}
}
if (displayChaiToken) {
foreign.investedErc20Balance = Web3Utils.fromWei(investedAmountInDai)
foreign.accumulatedInterest = Web3Utils.fromWei(bridgeDsrBalanceBN.minus(investedAmountInDaiBN).toString(10))
}
const diff = foreignErc20BalanceBN.minus(totalSupplyBN).toFixed()
logger.debug('Done')
return {
home: {
totalSupply: Web3Utils.fromWei(totalSupplyBN.toFixed())
},
foreign: {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
},
foreign,
balanceDiff: Number(Web3Utils.fromWei(diff)),
lastChecked: Math.floor(Date.now() / 1000)
}

View File

@@ -1,45 +1,16 @@
require('dotenv').config()
const express = require('express')
const fs = require('fs')
const { isV1Bridge } = require('./utils/serverUtils')
const { readFile } = require('./utils/file')
const app = express()
const bridgeRouter = express.Router({ mergeParams: true })
const MONITOR_TX_NUMBER_THRESHOLD = Number(process.env.MONITOR_TX_NUMBER_THRESHOLD) || 100
console.log('MONITOR_TX_NUMBER_THRESHOLD = ' + MONITOR_TX_NUMBER_THRESHOLD)
app.get('/favicon.ico', (req, res) => res.sendStatus(204))
app.use('/:bridgeName', bridgeRouter)
async function readFile(path) {
bridgeRouter.get('/', async (req, res, next) => {
try {
const content = await fs.readFileSync(path)
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
} catch (e) {
console.error(e)
return {
error: 'please check your worker'
}
}
}
async function initV1routes(app) {
const exposeV1Routes = await isV1Bridge()
if (exposeV1Routes) {
app.get('/stuckTransfers', async (req, res, next) => {
try {
const results = await readFile('./responses/stuckTransfers.json')
results.ok = results.total.length === 0
res.json(results)
} catch (e) {
next(e)
}
})
}
}
app.get('/', async (req, res, next) => {
try {
const results = await readFile('./responses/getBalances.json')
const results = await readFile(`./responses/${req.params.bridgeName}/getBalances.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
@@ -47,24 +18,9 @@ app.get('/', async (req, res, next) => {
}
})
app.get('/validators', async (req, res, next) => {
bridgeRouter.get('/validators', async (req, res, next) => {
try {
const results = await readFile('./responses/validators.json')
results.homeOk = true
results.foreignOk = true
for (const hv in results.home.validators) {
if (results.home.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
results.homeOk = false
break
}
}
for (const hv in results.foreign.validators) {
if (results.foreign.validators[hv].leftTx < MONITOR_TX_NUMBER_THRESHOLD) {
results.foreignOk = false
break
}
}
results.ok = results.homeOk && results.foreignOk
const results = await readFile(`./responses/${req.params.bridgeName}/validators.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
@@ -72,15 +28,9 @@ app.get('/validators', async (req, res, next) => {
}
})
// responses/eventsStats.json
app.get('/eventsStats', async (req, res, next) => {
bridgeRouter.get('/eventsStats', async (req, res, next) => {
try {
const results = await readFile('./responses/eventsStats.json')
results.ok =
results.onlyInHomeDeposits.length === 0 &&
results.onlyInForeignDeposits.length === 0 &&
results.onlyInHomeWithdrawals.length === 0 &&
results.onlyInForeignWithdrawals.length === 0
const results = await readFile(`./responses/${req.params.bridgeName}/eventsStats.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
@@ -88,17 +38,23 @@ app.get('/eventsStats', async (req, res, next) => {
}
})
app.get('/alerts', async (req, res, next) => {
bridgeRouter.get('/alerts', async (req, res, next) => {
try {
const results = await readFile('./responses/alerts.json')
results.ok = !results.executeAffirmations.mostRecentTxHash && !results.executeSignatures.mostRecentTxHash
const results = await readFile(`./responses/${req.params.bridgeName}/alerts.json`)
res.json(results)
} catch (e) {
next(e)
}
})
initV1routes(app)
bridgeRouter.get('/stuckTransfers', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/stuckTransfers.json`)
res.json(results)
} catch (e) {
next(e)
}
})
const port = process.env.MONITOR_PORT || 3003
app.set('port', port)

View File

@@ -4,11 +4,12 @@
"description": "",
"main": "index.js",
"scripts": {
"check-all": "timeout -s 9 5m node checkWorker.js && timeout -s 9 5m node checkWorker2.js",
"check-all": "timeout -s 9 5m node checkWorker.js && timeout -s 9 5m node checkWorker2.js && timeout -s 9 5m node checkWorker3.js",
"start": "node index.js",
"check-and-start": "yarn check-all && yarn start",
"lint": "eslint . --ignore-path ../.eslintignore",
"lint:fix": "eslint . --fix"
"lint:fix": "eslint . --fix",
"test": "NODE_ENV=test mocha"
},
"author": "",
"license": "ISC",
@@ -23,5 +24,6 @@
"node": ">=8.9"
},
"devDependencies": {
"chai": "^4.2.0"
}
}

View File

@@ -1,9 +0,0 @@
#!/bin/bash
cd $(dirname $0)/..
if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
# https://github.com/docker/compose/issues/3352
COMPOSE_INTERACTIVE_NO_CLI=1 /usr/local/bin/docker-compose exec -T monitor /bin/bash -c 'yarn check-all'
else
echo "Monitor is not running, skipping checks."
fi

View File

@@ -0,0 +1,11 @@
#!/bin/bash
cd $(dirname $0)/..
if /usr/local/bin/docker-compose ps | grep -q -i 'monitor'; then
for file in configs/*.env
do
docker run --rm --env-file $file -v $(pwd)/responses:/mono/monitor/responses poanetwork/tokenbridge-monitor:latest /bin/bash -c 'yarn check-all'
done
else
echo "Monitor is not running, skipping checks."
fi

View File

@@ -0,0 +1,206 @@
const { expect } = require('chai')
const { normalizeEventInformation, eventWithoutReference } = require('../utils/message')
describe('normalizeEventInformation', () => {
it('should return normalized object for UserRequestForSignature event', () => {
// Given
const event = {
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
blockNumber: 324231,
transactionHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
returnValues: {
recipient: '0xA84944735B66e957Fe385567dcc85975022Fe68A',
value: '100000000000000000000'
},
event: 'UserRequestForSignature'
}
// When
const result = normalizeEventInformation(event)
// Then
expect(result.transactionHash).to.equal(event.transactionHash)
expect(result.blockNumber).to.equal(event.blockNumber)
expect(result.referenceTx).to.equal(event.transactionHash)
expect(result.recipient).to.equal(event.returnValues.recipient)
expect(result.value).to.equal(event.returnValues.value)
})
it('should return normalized object for UserRequestForAffirmation event', () => {
// Given
const event = {
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
blockNumber: 324231,
transactionHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
returnValues: {
recipient: '0xA84944735B66e957Fe385567dcc85975022Fe68A',
value: '100000000000000000000'
},
event: 'UserRequestForAffirmation'
}
// When
const result = normalizeEventInformation(event)
// Then
expect(result.transactionHash).to.equal(event.transactionHash)
expect(result.blockNumber).to.equal(event.blockNumber)
expect(result.referenceTx).to.equal(event.transactionHash)
expect(result.recipient).to.equal(event.returnValues.recipient)
expect(result.value).to.equal(event.returnValues.value)
})
it('should return normalized object for transfer event', () => {
// Given
const event = {
address: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359',
blockNumber: 6593953,
transactionHash: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
returnValues: {
from: '0x13C0a8009A578837fB7A80Aa252F6A3ba4aD6B79',
to: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016',
value: '4000000000000000000'
},
event: 'Transfer'
}
// When
const result = normalizeEventInformation(event)
// Then
expect(result.transactionHash).to.equal(event.transactionHash)
expect(result.blockNumber).to.equal(event.blockNumber)
expect(result.referenceTx).to.equal(event.transactionHash)
expect(result.recipient).to.equal(event.returnValues.from)
expect(result.value).to.equal(event.returnValues.value)
})
it('should return normalized object for RelayedMessage event', () => {
// Given
const event = {
address: '0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016',
blockNumber: 7025826,
transactionHash: '0x6ee5969973da763d6d9f162d2dd1b1ec34c2dd977dc39e6b25030b4f04471567',
returnValues: {
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
value: '4900000000000000000',
transactionHash: '0x5c5c2ab5e333bda4acd035a6a30ea29c7370351891d85373b2d06c7cc6cbb210'
},
event: 'RelayedMessage'
}
// When
const result = normalizeEventInformation(event)
// Then
expect(result.transactionHash).to.equal(event.transactionHash)
expect(result.blockNumber).to.equal(event.blockNumber)
expect(result.referenceTx).to.equal(event.returnValues.transactionHash)
expect(result.recipient).to.equal(event.returnValues.recipient)
expect(result.value).to.equal(event.returnValues.value)
})
it('should return normalized object for AffirmationCompleted event', () => {
// Given
const event = {
address: '0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6',
blockNumber: 474439,
transactionHash: '0x654004b372ba32754cef34f403153bbdf43f0fbb3191d5e4683bba7f32e0dc4a',
returnValues: {
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
value: '5000000000000000000',
transactionHash: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d'
},
event: 'AffirmationCompleted'
}
// When
const result = normalizeEventInformation(event)
// Then
expect(result.transactionHash).to.equal(event.transactionHash)
expect(result.blockNumber).to.equal(event.blockNumber)
expect(result.referenceTx).to.equal(event.returnValues.transactionHash)
expect(result.recipient).to.equal(event.returnValues.recipient)
expect(result.value).to.equal(event.returnValues.value)
})
})
describe('eventWithoutReference', () => {
it('should return false if event is present', () => {
// Given
const event = {
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
value: '5000000000000000000'
}
const otherSideEvents = [
{
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
value: '5000000000000000000'
},
{
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
value: '6000000000000000000'
},
{
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
value: '8000000000000000000'
}
]
// When
const result = eventWithoutReference(otherSideEvents)(event)
// Then
expect(result).to.equal(false)
})
it('should return true if event is not present', () => {
// Given
const event = {
txHash: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d',
blockNumber: 474439,
referenceTx: '0xe96da94bbda2cfc865acd3f98040f5c79a627ee9de839d86885d34acd8ecd10d',
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
value: '2000000000000000000'
}
const otherSideEvents = [
{
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x9b7b2B4f7a391b6F14A81221AE0920A9735B67Fb',
value: '5000000000000000000'
},
{
txHash: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
blockNumber: 474439,
referenceTx: '0x05afb402e27946d3600b100020dc23419ffd10cb61d3b241cee7b4a84909b48a',
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
value: '6000000000000000000'
},
{
txHash: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
blockNumber: 474439,
referenceTx: '0x17be1e0745136b9e2857124542f8218812db8fe4458236d5ae045c1ceeb79978',
recipient: '0x38BC00Ea43EbB5ef5150593A0BA6C381803717e2',
value: '8000000000000000000'
}
]
// When
const result = eventWithoutReference(otherSideEvents)(event)
// Then
expect(result).to.equal(true)
})
})

View File

@@ -11,8 +11,11 @@ const {
ERC20_ABI,
ERC677_BRIDGE_TOKEN_ABI,
getTokenType,
getPastEvents
getPastEvents,
ZERO_ADDRESS
} = require('../../commons')
const { normalizeEventInformation } = require('./message')
const { filterTransferBeforeES } = require('./tokenUtils')
const {
COMMON_HOME_RPC_URL,
@@ -40,56 +43,136 @@ async function main(mode) {
const v1Bridge = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1
let isExternalErc20
let erc20Contract
let erc20Address
let normalizeEvent = normalizeEventInformation
if (bridgeMode !== BRIDGE_MODES.ARBITRARY_MESSAGE) {
const erc20MethodName = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || v1Bridge ? 'erc677token' : 'erc20token'
const erc20Address = await foreignBridge.methods[erc20MethodName]().call()
erc20Address = await foreignBridge.methods[erc20MethodName]().call()
const tokenType = await getTokenType(
new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, erc20Address),
COMMON_FOREIGN_BRIDGE_ADDRESS
)
isExternalErc20 = tokenType === ERC_TYPES.ERC20
erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
} else {
normalizeEvent = e => e
}
logger.debug('getting last block numbers')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
logger.debug("calling homeBridge.getPastEvents('UserRequestForSignature')")
const homeToForeignRequests = await getPastEvents(homeBridge, {
const homeToForeignRequests = (await getPastEvents(homeBridge, {
event: v1Bridge ? 'Deposit' : 'UserRequestForSignature',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber
})
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('RelayedMessage')")
const homeToForeignConfirmations = await getPastEvents(foreignBridge, {
const homeToForeignConfirmations = (await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Deposit' : 'RelayedMessage',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber
})
})).map(normalizeEvent)
logger.debug("calling homeBridge.getPastEvents('AffirmationCompleted')")
const foreignToHomeConfirmations = await getPastEvents(homeBridge, {
const foreignToHomeConfirmations = (await getPastEvents(homeBridge, {
event: v1Bridge ? 'Withdraw' : 'AffirmationCompleted',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber
})
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('UserRequestForAffirmation')")
const foreignToHomeRequests = isExternalErc20
? await getPastEvents(erc20Contract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
})
: await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
let foreignToHomeRequests = (await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber
})).map(normalizeEvent)
if (isExternalErc20) {
logger.debug("calling erc20Contract.getPastEvents('Transfer')")
let transferEvents = (await getPastEvents(erc20Contract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
})).map(normalizeEvent)
let directTransfers = transferEvents
const tokensSwappedAbiExists = FOREIGN_ABI.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
if (tokensSwappedAbiExists) {
const tokensSwappedEvents = await getPastEvents(foreignBridge, {
event: 'TokensSwapped',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber
})
// Get token swap events emitted by foreign bridge
const bridgeTokensSwappedEvents = tokensSwappedEvents.filter(e => e.address === COMMON_FOREIGN_BRIDGE_ADDRESS)
// Get transfer events for each previous erc20
const uniqueTokenAddressesSet = new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))
// Exclude chai token from previous erc20
try {
logger.debug('calling foreignBridge.chaiToken()')
const chaiToken = await foreignBridge.methods.chaiToken().call()
uniqueTokenAddressesSet.delete(chaiToken)
} catch (e) {
logger.debug('call to foreignBridge.chaiToken() failed')
}
// Exclude dai token from previous erc20
try {
logger.debug('calling foreignBridge.erc20token()')
const daiToken = await foreignBridge.methods.erc20token().call()
uniqueTokenAddressesSet.delete(daiToken)
} catch (e) {
logger.debug('call to foreignBridge.erc20token() failed')
}
const uniqueTokenAddresses = [...uniqueTokenAddressesSet]
await Promise.all(
uniqueTokenAddresses.map(async tokenAddress => {
const halfDuplexTokenContract = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
const halfDuplexTransferEvents = (await getPastEvents(halfDuplexTokenContract, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
})).map(normalizeEvent)
// Remove events after the ES
const validHalfDuplexTransfers = await filterTransferBeforeES(
halfDuplexTransferEvents,
web3Foreign,
foreignBridge
)
transferEvents = [...validHalfDuplexTransfers, ...transferEvents]
})
)
// filter transfer that is part of a token swap
directTransfers = transferEvents.filter(
e =>
bridgeTokensSwappedEvents.findIndex(
t => t.transactionHash === e.referenceTx && e.recipient === ZERO_ADDRESS
) === -1
)
}
// Get transfer events that didn't have a UserRequestForAffirmation event in the same transaction
directTransfers = directTransfers.filter(
e => foreignToHomeRequests.findIndex(t => t.referenceTx === e.referenceTx) === -1
)
foreignToHomeRequests = [...foreignToHomeRequests, ...directTransfers]
}
logger.debug('Done')
return {
homeToForeignRequests,

36
monitor/utils/file.js Normal file
View File

@@ -0,0 +1,36 @@
const fs = require('fs')
const path = require('path')
async function readFile(filePath) {
try {
const content = await fs.readFileSync(filePath)
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
} catch (e) {
console.error(e)
return {
error: 'the bridge statistics are not available'
}
}
}
function writeFile(filePath, object) {
fs.writeFileSync(path.join(process.cwd(), filePath), JSON.stringify(object, null, 4))
}
function createDir(dirPath) {
try {
fs.mkdirSync(path.join(process.cwd(), dirPath), { recursive: true })
} catch (e) {
if (!e.message.includes('exists')) {
throw e
}
}
}
module.exports = {
readFile,
writeFile,
createDir
}

View File

@@ -41,7 +41,32 @@ function messageEqualsEvent(parsedMsg, event) {
)
}
/**
* Normalizes the different event objects to facilitate data processing
* @param {Object} event
* @returns {{
* transactionHash: string,
* blockNumber: number,
* referenceTx: string,
* recipient: string | *,
* value: *
* }}
*/
const normalizeEventInformation = event => ({
transactionHash: event.transactionHash,
blockNumber: event.blockNumber,
referenceTx: event.returnValues.transactionHash || event.transactionHash,
recipient: event.returnValues.recipient || event.returnValues.from,
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
module.exports = {
deliveredMsgNotProcessed,
processedMsgNotDelivered
processedMsgNotDelivered,
normalizeEventInformation,
eventWithoutReference
}

View File

@@ -0,0 +1,40 @@
let beforeESBiggestBlockNumber = 0
/**
*
* Returns true if the event was before the Emergency Shutdown.
* The method has an optimization to avoid making request if a bigger block number is confirmed
* to be before the ES. Events should be iterated from newer to older order to use the optimization.
*/
async function transferBeforeES(event, web3Foreign, foreignBridge) {
const { blockNumber } = event
if (blockNumber < beforeESBiggestBlockNumber) {
return true
}
const block = await web3Foreign.eth.getBlock(blockNumber)
const tokenSwapAllowed = await foreignBridge.methods.isTokenSwapAllowed(block.timestamp).call()
if (tokenSwapAllowed) {
beforeESBiggestBlockNumber = blockNumber
}
return tokenSwapAllowed
}
async function filterTransferBeforeES(array, web3Foreign, foreignBridge) {
const newArray = []
// Iterate events from newer to older
for (let i = array.length - 1; i >= 0; i--) {
const beforeES = await transferBeforeES(array[i], web3Foreign, foreignBridge)
if (beforeES) {
// add element to first position so the new array will have the same order
newArray.unshift(array[i])
}
}
return newArray
}
module.exports = {
filterTransferBeforeES
}

View File

@@ -10,12 +10,10 @@ const {
COMMON_FOREIGN_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS,
MONITOR_VALIDATOR_HOME_TX_LIMIT,
COMMON_HOME_GAS_PRICE_SUPPLIER_URL,
COMMON_HOME_GAS_PRICE_SPEED_TYPE,
COMMON_HOME_GAS_PRICE_FALLBACK,
COMMON_HOME_GAS_PRICE_FACTOR,
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT,
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL,
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE,
COMMON_FOREIGN_GAS_PRICE_FALLBACK,
@@ -23,6 +21,8 @@ const {
} = 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
@@ -65,76 +65,97 @@ async function main(bridgeMode) {
const foreignBridgeValidators = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress)
logger.debug('calling foreignBridgeValidators getValidatorList()')
const foreignValidators = await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
const foreignValidators = (await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
from: MONITOR_FOREIGN_START_BLOCK,
to: foreignBlockNumber,
logger
})
})).map(web3Foreign.utils.toChecksumAddress)
logger.debug('calling homeBridgeValidators getValidatorList()')
const homeValidators = await getValidatorList(homeValidatorsAddress, web3Home.eth, {
const homeValidators = (await getValidatorList(homeValidatorsAddress, web3Home.eth, {
from: MONITOR_HOME_START_BLOCK,
to: homeBlockNumber,
logger
})
})).map(web3Home.utils.toChecksumAddress)
const homeBalances = {}
logger.debug('calling asyncForEach homeValidators homeBalances')
await asyncForEach(homeValidators, async v => {
homeBalances[v] = Web3Utils.fromWei(await web3Home.eth.getBalance(v))
})
const foreignVBalances = {}
const homeVBalances = {}
logger.debug('calling home getGasPrices')
const homeGasPrice =
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
const homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
const homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
let homeGasPrice
let homeGasPriceGwei
let homeTxCost
logger.debug('calling foreign getGasPrices')
const foreignGasPrice =
(await gasPriceFromSupplier(() => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL), foreignGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
const foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
const foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))
if (MONITOR_VALIDATOR_HOME_TX_LIMIT) {
logger.debug('calling home getGasPrices')
homeGasPrice =
(await gasPriceFromSupplier(() => fetch(COMMON_HOME_GAS_PRICE_SUPPLIER_URL), homeGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_HOME_GAS_PRICE_FALLBACK)
homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
homeTxCost = homeGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_HOME_TX_LIMIT))
}
let foreignGasPrice
let foreignGasPriceGwei
let foreignTxCost
if (MONITOR_VALIDATOR_FOREIGN_TX_LIMIT) {
logger.debug('calling foreign getGasPrices')
foreignGasPrice =
(await gasPriceFromSupplier(() => fetch(COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL), foreignGasPriceSupplierOpts)) ||
Web3Utils.toBN(COMMON_FOREIGN_GAS_PRICE_FALLBACK)
foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(MONITOR_VALIDATOR_FOREIGN_TX_LIMIT))
}
let validatorsMatch = true
logger.debug('calling asyncForEach foreignValidators foreignVBalances')
await asyncForEach(foreignValidators, async v => {
const balance = await web3Foreign.eth.getBalance(v)
const leftTx = Web3Utils.toBN(balance)
.div(foreignTxCost)
.toString(10)
foreignVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(foreignGasPriceGwei)
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)
}
} 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)
const leftTx = homeTxCost.isZero()
? 999999
: Web3Utils.toBN(balance)
.div(homeTxCost)
.toString(10)
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance),
leftTx: Number(leftTx),
gasPrice: Number(homeGasPriceGwei)
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)
}
} else {
homeVBalances[v] = {
balance: Web3Utils.fromWei(balance)
}
}
if (!foreignValidators.includes(v)) {
validatorsMatch = false
homeVBalances[v].onlyOnHome = true
}
})
logger.debug('calling homeBridgeValidators.methods.requiredSignatures().call()')
const reqSigHome = await homeBridgeValidators.methods.requiredSignatures().call()
logger.debug('calling foreignBridgeValidators.methods.requiredSignatures().call()')

View File

@@ -6,7 +6,7 @@
"scripts": {
"start": "mocha",
"lint": "eslint . --ignore-path ../.eslintignore",
"native-to-erc": "mocha test/nativeToErc.js"
"amb": "ULTIMATE=true mocha test/amb.js"
},
"author": "",
"license": "ISC",

View File

@@ -1,6 +1,6 @@
cd $(dirname $0)
../e2e-commons/up.sh deploy oracle
../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
rc=$?

View File

@@ -1,22 +1,55 @@
const Web3 = require('web3')
const assert = require('assert')
const promiseRetry = require('promise-retry')
const { user, homeRPC, foreignRPC, amb } = require('../../e2e-commons/constants.json')
const { generateNewBlock } = require('../../e2e-commons/utils')
const { BOX_ABI } = require('../../commons')
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 { toBN } = Web3.utils
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
const COMMON_HOME_BRIDGE_ADDRESS = amb.home
const COMMON_FOREIGN_BRIDGE_ADDRESS = amb.foreign
homeWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(validator.privateKey)
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 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', () => {
before(async () => {
// Only 1 validator is used in ultimate tests
if (process.env.ULTIMATE !== 'true') {
// Set 2 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
// Set 2 required signatures for foreign bridge
await setRequiredSignatures({
bridgeContract: foreignBridge,
web3: foreignWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
}
})
describe('Home to Foreign', () => {
describe('Subsidized Mode', () => {
it('should bridge message', async () => {
@@ -25,7 +58,7 @@ describe('arbitrary message bridging', () => {
const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
const setValueTx = await homeBox.methods
await homeBox.methods
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
.send({
from: user.address,
@@ -35,30 +68,8 @@ describe('arbitrary message bridging', () => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= setValueTx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that value changed and balance decreased
await promiseRetry(async retry => {
await uniformRetry(async retry => {
const value = await foreignBox.methods.value().call()
if (!toBN(value).eq(toBN(newValue))) {
retry()
@@ -85,12 +96,8 @@ describe('arbitrary message bridging', () => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// check that value changed and balance decreased
await promiseRetry(async retry => {
await uniformRetry(async retry => {
const value = await homeBox.methods.value().call()
if (!toBN(value).eq(toBN(newValue))) {
retry()

View File

@@ -1,9 +1,9 @@
const Web3 = require('web3')
const assert = require('assert')
const promiseRetry = require('promise-retry')
const { user, ercToErcBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI } = require('../../commons')
const { generateNewBlock } = require('../../e2e-commons/utils')
const { user, secondUser, ercToErcBridge, homeRPC, foreignRPC, validator } = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI, HOME_ERC_TO_ERC_ABI } = require('../../commons')
const { uniformRetry } = require('../../e2e-commons/utils')
const { setRequiredSignatures } = require('./utils')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
@@ -14,19 +14,48 @@ const COMMON_FOREIGN_BRIDGE_ADDRESS = ercToErcBridge.foreign
const { toBN } = foreignWeb3.utils
homeWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(validator.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
const erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.foreignToken)
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc677Token = new homeWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.homeToken)
const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_TO_ERC_ABI, COMMON_HOME_BRIDGE_ADDRESS)
describe('erc to erc', () => {
before(async () => {
// Set 2 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
// Set 2 required signatures for foreign bridge
await setRequiredSignatures({
bridgeContract: foreignBridge,
web3: foreignWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
})
it('should convert tokens in foreign to tokens in home', async () => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
assert(!toBN(balance).isZero(), 'Account should have tokens')
// send tokens to foreign bridge
const firstTransferValue = homeWeb3.utils.toWei('0.01')
// approve tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, firstTransferValue)
.send({
from: user.address,
gas: '1000000'
@@ -35,15 +64,47 @@ describe('erc to erc', () => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// call bridge method to transfer tokens to a different recipient
await foreignBridge.methods
.relayTokens(secondUser.address, firstTransferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await promiseRetry(async retry => {
await uniformRetry(async retry => {
const balance = await erc677Token.methods.balanceOf(user.address).call()
const recipientBalance = await erc677Token.methods.balanceOf(secondUser.address).call()
assert(toBN(balance).isZero(), 'User balance should be the same')
if (toBN(recipientBalance).isZero()) {
retry()
}
})
const secondTransferValue = homeWeb3.utils.toWei('0.05')
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, secondTransferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await uniformRetry(async retry => {
const balance = await erc677Token.methods.balanceOf(user.address).call()
if (toBN(balance).isZero()) {
retry()
} else {
assert(toBN(balance).eq(toBN(secondTransferValue)), 'User balance should be increased only by second transfer')
}
})
})
@@ -55,7 +116,7 @@ describe('erc to erc', () => {
assert(!toBN(balance).isZero(), 'Account should have tokens')
// send transaction to home bridge
const depositTx = await erc677Token.methods
await erc677Token.methods
.transferAndCall(COMMON_HOME_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'), '0x')
.send({
from: user.address,
@@ -65,30 +126,8 @@ describe('erc to erc', () => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= depositTx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that balance increases
await promiseRetry(async retry => {
await uniformRetry(async retry => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
if (toBN(balance).lte(toBN(originalBalance))) {
retry()

View File

@@ -1,9 +1,17 @@
const Web3 = require('web3')
const assert = require('assert')
const promiseRetry = require('promise-retry')
const { user, ercToNativeBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI } = require('../../commons')
const { generateNewBlock } = require('../../e2e-commons/utils')
const {
user,
secondUser,
validator,
ercToNativeBridge,
homeRPC,
foreignRPC
} = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI, SAI_TOP, HOME_ERC_TO_NATIVE_ABI } = require('../../commons')
const { uniformRetry, sleep } = require('../../e2e-commons/utils')
const { setRequiredSignatures } = require('./utils')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(foreignRPC.URL))
@@ -14,19 +22,56 @@ const COMMON_FOREIGN_BRIDGE_ADDRESS = ercToNativeBridge.foreign
const { toBN } = foreignWeb3.utils
homeWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(validator.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(validator.privateKey)
const erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToNativeBridge.foreignToken)
const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_TO_NATIVE_ABI, COMMON_HOME_BRIDGE_ADDRESS)
describe('erc to native', () => {
it('should convert tokens in foreign to coins in home', async () => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
let halfDuplexTokenAddress
let halfDuplexToken
before(async () => {
halfDuplexTokenAddress = await foreignBridge.methods.halfDuplexErc20token().call()
halfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, halfDuplexTokenAddress)
// Set 2 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
// Set 2 required signatures for foreign bridge
await setRequiredSignatures({
bridgeContract: foreignBridge,
web3: foreignWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
})
it('should continue working after migration', async () => {
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
assert(!toBN(balance).isZero(), 'Account should have tokens')
const transferValue = homeWeb3.utils.toWei('0.01')
// erc20 token address and half duplex address are the same before migration
const tokenAddress = await foreignBridge.methods.erc20token().call()
const erc20AndhalfDuplexToken = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, tokenAddress)
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
await erc20AndhalfDuplexToken.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
.send({
from: user.address,
gas: '1000000'
@@ -35,15 +80,364 @@ describe('erc to native', () => {
console.error(e)
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(foreignWeb3, user.address)
// check that balance increases
await promiseRetry(async (retry, number) => {
const balance = await homeWeb3.eth.getBalance(user.address)
// retry at least 4 times to check transfer is not double processed by the two watchers
if (toBN(balance).lte(toBN(originalBalanceOnHome)) || number < 4) {
retry()
} else {
assert(
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(transferValue))),
'User balance should be increased only by second transfer'
)
}
})
// call migration
await foreignBridge.methods.migrateToMCD().send({
from: validator.address,
gas: '4000000'
})
// update min threshold for swap
await foreignBridge.methods.setMinHDTokenBalance(foreignWeb3.utils.toWei('2', 'ether')).send({
from: validator.address,
gas: '1000000'
})
const AfterMigrateBalance = await homeWeb3.eth.getBalance(user.address)
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await promiseRetry(async retry => {
await promiseRetry(async (retry, number) => {
const balance = await homeWeb3.eth.getBalance(user.address)
// retry at least 4 times to check transfer is not double processed by the two watchers
if (toBN(balance).lte(toBN(AfterMigrateBalance)) || number < 4) {
retry()
} else {
assert(
toBN(balance).eq(toBN(AfterMigrateBalance).add(toBN(transferValue))),
'User balance should be increased only by second transfer'
)
}
})
const afterMigrateAndTransferBalance = await homeWeb3.eth.getBalance(user.address)
// send tokens to foreign bridge
await halfDuplexToken.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await promiseRetry(async (retry, number) => {
const balance = await homeWeb3.eth.getBalance(user.address)
// retry at least 4 times to check transfer is not double processed by the two watchers
if (toBN(balance).lte(toBN(afterMigrateAndTransferBalance)) || number < 4) {
retry()
} else {
assert(
toBN(balance).eq(toBN(afterMigrateAndTransferBalance).add(toBN(transferValue))),
'User balance should be increased only by second transfer'
)
}
})
})
it('should convert tokens in foreign to coins in home', async () => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
const initialBalanceSecondUser = await homeWeb3.eth.getBalance(secondUser.address)
assert(!toBN(balance).isZero(), 'Account should have tokens')
// approve tokens to foreign bridge
await erc20Token.methods
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, homeWeb3.utils.toWei('0.01'))
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// call bridge method to transfer tokens to a different recipient
await foreignBridge.methods
.relayTokens(secondUser.address, homeWeb3.utils.toWei('0.01'))
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await uniformRetry(async retry => {
const balance = await homeWeb3.eth.getBalance(user.address)
const secondUserbalance = await homeWeb3.eth.getBalance(secondUser.address)
assert(toBN(balance).lte(toBN(originalBalanceOnHome)), 'User balance should be the same')
if (toBN(secondUserbalance).lte(toBN(initialBalanceSecondUser))) {
retry()
}
})
const transferValue = homeWeb3.utils.toWei('0.05')
// send tokens to foreign bridge
await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await uniformRetry(async retry => {
const balance = await homeWeb3.eth.getBalance(user.address)
if (toBN(balance).lte(toBN(originalBalanceOnHome))) {
retry()
} else {
assert(
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(transferValue))),
'User balance should be increased only by second transfer'
)
}
})
})
it('should convert half duplex token in foreign to native token in home', async () => {
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
// this transfer won't trigger a call to swap tokens
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
// check that balance increases
await uniformRetry(async retry => {
const balance = await homeWeb3.eth.getBalance(user.address)
if (toBN(balance).lte(toBN(originalBalanceOnHome))) {
retry()
} else {
assert(
toBN(balance).eq(toBN(originalBalanceOnHome).add(toBN(valueToTransfer))),
'User balance should be increased by the half duplex token transfer'
)
}
})
const updatedBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
const updatedBridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(
toBN(updatedBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))),
'Bridge balance should reflect the transfer value'
)
// this transfer will trigger call to swap tokens
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
await sleep(2000)
await uniformRetry(async retry => {
const userBalance = await homeWeb3.eth.getBalance(user.address)
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
if (
toBN(userBalance).lte(toBN(updatedBalanceOnHome)) ||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
) {
retry()
} else {
assert(
toBN(userBalance).eq(toBN(updatedBalanceOnHome).add(toBN(valueToTransfer))),
'User balance should be increased by the half duplex token transfer'
)
const updatedBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(toBN(updatedBalance).isZero(), 'Half duplex bridge balance should be zero')
assert(
toBN(updatedBridgeErc20TokenBalance).eq(
toBN(bridgeErc20TokenBalance)
.add(toBN(bridgeHalfDuplexBalance))
.add(toBN(foreignWeb3.utils.toWei('2', 'ether')))
),
'Erc20 token balance should be correctly increased by the token swap'
)
}
})
})
it('should convert half duplex token in foreign to native token in home for alternative receiver ', async () => {
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
const initialBalanceSecondUser = await homeWeb3.eth.getBalance(secondUser.address)
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
// approve tokens to foreign bridge
await halfDuplexToken.methods
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// call bridge method to transfer tokens to a different recipient
await foreignBridge.methods['relayTokens(address,uint256,address)'](
secondUser.address,
valueToTransfer,
halfDuplexTokenAddress
)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
// check that balance increases
await uniformRetry(async retry => {
const secondUserbalance = await homeWeb3.eth.getBalance(secondUser.address)
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const userbalance = await homeWeb3.eth.getBalance(user.address)
assert(toBN(userbalance).lte(toBN(originalBalanceOnHome)), 'User balance should be the same')
if (
toBN(secondUserbalance).lte(toBN(initialBalanceSecondUser)) ||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
) {
retry()
} else {
assert(
toBN(secondUserbalance).eq(toBN(initialBalanceSecondUser).add(toBN(valueToTransfer))),
'User balance should be increased by the half duplex token transfer'
)
const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero')
assert(
toBN(updatedBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance).add(toBN(valueToTransfer))),
'Erc20 token balance should be correctly increased by the token swap'
)
}
})
})
it('should not relay half duplex token transfer after Emergency Shutdown', async () => {
const originalBalanceOnHome = await homeWeb3.eth.getBalance(user.address)
const bridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const bridgeHalfDuplexBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const block = await foreignWeb3.eth.getBlock('latest')
const saiTop = new foreignWeb3.eth.Contract(SAI_TOP, ercToNativeBridge.saiTop)
// Trigger Emergency Shutdown
await saiTop.methods
.setCaged(block.timestamp)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
console.error(e)
})
const valueToTransfer = foreignWeb3.utils.toWei('1', 'ether')
await sleep(2000)
// this transfer won't trigger a call to swap tokens
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
// check that transfer and swap are not processed in the next blocks.
await promiseRetry(async (retry, number) => {
const balanceOnHome = await homeWeb3.eth.getBalance(user.address)
const currentBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
const currentBridgeHalfDuplexBalance = await halfDuplexToken.methods
.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS)
.call()
assert(toBN(balanceOnHome).eq(toBN(originalBalanceOnHome)), 'User balance should be the same')
assert(
toBN(currentBridgeHalfDuplexBalance).eq(toBN(bridgeHalfDuplexBalance).add(toBN(valueToTransfer))),
'Half duplex balance should be the value of transfer'
)
assert(toBN(currentBridgeErc20TokenBalance).eq(toBN(bridgeErc20TokenBalance)), 'erc20 balance should not change')
// after several retries, the state is corrects
if (number < 4) {
retry()
}
})
// let's undo the Emergency Shutdown to check that the oracle is still working
await saiTop.methods.setCaged('0').send({
from: user.address,
gas: '1000000'
})
const newValueToTransfer = foreignWeb3.utils.toWei('2', 'ether')
await halfDuplexToken.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, newValueToTransfer).send({
from: user.address,
gas: '1000000'
})
await sleep(2000)
await uniformRetry(async retry => {
const userBalance = await homeWeb3.eth.getBalance(user.address)
const updatedBridgeErc20TokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
if (
toBN(userBalance).lte(toBN(originalBalanceOnHome)) ||
toBN(updatedBridgeErc20TokenBalance).lte(toBN(bridgeErc20TokenBalance))
) {
retry()
} else {
assert(
toBN(userBalance).eq(toBN(originalBalanceOnHome).add(toBN(newValueToTransfer))),
'User balance should be increased by the half duplex token transfer'
)
const updatedHDBalance = await halfDuplexToken.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(toBN(updatedHDBalance).isZero(), 'Half duplex bridge balance should be zero')
assert(
toBN(updatedBridgeErc20TokenBalance).eq(
toBN(bridgeErc20TokenBalance)
.add(toBN(valueToTransfer))
.add(toBN(newValueToTransfer))
),
'Erc20 token balance should be correctly increased by the token swap'
)
}
})
})
@@ -55,7 +449,7 @@ describe('erc to native', () => {
assert(!toBN(balance).isZero(), 'Account should have tokens')
// send transaction to home bridge
const depositTx = await homeWeb3.eth.sendTransaction({
await homeWeb3.eth.sendTransaction({
from: user.address,
to: COMMON_HOME_BRIDGE_ADDRESS,
gasPrice: '1',
@@ -63,34 +457,217 @@ describe('erc to native', () => {
value: homeWeb3.utils.toWei('0.01')
})
// Send a trivial transaction to generate a new block since the watcher
// is configured to wait 1 confirmation block
await generateNewBlock(homeWeb3, user.address)
// The bridge should create a new transaction with a CollectedSignatures
// event so we generate another trivial transaction
await promiseRetry(
async retry => {
const lastBlockNumber = await homeWeb3.eth.getBlockNumber()
if (lastBlockNumber >= depositTx.blockNumber + 2) {
await generateNewBlock(homeWeb3, user.address)
} else {
retry()
}
},
{
forever: true,
factor: 1,
minTimeout: 500
}
)
// check that balance increases
await promiseRetry(async retry => {
await uniformRetry(async retry => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
if (toBN(balance).lte(toBN(originalBalance))) {
retry()
}
})
})
it('should not invest dai when chai token is disabled', async () => {
const bridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
await foreignBridge.methods.setMinDaiTokenBalance(foreignWeb3.utils.toWei('2', 'ether')).send({
from: validator.address,
gas: '1000000'
}) // set min limit for automatic investment to 2*2 dai
const valueToTransfer = foreignWeb3.utils.toWei('5', 'ether')
// this transfer won't trigger a call to convert to chai
await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
await promiseRetry(async (retry, number) => {
if (number < 4) {
retry()
} else {
const updatedBridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(
toBN(bridgeDaiTokenBalance)
.add(toBN(valueToTransfer))
.eq(toBN(updatedBridgeDaiTokenBalance)),
'Dai tokens should not be when chai is disabled'
)
}
})
})
it('should invest dai after enough tokens are collected on bridge account', async () => {
await foreignBridge.methods.initializeChaiToken().send({
from: validator.address,
gas: '1000000'
}) // initialize chai token
await foreignBridge.methods.setMinDaiTokenBalance('0').send({
from: validator.address,
gas: '1000000'
}) // set investing limit to 0
await foreignBridge.methods.convertDaiToChai().send({
from: validator.address,
gas: '1000000'
}) // convert all existing dai tokens on bridge account to chai, in order to start from zero balance
await foreignBridge.methods.setMinDaiTokenBalance(foreignWeb3.utils.toWei('2', 'ether')).send({
from: validator.address,
gas: '1000000'
}) // set investing limit to 2 dai, automatically invest should happen after 4 dai
const valueToTransfer = foreignWeb3.utils.toWei('3', 'ether')
// this transfer won't trigger a call to convert to chai
await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
await promiseRetry(async (retry, number) => {
if (number < 4) {
retry()
} else {
const bridgeDaiTokenBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(
valueToTransfer === bridgeDaiTokenBalance,
'Dai tokens should not be invested automatically before twice limit is reached'
)
}
})
// this transfer will trigger call to convert to chai
await erc20Token.methods.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, valueToTransfer).send({
from: user.address,
gas: '1000000'
})
await promiseRetry(async retry => {
const updatedBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
if (toBN(updatedBalance).gte(toBN(valueToTransfer).add(toBN(valueToTransfer)))) {
retry()
} else {
const updatedBalance = await erc20Token.methods.balanceOf(COMMON_FOREIGN_BRIDGE_ADDRESS).call()
assert(
toBN(updatedBalance).eq(toBN(foreignWeb3.utils.toWei('2', 'ether'))),
'Dai bridge balance should be equal to limit'
)
}
})
})
describe('handling of chai swaps', async () => {
before(async () => {
// Next tests check validator nonces, this will force every validator to submit signature/affirmation
// Set 3 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 3,
options: {
from: validator.address,
gas: '4000000'
}
})
// Set 3 required signatures for foreign bridge
await setRequiredSignatures({
bridgeContract: foreignBridge,
web3: foreignWeb3,
requiredSignatures: 3,
options: {
from: validator.address,
gas: '4000000'
}
})
})
it('should not handle transfer event in paying interest', async () => {
await foreignBridge.methods.setInterestReceiver(user.address).send({
from: validator.address,
gas: '1000000'
})
const initialNonce = await homeWeb3.eth.getTransactionCount(validator.address)
await foreignBridge.methods.payInterest().send({
from: user.address,
gas: '1000000'
})
await promiseRetry(async (retry, number) => {
if (number < 6) {
retry()
} else {
const nonce = await homeWeb3.eth.getTransactionCount(validator.address)
assert(
nonce === initialNonce,
'Validator should not process transfer event originated during converting Chai => Dai'
)
}
})
})
it('should not handle chai withdrawal transfer event in executeSignatures as a regular transfer', async () => {
await foreignBridge.methods.setMinDaiTokenBalance('0').send({
from: validator.address,
gas: '1000000'
}) // set investing limit to 0
await foreignBridge.methods.convertDaiToChai().send({
from: validator.address,
gas: '1000000'
}) // convert all existing dai tokens on bridge account to chai, in order to start from zero balance
const initialNonce = await homeWeb3.eth.getTransactionCount(validator.address)
const originalBalance = await erc20Token.methods.balanceOf(user.address).call()
// send transaction to home bridge
await homeWeb3.eth.sendTransaction({
from: user.address,
to: COMMON_HOME_BRIDGE_ADDRESS,
gasPrice: '1',
gas: '1000000',
value: homeWeb3.utils.toWei('0.01')
})
// check that balance increases
await uniformRetry(async retry => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
if (toBN(balance).lte(toBN(originalBalance))) {
retry()
}
})
await promiseRetry(async (retry, number) => {
if (number < 6) {
retry()
} else {
const nonce = await homeWeb3.eth.getTransactionCount(validator.address)
assert(
nonce === initialNonce + 1,
'Validator should not process transfer event originated during converting Chai => Dai'
)
}
})
})
after(async () => {
// Set 2 required signatures for home bridge
await setRequiredSignatures({
bridgeContract: homeBridge,
web3: homeWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
// Set 2 required signatures for foreign bridge
await setRequiredSignatures({
bridgeContract: foreignBridge,
web3: foreignWeb3,
requiredSignatures: 2,
options: {
from: validator.address,
gas: '4000000'
}
})
})
})
})

View File

@@ -1 +1 @@
--timeout 30000
--timeout 120000

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