Compare commits

..

14 Commits

Author SHA1 Message Date
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
44 changed files with 1379 additions and 1641 deletions

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

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

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

@@ -32,5 +32,31 @@
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/docker-compose.yml"
- name: Slurp docker compose extended file
slurp:
src: "/home/poadocker/bridge/oracle/docker-compose-transfer.yml"
register: docker_compose_extended_slurp
- name: Parse docker compose file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_slurp['content'] | b64decode | from_yaml }}"
- name: Add the external network used to connect to Parity nodes in compose extended file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
- name: Add all Oracle containers to the network in compose extended file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_parsed | combine({'services': {item: {'networks': docker_compose_extended_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
with_items: "{{ docker_compose_extended_parsed.services }}"
- name: Expose Redis port to allow connecting from redis-cli in compose extended file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
- name: Write new docker-compose extended file
copy:
content: "{{ docker_compose_extended_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/docker-compose-transfer.yml"
- 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

@@ -18,6 +18,25 @@
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "{{ bridge_path }}/oracle/docker-compose.yml"
- name: Slurp docker compose extended file
slurp:
src: "{{ bridge_path }}/oracle/docker-compose-transfer.yml"
register: docker_compose_extended_slurp
- name: Parse docker compose extended file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_slurp['content'] | b64decode | from_yaml }}"
- name: Set logger to remote server for extended file
set_fact:
docker_compose_extended_parsed: "{{ docker_compose_extended_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
with_items: "{{ docker_compose_extended_parsed.services }}"
- name: Write new docker-compose-extended file
copy:
content: "{{ docker_compose_extended_parsed | to_yaml }}"
dest: "{{ bridge_path }}/oracle/docker-compose-transfer.yml"
- name: Set the local container logs configuration file
template:
src: 31-oracle-docker.conf.j2

View File

@@ -5,14 +5,16 @@
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 }}"
@@ -25,6 +27,21 @@
set_fact:
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
- name: Get foreign erc type
become_user: "{{ compose_service_user }}"
shell: docker-compose run --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_NATIVE" or ( ORACLE_BRIDGE_MODE == "ERC_TO_ERC" and FOREIGN_ERC_TYPE != "ERC677")
- name: Install .key config
template:
src: key.j2

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,30 @@ 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
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose $composefileoverride 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 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 --force-recreate --no-deps --build
}

View File

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

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

@@ -41,7 +41,8 @@
"home": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"homeBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1"
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"monitor": "http://monitor-amb:3013"
},
"homeRPC": {
"URL": "http://parity1:8545",

View File

@@ -132,6 +132,17 @@ services:
- "3012:3012"
networks:
- ultimate
monitor-amb:
build:
context: ..
dockerfile: monitor/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/monitor-amb.env
entrypoint: yarn check-and-start
ports:
- "3013:3013"
networks:
- ultimate
e2e:
build:
context: ..

View File

@@ -17,9 +17,11 @@ 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-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
@@ -44,7 +46,7 @@ while [ "$1" != "" ]; do
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

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

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,5 +1,5 @@
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, BOX_ABI } = require('../commons')
const waitUntil = async (predicate, step = 100, timeout = 10000) => {
const stopTime = Date.now() + timeout
@@ -37,6 +37,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)
@@ -53,5 +64,6 @@ module.exports = {
waitUntil,
sendEther,
sendTokens,
addValidator
addValidator,
sendAMBMessage
}

View File

@@ -7,6 +7,7 @@ check_files_exist() {
(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

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

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,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-compose exec monitor yarn check-all >>cronWorker.out 2>>cronWorker.err

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

@@ -77,10 +77,10 @@ app.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
(results.onlyInHomeDeposits || results.home.deliveredMsgNotProcessedInForeign).length === 0 &&
(results.onlyInForeignDeposits || results.home.processedMsgNotDeliveredInForeign).length === 0 &&
(results.onlyInHomeWithdrawals || results.foreign.deliveredMsgNotProcessedInHome).length === 0 &&
(results.onlyInForeignWithdrawals || results.foreign.processedMsgNotDeliveredInHome).length === 0
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware

View File

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

@@ -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,10 @@ const {
ERC20_ABI,
ERC677_BRIDGE_TOKEN_ABI,
getTokenType,
getPastEvents
getPastEvents,
ZERO_ADDRESS
} = require('../../commons')
const { normalizeEventInformation } = require('./message')
const {
COMMON_HOME_RPC_URL,
@@ -40,6 +42,7 @@ async function main(mode) {
const v1Bridge = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1
let isExternalErc20
let erc20Contract
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()
@@ -49,47 +52,98 @@ async function main(mode) {
)
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 uniqueTokenAddresses = [...new Set(bridgeTokensSwappedEvents.map(e => e.returnValues.from))]
await Promise.all(
uniqueTokenAddresses.map(async tokenAddress => {
const previousERC20 = new web3Foreign.eth.Contract(ERC20_ABI, tokenAddress)
const previousTransferEvents = (await getPastEvents(previousERC20, {
event: 'Transfer',
fromBlock: MONITOR_FOREIGN_START_BLOCK,
toBlock: foreignBlockNumber,
options: {
filter: { to: COMMON_FOREIGN_BRIDGE_ADDRESS }
}
})).map(normalizeEvent)
transferEvents = [...previousTransferEvents, ...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,

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

@@ -1,8 +1,8 @@
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 { user, secondUser, ercToErcBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons')
const { generateNewBlock } = require('../../e2e-commons/utils')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
@@ -17,6 +17,7 @@ homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.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)
describe('erc to erc', () => {
@@ -24,9 +25,49 @@ describe('erc to erc', () => {
const balance = await erc20Token.methods.balanceOf(user.address).call()
assert(!toBN(balance).isZero(), 'Account should have tokens')
const firstTransferValue = homeWeb3.utils.toWei('0.01')
// approve tokens to foreign bridge
await erc20Token.methods
.approve(COMMON_FOREIGN_BRIDGE_ADDRESS, firstTransferValue)
.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, firstTransferValue)
.send({
from: user.address,
gas: '1000000'
})
.catch(e => {
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 => {
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, homeWeb3.utils.toWei('0.01'))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, secondTransferValue)
.send({
from: user.address,
gas: '1000000'
@@ -44,6 +85,8 @@ describe('erc to erc', () => {
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')
}
})
})

View File

@@ -1,8 +1,8 @@
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 { user, secondUser, ercToNativeBridge, homeRPC, foreignRPC } = require('../../e2e-commons/constants.json')
const { ERC677_BRIDGE_TOKEN_ABI, FOREIGN_ERC_TO_NATIVE_ABI } = require('../../commons')
const { generateNewBlock } = require('../../e2e-commons/utils')
const homeWeb3 = new Web3(new Web3.providers.HttpProvider(homeRPC.URL))
@@ -17,16 +17,56 @@ homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.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)
describe('erc to native', () => {
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)
})
// 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 => {
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, homeWeb3.utils.toWei('0.01'))
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)
.send({
from: user.address,
gas: '1000000'
@@ -44,6 +84,11 @@ describe('erc to native', () => {
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'
)
}
})
})

View File

@@ -53,7 +53,7 @@ For more information on the Redis/RabbitMQ requirements, see [#90](/../../issues
**Note:** The following steps detail the bridge deployment process for development and testing. For deployment in a production environment we recommend using the [Bridge Deployment Playbooks](../deployment/README.md).
1. [Deploy the bridge contracts](https://github.com/poanetwork/poa-bridge-contracts/blob/master/deploy/README.md)
1. [Deploy the bridge contracts](https://github.com/poanetwork/tokenbridge-contracts/blob/master/deploy/README.md)
2. Open `bridgeDeploymentResults.json` or copy the JSON output generated by the bridge contract deployment process.
@@ -232,10 +232,10 @@ When running the processes, the following commands can be used to test functiona
| `USER_ADDRESS` | An account - the current owner of coins/tokens. |
| `USER_ADDRESS_PRIVATE_KEY` | A private key belonging to the account. |
| `COMMON_HOME_BRIDGE_ADDRESS` | Address of the bridge in the Home network to send transactions. |
| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network. This should be greater than or equal to the value specified in the `poa-bridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
| `HOME_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Home network. This should be greater than or equal to the value specified in the `tokenbridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
| `HOME_TEST_TX_GAS_PRICE` | The gas price (in Wei) that is used to send transactions in the Home network . |
| `COMMON_FOREIGN_BRIDGE_ADDRESS` | Address of the bridge in the Foreign network to send transactions. |
| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network. This should be greater than or equal to the value specified in the `poa-bridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
| `FOREIGN_MIN_AMOUNT_PER_TX` | Value (in _eth_ or tokens) to be sent in one transaction for the Foreign network. This should be greater than or equal to the value specified in the `tokenbridge-contracts/deploy/.env` file. The default value in that file is 500000000000000000, which is equivalent to 0.5. |
| `FOREIGN_TEST_TX_GAS_PRICE` | The gas price (in Wei) that is used to send transactions in the Foreign network . |
## Contributing

View File

@@ -1,5 +1,5 @@
const baseConfig = require('./base.config')
const { ERC20_ABI, ERC_TYPES } = require('../../commons')
const { ERC_TYPES } = require('../../commons')
const initialChecksJson = process.argv[3]
@@ -20,24 +20,11 @@ if (baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC677
const id = `${baseConfig.id}-affirmation-request`
module.exports =
(baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC20) || baseConfig.id === 'erc-native'
? {
...baseConfig.bridgeConfig,
...baseConfig.foreignConfig,
event: 'Transfer',
eventContractAddress: initialChecks.bridgeableTokenAddress,
eventAbi: ERC20_ABI,
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
queue: 'home',
name: `watcher-${id}`,
id
}
: {
...baseConfig.bridgeConfig,
...baseConfig.foreignConfig,
event: 'UserRequestForAffirmation',
queue: 'home',
name: `watcher-${id}`,
id
}
module.exports = {
...baseConfig.bridgeConfig,
...baseConfig.foreignConfig,
event: 'UserRequestForAffirmation',
queue: 'home',
name: `watcher-${id}`,
id
}

View File

@@ -0,0 +1,42 @@
const baseConfig = require('./base.config')
const { ERC20_ABI, ERC_TYPES } = require('../../commons')
const { EXIT_CODES } = require('../src/utils/constants')
const initialChecksJson = process.argv[3]
if (!initialChecksJson) {
throw new Error('initial check parameter was not provided.')
}
let initialChecks
try {
initialChecks = JSON.parse(initialChecksJson)
} catch (e) {
throw new Error('Error on decoding values from initial checks.')
}
if (baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC677) {
baseConfig.id = 'erc677-erc677'
}
const id = `${baseConfig.id}-transfer`
const transferWatcherRequired =
(baseConfig.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC20) || baseConfig.id === 'erc-native'
if (!transferWatcherRequired) {
console.error(`Transfer watcher not required for bridge mode ${process.env.ORACLE_BRIDGE_MODE}`)
process.exit(EXIT_CODES.WATCHER_NOT_REQUIRED)
}
module.exports = {
...baseConfig.bridgeConfig,
...baseConfig.foreignConfig,
event: 'Transfer',
eventContractAddress: initialChecks.bridgeableTokenAddress,
eventAbi: ERC20_ABI,
eventFilter: { to: process.env.COMMON_FOREIGN_BRIDGE_ADDRESS },
queue: 'home',
name: `watcher-${id}`,
id
}

View File

@@ -0,0 +1,91 @@
---
version: '2.4'
services:
rabbit:
extends:
file: docker-compose.yml
service: rabbit
networks:
- net_rabbit_bridge_transfer
redis:
extends:
file: docker-compose.yml
service: redis
networks:
- net_db_bridge_transfer
bridge_request:
extends:
file: docker-compose.yml
service: bridge_request
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
bridge_collected:
extends:
file: docker-compose.yml
service: bridge_collected
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
bridge_affirmation:
extends:
file: docker-compose.yml
service: bridge_affirmation
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
bridge_transfer:
cpus: 0.1
mem_limit: 500m
build:
context: ..
dockerfile: oracle/Dockerfile
env_file: ./.env
environment:
- NODE_ENV=production
- ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}
restart: unless-stopped
entrypoint: yarn watcher:transfer
networks:
- net_db_bridge_transfer
- net_rabbit_bridge_transfer
bridge_senderhome:
extends:
file: docker-compose.yml
service: bridge_senderhome
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
bridge_senderforeign:
extends:
file: docker-compose.yml
service: bridge_senderforeign
networks:
- net_db_bridge_request
- net_rabbit_bridge_request
networks:
net_db_bridge_request:
driver: bridge
net_db_bridge_collected:
driver: bridge
net_db_bridge_affirmation:
driver: bridge
net_db_bridge_transfer:
driver: bridge
net_db_bridge_senderhome:
driver: bridge
net_db_bridge_senderforeign:
driver: bridge
net_rabbit_bridge_request:
driver: bridge
net_rabbit_bridge_collected:
driver: bridge
net_rabbit_bridge_affirmation:
driver: bridge
net_rabbit_bridge_transfer:
driver: bridge
net_rabbit_bridge_senderhome:
driver: bridge
net_rabbit_bridge_senderforeign:
driver: bridge

View File

@@ -8,9 +8,10 @@
"watcher:signature-request": "./scripts/start-worker.sh watcher signature-request-watcher",
"watcher:collected-signatures": "./scripts/start-worker.sh watcher collected-signatures-watcher",
"watcher:affirmation-request": "./scripts/start-worker.sh watcher affirmation-request-watcher",
"watcher:transfer": "./scripts/start-worker.sh watcher transfer-watcher",
"sender:home": "./scripts/start-worker.sh sender home-sender",
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn sender:home' 'yarn sender:foreign'",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer,sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'",
"test": "NODE_ENV=test mocha",
"test:watch": "NODE_ENV=test mocha --watch --reporter=min",
"coverage": "NODE_ENV=test nyc --reporter=text --reporter=html mocha",

View File

@@ -1,9 +1,9 @@
require('../../../env')
const promiseLimit = require('promise-limit')
const { HttpListProviderError } = require('http-list-provider')
const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons')
const { BRIDGE_VALIDATORS_ABI, ZERO_ADDRESS } = require('../../../../commons')
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const { web3Home, web3Foreign } = require('../../services/web3')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('../processAffirmationRequests/estimateGas')
@@ -14,6 +14,12 @@ let validatorContract = null
function processTransfersBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
const userRequestForAffirmationAbi = config.foreignBridgeAbi.filter(
e => e.type === 'event' && e.name === 'UserRequestForAffirmation'
)[0]
const tokensSwappedAbi = config.foreignBridgeAbi.filter(e => e.type === 'event' && e.name === 'TokensSwapped')[0]
const userRequestForAffirmationHash = web3Home.eth.abi.encodeEventSignature(userRequestForAffirmationAbi)
const tokensSwappedHash = tokensSwappedAbi ? web3Home.eth.abi.encodeEventSignature(tokensSwappedAbi) : '0x'
return async function processTransfers(transfers) {
const txToSend = []
@@ -37,6 +43,32 @@ function processTransfersBuilder(config) {
logger.info({ from, value }, `Processing transfer ${transfer.transactionHash}`)
const receipt = await web3Foreign.eth.getTransactionReceipt(transfer.transactionHash)
const existsAffirmationEvent = receipt.logs.some(
e => e.address === config.foreignBridgeAddress && e.topics[0] === userRequestForAffirmationHash
)
if (existsAffirmationEvent) {
logger.info(
`Transfer event discarded because a transaction with alternative receiver detected in transaction ${
transfer.transactionHash
}`
)
return
}
const existsTokensSwappedEvent = tokensSwappedAbi
? receipt.logs.some(e => e.address === config.foreignBridgeAddress && e.topics[0] === tokensSwappedHash)
: false
if (from === ZERO_ADDRESS && existsTokensSwappedEvent) {
logger.info(
`Transfer event discarded because token swap is detected in transaction ${transfer.transactionHash}`
)
return
}
let gasEstimate
try {
logger.debug('Estimate gas')

View File

@@ -11,6 +11,7 @@ module.exports = {
DEFAULT_GAS_PRICE_FACTOR: 1,
EXIT_CODES: {
GENERAL_ERROR: 1,
WATCHER_NOT_REQUIRED: 0,
INCOMPATIBILITY: 10,
MAX_TIME_REACHED: 11
},

View File

@@ -96,9 +96,11 @@ function processEvents(events) {
return processCollectedSignatures(events)
case 'native-erc-affirmation-request':
case 'erc677-erc677-affirmation-request':
return processAffirmationRequests(events)
case 'erc-erc-affirmation-request':
case 'erc-native-affirmation-request':
case 'erc-erc-affirmation-request':
return processAffirmationRequests(events)
case 'erc-erc-transfer':
case 'erc-native-transfer':
return processTransfers(events)
case 'amb-signature-request':
return processAMBSignatureRequests(events)

View File

@@ -36,7 +36,7 @@
"initialize": "yarn clean && git submodule update --init && yarn install --unsafe-perm --frozen-lockfile && yarn install:deploy && yarn compile:contracts",
"build": "yarn workspace ui run build",
"lint": "yarn wsrun --exclude token-bridge-contracts lint",
"test": "yarn wsrun --exclude monitor --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",
"test": "yarn wsrun --exclude oracle-e2e --exclude ui-e2e --exclude monitor-e2e test",
"oracle-e2e": "./oracle-e2e/run-tests.sh",
"ui-e2e": "./ui-e2e/run-tests.sh",
"clean": "rm -rf ./node_modules ./**/node_modules ./**/**/node_modules ./**/build",

View File

@@ -4,9 +4,9 @@
"license": "MIT",
"private": true,
"devDependencies": {
"selenium-webdriver": "3.6.0",
"chromedriver": "^77.0.0",
"mocha": "^5.2.0",
"chromedriver": "76.0.0"
"selenium-webdriver": "3.6.0"
},
"scripts": {
"lint": "eslint . --ignore-path ../.eslintignore"

View File

@@ -52,7 +52,7 @@ The following is an example setup using the POA Sokol testnet as the Home networ
### Dependencies
- [Smart Contracts](https://github.com/poanetwork/poa-bridge-contracts)
- [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts)
- [Oracle](../oracle/README.md)
- [Node.js](https://nodejs.org/en/download/)
- [AlphaWallet](https://alphawallet.com/) or [Nifty Wallet](https://github.com/poanetwork/nifty-wallet) or [MetaMask](https://metamask.io/)
@@ -72,8 +72,8 @@ The following is an example setup using the POA Sokol testnet as the Home networ
* Get free Kovan Coins from the [gitter channel](https://gitter.im/kovan-testnet/faucet) or [Iracus faucet](https://github.com/kovan-testnet/faucet) for Foreign account(s). Get 5 Keth to 1 acc, and transfer from there to all other wallets if more than one account is used.
4. Deploy the Sokol <-> Kovan Bridge contracts.
* Go to the the `sokol-kovan-bridge` folder created in step 1 and `git clone https://github.com/poanetwork/poa-bridge-contracts`
* Follow instructions in the [POA Bridge contracts repo](https://github.com/poanetwork/poa-bridge-contracts).
* Go to the the `sokol-kovan-bridge` folder created in step 1 and `git clone https://github.com/poanetwork/tokenbridge-contracts`
* Follow instructions in the [POA Bridge contracts repo](https://github.com/poanetwork/tokenbridge-contracts).
* Set the parameters in the .env file.
* `DEPLOYMENT_ACCOUNT_PRIVATE_KEY`: Export the private key from step 2
* `HOME_RPC_URL`=https://sokol.poa.network
@@ -86,7 +86,7 @@ The following is an example setup using the POA Sokol testnet as the Home networ
* `FOREIGN_UPGRADEABLE_ADMIN_BRIDGE`
* `VALIDATORS` _Note: Wallet address(es) for validator(s) are separated by a space. For testing, you can use the same address that was used as the bridge contracts management account._
* `FOREIGN_RPC_URL`=https://kovan.infura.io/mew
* When deployment is finished, check that the `bridgeDeploymentResults.json` file exists in the `poa-bridge-contracts/deploy` directory and includes the bridge contract addresses.
* When deployment is finished, check that the `bridgeDeploymentResults.json` file exists in the `tokenbridge-contracts/deploy` directory and includes the bridge contract addresses.
5. Install and run the TokenBridge Oracle.
* Go to the `sokol-kovan-bridge` folder
@@ -125,7 +125,7 @@ cp .env.example .env
````
* Insert the addresses from the bridgeDeploymentResults.json file (from step 4) into the .env file. No other changes are needed, see [Env Parameter Details](#env-parameter-details) for information about each parameter.
```
cat ../poa-bridge-contracts/deploy/bridgeDeploymentResults.json
cat ../tokenbridge-contracts/deploy/bridgeDeploymentResults.json
```
```bash

View File

@@ -60,7 +60,7 @@ class GasPriceStore {
@computed
get gasPriceInHex() {
return toHex(this.gasPrice)
return toHex(this.gasPrice.toString())
}
}

View File

@@ -64,7 +64,7 @@ class TxStore {
try {
return this.web3Store.getWeb3Promise.then(async () => {
if (this.web3Store.defaultAccount.address) {
const data = await contract.methods.transferAndCall(to, value, '0x00').encodeABI()
const data = await contract.methods.transferAndCall(to, value, '0x').encodeABI()
return this.doSend({ to: tokenAddress, from, value: '0x00', data, sentValue: value })
} else {
this.alertStore.pushError('Please unlock wallet')

1935
yarn.lock

File diff suppressed because it is too large Load Diff