Merge branch 'master' into amb-oracle

# Conflicts:
#	.prettierrc
#	commons/abis.js
#	commons/package.json
#	e2e-commons/docker-compose.yml
#	monitor/eventsStats.js
#	monitor/utils/events.js
#	oracle/src/services/gasPrice.js
#	oracle/test/gasPrice.test.js
#	yarn.lock
This commit is contained in:
Gerardo Nardelli 2019-08-22 12:10:49 -03:00
commit d22a3e8bae
150 changed files with 4446 additions and 2387 deletions

@ -1,8 +1,84 @@
version: 2
version: 2.1
orbs:
tokenbridge-orb:
commands:
install-chrome:
steps:
- run:
name: Install Chrome
command: |
wget -O chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i chrome.deb
install-node:
steps:
- run:
name: Install Node
command: |
export NVM_DIR="/opt/circleci/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 11.4.0 && nvm alias default 11.4.0
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
install-yarn:
steps:
- run:
name: Install Yarn
command: |
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get -y install yarn
yarn-install-cached-on-machine:
steps:
- restore_cache:
name: Restore Machine Yarn Package Cache
keys:
- yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- run:
name: Install npm dependencies using Yarn
command: nvm use default; yarn install --frozen-lockfile
- save_cache:
name: Save Machine Yarn Package Cache
key: yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
wait-for-oracle:
parameters:
redis-key:
type: string
steps:
- run:
name: Install redis tools
command: sudo apt-get install -y redis-tools
- run:
name: Wait for the Oracle to start
command: |
set +e
i=0
while [[ $(redis-cli GET << parameters.redis-key >> ) ]]; do
((i++))
if [ "$i" -gt 30 ]
then
exit -1
fi
echo "Sleeping..."
sleep 3
done
executors:
docker-node:
docker:
- image: circleci/node:10.15
machine-with-docker-caching:
machine:
image: circleci/classic:latest
docker_layer_caching: true
jobs:
initialize:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- checkout
- run: git submodule update --init
@ -36,35 +112,30 @@ jobs:
paths:
- ~/project
initialize-root:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- checkout
- run: sudo su - -c 'export CI=true && cd /home/circleci/project && yarn initialize && yarn test'
build:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- restore_cache:
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn run build
lint:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- restore_cache:
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn run lint
test:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- restore_cache:
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn run test
oracle-e2e:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- checkout
- run: git submodule update --init
@ -72,75 +143,75 @@ jobs:
docker_layer_caching: true
- run: yarn run oracle-e2e
ui-e2e:
machine:
image: circleci/classic:latest
docker_layer_caching: true
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: |
echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
- run: nvm install 11.4.0 && nvm alias default 11.4.0
- run: curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
- run: echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
- run: sudo apt-get update && sudo apt-get install yarn
- run: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- run: sudo dpkg -i google-chrome-stable_current_amd64.deb
- tokenbridge-orb/install-node
- tokenbridge-orb/install-yarn
- tokenbridge-orb/install-chrome
- run: git submodule update --init
- restore_cache:
name: Restore Machine Yarn Package Cache
keys:
- yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- run: yarn install --frozen-lockfile
- save_cache:
name: Save Machine Yarn Package Cache
key: yarn-machine-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- tokenbridge-orb/yarn-install-cached-on-machine
- run: yarn run ui-e2e
monitor-e2e:
machine:
image: circleci/classic:latest
docker_layer_caching: true
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: git submodule update --init
- run: ./monitor-e2e/run-tests.sh
cover:
docker:
- image: circleci/node:10.15
executor: tokenbridge-orb/docker-node
steps:
- restore_cache:
key: initialize-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn workspace ui run coverage
- run: yarn workspace ui run coveralls
deployment-oracle:
machine:
image: circleci/classic:latest
docker_layer_caching: true
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: deployment/molecule/molecule.sh oracle
deployment-ui:
machine:
image: circleci/classic:latest
docker_layer_caching: true
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: deployment/molecule/molecule.sh ui
ultimate-native-to-erc:
machine:
image: circleci/classic:latest
docker_layer_caching: true
deployment-monitor:
executor: tokenbridge-orb/machine-with-docker-caching
steps:
- checkout
- run: deployment/molecule/molecule.sh monitor
ultimate:
executor: tokenbridge-orb/machine-with-docker-caching
parameters:
scenario-name:
description: "Molecule scenario name used to create the infrastructure"
type: string
redis-key:
description: "Redis key checked for non-emptiness to assert if Oracle is running"
type: string
ui-e2e-grep:
description: "Mocha grep string used to run ui-e2e tests specific to given type of bridge"
type: string
steps:
- checkout
- run: git submodule update --init
- tokenbridge-orb/install-node
- tokenbridge-orb/install-chrome
- tokenbridge-orb/install-yarn
- tokenbridge-orb/yarn-install-cached-on-machine
- run:
name: Prepare the infrastructure
command: e2e-commons/up.sh deploy native-to-erc
- run: 'echo "TODO - Run the e2e tests on top of the infrastructure created by previous step"'
command: e2e-commons/up.sh deploy << parameters.scenario-name >>
no_output_timeout: 30m
- 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
workflows:
version: 2
tokenbridge:
jobs:
- initialize
@ -168,4 +239,19 @@ workflows:
- monitor-e2e
- deployment-oracle
- deployment-ui
- ultimate-native-to-erc
- deployment-monitor
- ultimate:
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"
scenario-name: erc-to-native
redis-key: erc-native-collected-signatures:lastProcessedBlock
ui-e2e-grep: "ERC TO NATIVE"
- ultimate:
name: "ultimate: erc to erc"
scenario-name: erc-to-erc
redis-key: erc-erc-collected-signatures:lastProcessedBlock
ui-e2e-grep: "ERC TO ERC"

@ -1,6 +1,6 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"printWidth": 120,
"bracketSpacing": true
}

@ -3,15 +3,15 @@
[![License: LGPL v3.0](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
# Tokenbridge
Welcome to the **POA Token Bridge** monorepository!
Welcome to the **POA TokenBridge** monorepository!
Please note that this repository as a **work in progress**.
## Overview
The POA Token Bridge allows users to transfer assets between two chains in the Ethereum ecosystem. It is composed of several elements which are contained within this monorepository.
The POA TokenBridge allows users to transfer assets between two chains in the Ethereum ecosystem. It is composed of several elements which are contained within this monorepository.
For a complete picture of the POA Token Bridge functionality, it is useful to explore each subrepository.
For a complete picture of the POA TokenBridge functionality, it is useful to explore each subrepository.
## Structure
@ -35,9 +35,9 @@ Additionally there are [Smart Contracts](https://github.com/poanetwork/poa-bridg
| **Launched by POA** | **Launched by 3rd parties** |
| ---------- | ---------- |
| [POA20 Bridge](https://bridge.poa.net/) | [Ocean Token Bridge](https://bridge.oceanprotocol.com/) |
| [POA20 Bridge](https://bridge.poa.net/) | [Ocean TokenBridge](https://bridge.oceanprotocol.com/) |
| [xDai Bridge](https://dai-bridge.poa.network/) | [Thunder bridge](https://ui.stormdapps.com/) |
| [WETC Bridge](https://wetc.app/) | [Volta Token Bridge](https://vt.volta.bridge.eth.events/) & [DAI bridge to Volta Chain](https://dai.volta.bridge.eth.events/) |
| [WETC Bridge](https://wetc.app/) | [Volta TokenBridge](https://vt.volta.bridge.eth.events/) & [DAI bridge to Volta Chain](https://dai.volta.bridge.eth.events/) |
| | [Artis Brige](https://bridge.artis.network/) |
| | [Tenda bridge](https://bridge-mainnet.tenda.network) & [xDai-to-Tenda bridge](https://bridge-xdai.tenda.network/) |

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

@ -1,2 +1,2 @@
# POA Token Bridge / Commons
# POA TokenBridge / Commons
Interfaces, constants and utilities shared between the sub-repositories

@ -1,12 +1,9 @@
const HOME_NATIVE_TO_ERC_ABI = require('../contracts/build/contracts/HomeBridgeNativeToErc').abi
const FOREIGN_NATIVE_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeNativeToErc')
.abi
const FOREIGN_NATIVE_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeNativeToErc').abi
const HOME_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeBridgeErcToErc').abi
const FOREIGN_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeErc677ToErc677')
.abi
const FOREIGN_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeErc677ToErc677').abi
const HOME_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/HomeBridgeErcToNative').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative')
.abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative').abi
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
const ERC677_ABI = require('../contracts/build/contracts/ERC677').abi
const ERC677_BRIDGE_TOKEN_ABI = require('../contracts/build/contracts/ERC677BridgeToken').abi
@ -17,9 +14,40 @@ 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 { homeV1Abi, foreignViAbi } = require('./v1Abis')
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
const { BRIDGE_MODES } = require('./constants')
const ERC20_BYTES32_ABI = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'bytes32'
}
],
payable: false,
stateMutability: 'view',
type: 'function'
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'bytes32'
}
],
payable: false,
stateMutability: 'view',
type: 'function'
}
]
function getBridgeABIs(bridgeMode) {
let HOME_ABI = null
let FOREIGN_ABI = null
@ -33,8 +61,8 @@ function getBridgeABIs(bridgeMode) {
HOME_ABI = HOME_ERC_TO_NATIVE_ABI
FOREIGN_ABI = FOREIGN_ERC_TO_NATIVE_ABI
} else if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
HOME_ABI = homeV1Abi
FOREIGN_ABI = foreignViAbi
HOME_ABI = HOME_V1_ABI
FOREIGN_ABI = FOREIGN_V1_ABI
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
HOME_ABI = HOME_AMB_ABI
FOREIGN_ABI = FOREIGN_AMB_ABI
@ -59,6 +87,9 @@ module.exports = {
BLOCK_REWARD_ABI,
BRIDGE_VALIDATORS_ABI,
REWARDABLE_VALIDATORS_ABI,
HOME_V1_ABI,
FOREIGN_V1_ABI,
ERC20_BYTES32_ABI,
HOME_AMB_ABI,
FOREIGN_AMB_ABI,
BOX_ABI

162
commons/test/gas.js Normal file

@ -0,0 +1,162 @@
const { expect } = require('chai')
const Web3Utils = require('web3-utils')
const { gasPriceWithinLimits, normalizeGasPrice } = require('..')
const GAS_PRICE_BOUNDARIES = {
MIN: 1,
MAX: 250
}
describe('gas', () => {
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 30
const factor = 1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 300
const factor = 0.1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should increase gas price value from oracle', () => {
// Given
const oracleGasPrice = 20
const factor = 1.5
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
})
describe('gasPriceWithinLimits', () => {
it('should return gas price if gas price is between boundaries', () => {
// given
const minGasPrice = 1
const middleGasPrice = 10
const maxGasPrice = 250
// when
const minGasPriceWithinLimits = gasPriceWithinLimits(minGasPrice, GAS_PRICE_BOUNDARIES)
const middleGasPriceWithinLimits = gasPriceWithinLimits(middleGasPrice, GAS_PRICE_BOUNDARIES)
const maxGasPriceWithinLimits = gasPriceWithinLimits(maxGasPrice, GAS_PRICE_BOUNDARIES)
// then
expect(minGasPriceWithinLimits).to.equal(minGasPrice)
expect(middleGasPriceWithinLimits).to.equal(middleGasPrice)
expect(maxGasPriceWithinLimits).to.equal(maxGasPrice)
})
it('should return min limit if gas price is below min boundary', () => {
// Given
const initialGasPrice = 0.5
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice, GAS_PRICE_BOUNDARIES)
// Then
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MIN)
})
it('should return max limit if gas price is above max boundary', () => {
// Given
const initialGasPrice = 260
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice, GAS_PRICE_BOUNDARIES)
// Then
expect(gasPrice).to.equal(GAS_PRICE_BOUNDARIES.MAX)
})
it('should return gas price if boundaries not provided', () => {
// Given
const initialGasPrice = 260
// When
const gasPrice = gasPriceWithinLimits(initialGasPrice)
// Then
expect(gasPrice).to.equal(initialGasPrice)
})
})
describe('normalizeGasPrice', () => {
it('should work with oracle gas price in gwei', () => {
// Given
const oracleGasPrice = 20
const factor = 1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('20000000000')
})
it('should work with oracle gas price not in gwei', () => {
// Given
const oracleGasPrice = 200
const factor = 0.1
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('20000000000')
})
it('should increase gas price value from oracle', () => {
// Given
const oracleGasPrice = 20
const factor = 1.5
// When
const result = normalizeGasPrice(oracleGasPrice, factor).toString()
// Then
expect(result).to.equal('30000000000')
})
it('should respect gas price max limit', () => {
// Given
const oracleGasPrice = 200
const factor = 4
const maxInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MAX.toString(), 'gwei')
// When
const result = normalizeGasPrice(oracleGasPrice, factor, GAS_PRICE_BOUNDARIES).toString()
// Then
expect(result).to.equal(maxInWei)
})
it('should respect gas price min limit', () => {
// Given
const oracleGasPrice = 1
const factor = 0.01
const minInWei = Web3Utils.toWei(GAS_PRICE_BOUNDARIES.MIN.toString(), 'gwei')
// When
const result = normalizeGasPrice(oracleGasPrice, factor, GAS_PRICE_BOUNDARIES).toString()
// Then
expect(result).to.equal(minInWei)
})
})
})

@ -0,0 +1,64 @@
const { expect } = require('chai')
const { getTokenType, ERC_TYPES } = require('..')
describe('getTokenType', () => {
it('should return ERC677 if bridgeContract is equal to bridgeAddress', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.resolve(bridgeAddress)
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC677)
})
it('should return ERC20 if bridgeContract is not equal to bridgeAddress', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.resolve('0xBFCb120F7B1de491262CA4D9D8Eba70438b6896E')
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC20)
})
it('should return ERC20 if bridgeContract is not present', async () => {
// Given
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
const contract = {
methods: {
bridgeContract: () => {
return {
call: () => Promise.reject()
}
}
}
}
// When
const type = await getTokenType(contract, bridgeAddress)
// Then
expect(type).to.equal(ERC_TYPES.ERC20)
})
})

@ -1,4 +1,5 @@
import { processValidatorsEvents, parseValidatorEvent } from '../contract'
const { expect } = require('chai')
const { processValidatorsEvents, parseValidatorEvent } = require('..')
describe('parseValidatorEvent', () => {
it('should parse ValidatorAdded event from v1', () => {
@ -14,8 +15,8 @@ describe('parseValidatorEvent', () => {
parseValidatorEvent(event)
// Then
expect(event.event).toBe('ValidatorAdded')
expect(event.returnValues.validator).toBe('0xcfef0c6bb765321529ffe81507f6d099693cd225')
expect(event.event).to.be.equal('ValidatorAdded')
expect(event.returnValues.validator).to.be.equal('0xcfef0c6bb765321529ffe81507f6d099693cd225')
})
it('should parse ValidatorAdded event', () => {
// Given
@ -33,8 +34,8 @@ describe('parseValidatorEvent', () => {
parseValidatorEvent(event)
// Then
expect(event.event).toBe('ValidatorAdded')
expect(event.returnValues.validator).toBe('0xcfef0c6bb765321529ffe81507f6d099693cd225')
expect(event.event).to.be.equal('ValidatorAdded')
expect(event.returnValues.validator).to.be.equal('0xcfef0c6bb765321529ffe81507f6d099693cd225')
})
it('should parse ValidatorAdded event from rewardableValidators', () => {
// Given
@ -52,8 +53,8 @@ describe('parseValidatorEvent', () => {
parseValidatorEvent(event)
// Then
expect(event.event).toBe('ValidatorAdded')
expect(event.returnValues.validator).toBe('0xcfef0c6bb765321529ffe81507f6d099693cd225')
expect(event.event).to.be.equal('ValidatorAdded')
expect(event.returnValues.validator).to.be.equal('0xcfef0c6bb765321529ffe81507f6d099693cd225')
})
it('should parse ValidatorRemoved event', () => {
// Given
@ -71,8 +72,8 @@ describe('parseValidatorEvent', () => {
parseValidatorEvent(event)
// Then
expect(event.event).toBe('ValidatorRemoved')
expect(event.returnValues.validator).toBe('0xcfef0c6bb765321529ffe81507f6d099693cd225')
expect(event.event).to.be.equal('ValidatorRemoved')
expect(event.returnValues.validator).to.be.equal('0xcfef0c6bb765321529ffe81507f6d099693cd225')
})
it('should parse ValidatorRemoved event from v1', () => {
// Given
@ -87,8 +88,8 @@ describe('parseValidatorEvent', () => {
parseValidatorEvent(event)
// Then
expect(event.event).toBe('ValidatorRemoved')
expect(event.returnValues.validator).toBe('0xcfef0c6bb765321529ffe81507f6d099693cd225')
expect(event.event).to.be.equal('ValidatorRemoved')
expect(event.returnValues.validator).to.be.equal('0xcfef0c6bb765321529ffe81507f6d099693cd225')
})
})
describe('processValidatorsEvents', () => {
@ -169,9 +170,9 @@ describe('processValidatorsEvents', () => {
const validatorList = processValidatorsEvents(events)
// Then
expect(validatorList.length).toBe(3)
expect(validatorList[0]).toBe('0xCbd25A2a5708051747a052dBB1b291865Fc0e474')
expect(validatorList[1]).toBe('0xBac68A86Cf596E3b124781E0bdbC47bb458bec62')
expect(validatorList[2]).toBe('0xf4BEF13F9f4f2B203FAF0C3cBbaAbe1afE056955')
expect(validatorList.length).to.be.equal(3)
expect(validatorList[0]).to.be.equal('0xCbd25A2a5708051747a052dBB1b291865Fc0e474')
expect(validatorList[1]).to.be.equal('0xBac68A86Cf596E3b124781E0bdbC47bb458bec62')
expect(validatorList[2]).to.be.equal('0xf4BEF13F9f4f2B203FAF0C3cBbaAbe1afE056955')
})
})

@ -1,4 +1,6 @@
const { BRIDGE_MODES, FEE_MANAGER_MODE } = require('./constants')
const { toWei, toBN } = require('web3-utils')
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
function decodeBridgeMode(bridgeModeHash) {
switch (bridgeModeHash) {
@ -35,6 +37,19 @@ async function getBridgeMode(contract) {
}
}
const getTokenType = async (bridgeTokenContract, bridgeAddress) => {
try {
const resultBridgeAddress = await bridgeTokenContract.methods.bridgeContract().call()
if (resultBridgeAddress === bridgeAddress) {
return ERC_TYPES.ERC677
} else {
return ERC_TYPES.ERC20
}
} catch (e) {
return ERC_TYPES.ERC20
}
}
const getUnit = bridgeMode => {
let unitHome = null
let unitForeign = null
@ -54,9 +69,196 @@ const getUnit = bridgeMode => {
return { unitHome, unitForeign }
}
const parseValidatorEvent = event => {
if (
event.event === undefined &&
event.raw &&
event.raw.topics &&
(event.raw.topics[0] === '0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987' ||
event.raw.topics[0] === '0x8064a302796c89446a96d63470b5b036212da26bd2debe5bec73e0170a9a5e83')
) {
const rawAddress = event.raw.topics.length > 1 ? event.raw.topics[1] : event.raw.data
const address = '0x' + rawAddress.slice(26)
event.event = 'ValidatorAdded'
event.returnValues.validator = address
} else if (
event.event === undefined &&
event.raw &&
event.raw.topics &&
event.raw.topics[0] === '0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1'
) {
const rawAddress = event.raw.data === '0x' ? event.raw.topics[1] : event.raw.data
const address = '0x' + rawAddress.slice(26)
event.event = 'ValidatorRemoved'
event.returnValues.validator = address
}
}
const processValidatorsEvents = events => {
const validatorList = new Set()
events.forEach(event => {
parseValidatorEvent(event)
if (event.event === 'ValidatorAdded') {
validatorList.add(event.returnValues.validator)
} else if (event.event === 'ValidatorRemoved') {
validatorList.delete(event.returnValues.validator)
}
})
return Array.from(validatorList)
}
const tryCall = async (method, fallbackValue) => {
try {
return await method.call()
} catch (e) {
return fallbackValue
}
}
const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0)
const getPastEvents = async (
contract,
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
) => {
let events
try {
events = await contract.getPastEvents(event, {
...options,
fromBlock,
toBlock
})
} catch (e) {
if (e.message.includes('query returned more than') && toBlock !== 'latest') {
const middle = toBN(fromBlock)
.add(toBlock)
.divRound(toBN(2))
const middlePlusOne = middle.add(toBN(1))
const firstHalfEvents = await getPastEvents(contract, {
...options,
event,
fromBlock,
toBlock: middle
})
const secondHalfEvents = await getPastEvents(contract, {
...options,
event,
fromBlock: middlePlusOne,
toBlock
})
events = [...firstHalfEvents, ...secondHalfEvents]
} else {
throw new Error(e)
}
}
return events
}
const getValidatorList = async (address, eth, options) => {
options.logger && options.logger.debug && options.logger.debug('getting validatorList')
const validatorsContract = new eth.Contract(REWARDABLE_VALIDATORS_ABI, address) // in monitor, BRIDGE_VALIDATORS_ABI was used
const validators = await tryCall(validatorsContract.methods.validatorList(), [])
if (validators.length) {
return validators
}
options.logger && options.logger.debug && options.logger.debug('getting validatorsEvents')
const deployedAtBlock = await tryCall(validatorsContract.methods.deployedAtBlock(), 0)
const fromBlock = options.fromBlock || Number(deployedAtBlock) || 0
const toBlock = options.toBlock || 'latest'
const validatorsEvents = await getPastEvents(new eth.Contract([], address), {
event: 'allEvents',
fromBlock,
toBlock,
options: {}
})
return processValidatorsEvents(validatorsEvents)
}
const gasPriceWithinLimits = (gasPrice, limits) => {
if (!limits) {
return gasPrice
}
if (gasPrice < limits.MIN) {
return limits.MIN
} else if (gasPrice > limits.MAX) {
return limits.MAX
} else {
return gasPrice
}
}
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
let gasPrice = oracleGasPrice * factor
gasPrice = gasPriceWithinLimits(gasPrice, limits)
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
// fetchFn has to be supplied (instead of just url to oracle),
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromOracle = async (fetchFn, options = {}) => {
try {
const response = await fetchFn()
const json = await response.json()
const oracleGasPrice = json[options.speedType]
if (!oracleGasPrice) {
options.logger &&
options.logger.error &&
options.logger.error(`Response from Oracle didn't include gas price for ${options.speedType} type.`)
return null
}
const normalizedGasPrice = normalizeGasPrice(oracleGasPrice, options.factor, options.limits)
options.logger &&
options.logger.debug &&
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
return normalizedGasPrice
} catch (e) {
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
}
return null
}
const gasPriceFromContract = async (bridgeContract, options = {}) => {
try {
const gasPrice = await bridgeContract.methods.gasPrice().call()
options.logger &&
options.logger.debug &&
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
return gasPrice
} catch (e) {
options.logger &&
options.logger.error &&
options.logger.error(`There was a problem getting the gas price from the contract. ${e.message}`)
}
return null
}
module.exports = {
decodeBridgeMode,
decodeFeeManagerMode,
getBridgeMode,
getUnit
getTokenType,
getUnit,
parseValidatorEvent,
processValidatorsEvents,
getValidatorList,
getPastEvents,
getDeployedAtBlock,
normalizeGasPrice,
gasPriceFromOracle,
gasPriceFromContract,
gasPriceWithinLimits
}

@ -158,6 +158,6 @@ const foreignViAbi = [
]
module.exports = {
homeV1Abi,
foreignViAbi
HOME_V1_ABI: homeV1Abi,
FOREIGN_V1_ABI: foreignViAbi
}

@ -1,4 +1,4 @@
# POA Token Bridge / Deployment Configuration
# POA TokenBridge / Deployment Configuration
Please see the [Oracle](../oracle/README.md) for additional configuration and execution details.
@ -46,6 +46,8 @@ cp hosts.yml.example hosts.yml
hosts:
<host_ip_B>:
ansible_user: <user>
#syslog_server_port: "<protocol>://<ip>:<port>"
#monitor_cron_schedule: "*/4 * * * *" # When this parameter is set, it will overwrite default schedule for performing checks
```
The config above would install the Oracle on `<host_ip_A>`, UI on `<host_ip_C>`, and both Oracle, UI and Monitor on `<host_ip_B>`.
@ -85,13 +87,13 @@ Example config for installing only UI:
2. You can also add the following parameters in the `group_vars` to change the default behavior of the playbooks:
2.1 `compose_service_user` - specifies the user created by the playbooks. This user runs the Token Bridge Oracle.
2.1 `compose_service_user` - specifies the user created by the playbooks. This user runs the TokenBridge Oracle.
2.2 `bridge_repo` contains the address of the Token Bridge Oracle repository. The default value is https://github.com/poanetwork/tokenbridge.
2.2 `bridge_repo` contains the address of the TokenBridge Oracle repository. The default value is https://github.com/poanetwork/tokenbridge.
2.3 `bridge_repo_branch` points to the specific branch or commit to use with the `bridge_repo`. If `bridge_repo_branch` is not specified, the default (`master`) branch is used.
2.4 `bridge_path` sets the path where the Token Bridge Oracle is installed. By default, it points. to the home folder of `compose_service_user`
2.4 `bridge_path` sets the path where the TokenBridge Oracle is installed. By default, it points. to the home folder of `compose_service_user`
2.5 `docker_compose_version` - specifies a version of docker-compose to be installed.

@ -1,4 +1,4 @@
# POA Token Bridge / Deployment Execution
# POA TokenBridge / Deployment Execution
Please refer to the [Configuration](./CONFIGURATION.md) first.
@ -74,6 +74,49 @@ where the _<watcher>_ could be one of the following:
- `collected-signatures`
- `affirmation-request`
## Reset nonce counters
In case some tx from your bridge validator account were done outside the bridge, you might need to update nonce counters.
1. ssh to your bridge node and run:
```
$ sudo su poadocker
$ cd ~/bridge
```
1. stop running docker containers using the nonce by running:
```
$ docker-compose stop bridge_senderhome bridge_senderforeign
```
1. Connect to the redis container:
```
$ docker-compose exec redis /bin/bash
```
you should get a shell prompt from inside the docker container, similar to this:
```
root@redis:/data#
```
1. connect to redis database by running `redis-cli`, prompt should change once again to
```
127.0.0.1:6379>
```
1. list existing keys by running `keys *`, output should look like this:
```
127.0.0.1:6379> keys *
1) "erc-native-affirmation-request:lastProcessedBlock"
2) "erc-native-collected-signatures:lastProcessedBlock"
3) "erc-native-signature-request:lastProcessedBlock"
4) "home:nonce"
5) "foreign:nonce"
```
1. delete keys containing last used nonces on both networks
```
127.0.0.1:6379> del "home:nonce" "foreign:nonce"
```
1. exit from redis container by running `exit` twice
1. start the rest of bridge containers:
```
$ docker-compose start bridge_senderhome bridge_senderforeign
```
## Logs
If the `syslog_server_port` option in the hosts.yml file is not set, all logs will be stored in `/var/log/docker/` folder in the set of folders with the `bridge_` prefix.

@ -1,8 +1,8 @@
# POA Token Bridge / Deployment
# POA TokenBridge / Deployment
Ansible playbooks for deploying cross-chain bridges.
## Overview
Please refer to the [POA Token Bridge](../README.md) overview first of all.
Please refer to the [POA TokenBridge](../README.md) overview first of all.
These playbooks are designed to automate the deployment process for cross-chain bridges on bridge validator nodes. This process installs the bridge as a service and sets .env configurations on a remote server.

@ -51,4 +51,3 @@ MONITOR_FOREIGN_GAS_LIMIT: 300000
MONITOR_HOME_GAS_PRICE_FALLBACK: 0
MONITOR_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
MONITOR_LEFT_TX_THRESHOLD: 100
MONITOR_CRON_SCHEDULE: "* * * * *"

@ -0,0 +1,7 @@
---
BRIDGE_MODE: "ERC_TO_ERC"
HOME_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
FOREIGN_BRIDGE_ADDRESS: "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
ERC20_TOKEN_ADDRESS: "0x3C665A31199694Bf723fD08844AD290207B5797f"
UI_PORT: 3001
MONITOR_PORT: 3011

@ -0,0 +1,7 @@
---
BRIDGE_MODE: "ERC_TO_NATIVE"
HOME_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
FOREIGN_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
ERC20_TOKEN_ADDRESS: "0x3C665A31199694Bf723fD08844AD290207B5797f"
UI_PORT: 3002
MONITOR_PORT: 3012

@ -52,4 +52,3 @@ MONITOR_FOREIGN_GAS_LIMIT: 300000
MONITOR_HOME_GAS_PRICE_FALLBACK: 1000000000
MONITOR_FOREIGN_GAS_PRICE_FALLBACK: 1000000000
MONITOR_LEFT_TX_THRESHOLD: 100
MONITOR_CRON_SCHEDULE: "* * * * *"

@ -0,0 +1,7 @@
---
BRIDGE_MODE: "NATIVE_TO_ERC"
HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
ERC20_TOKEN_ADDRESS: "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B"
UI_PORT: 3000
MONITOR_PORT: 3010

@ -0,0 +1,56 @@
---
## General settings
# BRIDGE_MODE: ""
HOME_NATIVE_NAME: "POA"
ALLOW_HTTP: yes
LOG_LEVEL: debug
## Home contract
HOME_RPC_URL: "https://sokol.poa.network"
HOME_NAME: "POA Sokol"
HOME_WITHOUT_EVENTS: false
# HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
HOME_POLLING_INTERVAL: 5000
## Foreign contract
FOREIGN_RPC_URL: "https://sokol.poa.network"
FOREIGN_NAME: "Kovan"
FOREIGN_WITHOUT_EVENTS: false
# FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
# ERC20_TOKEN_ADDRESS: "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B"
FOREIGN_POLLING_INTERVAL: 1000
## Home Gasprice
HOME_GAS_PRICE_ORACLE_URL: "https://gasprice.poa.network/"
HOME_GAS_PRICE_SPEED_TYPE: "standard"
HOME_GAS_PRICE_FALLBACK: 1000000000 # in wei
HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
HOME_GAS_PRICE_FACTOR: 1
## Foreign Gasprice
FOREIGN_GAS_PRICE_ORACLE_URL: "https://gasprice.poa.network/"
FOREIGN_GAS_PRICE_SPEED_TYPE: "standard"
FOREIGN_GAS_PRICE_FALLBACK: 1000000000 # in wei
FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
FOREIGN_GAS_PRICE_FACTOR: 1
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_DESCRIPTION: "The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner."
# UI_PORT: 3000
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/sokol/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%s
UI_HOME_GAS_PRICE_FALLBACK: 1000000000
UI_FOREIGN_GAS_PRICE_FALLBACK: 1000000000
## Monitor
# MONITOR_PORT: 3003
MONITOR_HOME_DEPLOYMENT_BLOCK: 0
MONITOR_FOREIGN_DEPLOYMENT_BLOCK: 0
MONITOR_HOME_GAS_LIMIT: 300000
MONITOR_FOREIGN_GAS_LIMIT: 300000
MONITOR_HOME_GAS_PRICE_FALLBACK: 1000000000
MONITOR_FOREIGN_GAS_PRICE_FALLBACK: 1000000000
MONITOR_LEFT_TX_THRESHOLD: 100

@ -52,4 +52,3 @@ MONITOR_FOREIGN_GAS_LIMIT: 300000
MONITOR_HOME_GAS_PRICE_FALLBACK: 15000000000
MONITOR_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
MONITOR_LEFT_TX_THRESHOLD: 100
MONITOR_CRON_SCHEDULE: "* * * * *"

@ -16,3 +16,5 @@ sokol-kovan:
hosts:
127.0.0.1:
ansible_user: ubuntu
#syslog_server_port: "<protocol>://<ip>:<port>"
#monitor_cron_schedule: "*/4 * * * *"

@ -1,4 +1,4 @@
# POA Token Bridge / Deployment Testing
# POA TokenBridge / Deployment Testing
The deployment playbooks are tested using [Molecule](https://molecule.readthedocs.io).
@ -30,3 +30,7 @@ Scenario | Description
--- | ---
oracle | Deploys and checks standalone Oracle on Ubuntu host
ui | Deploys and checks standalone UI on Ubuntu host
## Ultimate E2E tests
For information on the Ultimate tests, please refer to [Ultimate](../../e2e-commons/ULTIMATE.md).

@ -7,6 +7,5 @@ services:
dockerfile: molecule/Dockerfile
restart: 'no'
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock

@ -3,10 +3,9 @@ cd $(dirname $0)
set -e # exit when any command fails
CODEBASE_BRANCH=${CIRCLE_BRANCH-$(git symbolic-ref --short HEAD)}
DOCKER_LOCALHOST=${DOCKER_LOCALHOST-localhost}
while [ "$1" != "" ]; do
docker-compose build && docker-compose run -e CODEBASE_BRANCH=$CODEBASE_BRANCH -e DOCKER_LOCALHOST=$DOCKER_LOCALHOST molecule_runner /bin/bash -c "molecule test --scenario-name $1"
docker-compose build && docker-compose run -e CODEBASE_BRANCH=$CODEBASE_BRANCH molecule_runner /bin/bash -c "molecule test --scenario-name $1"
shift # Shift all the parameters down by one
done

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

@ -0,0 +1,56 @@
---
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
enabled: True
options:
config-data:
ignore: ../../hosts.yml
platforms:
- name: monitor-host
groups:
- example
children:
- monitor
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: ../../site.yml
inventory:
host_vars:
monitor-host:
bridge_repo_branch: $CODEBASE_BRANCH
syslog_server_port: "udp://127.0.0.1:514"
verifier:
name: testinfra
lint:
name: flake8
additional_files_or_dirs:
- ../../tests/*
scenario:
name: monitor
test_sequence:
- lint
- cleanup
- destroy
- dependency
- syntax
- create
- prepare
- converge
- verify
- destroy

@ -0,0 +1,54 @@
import os
import pytest
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('monitor')
@pytest.mark.parametrize("name", [
("monitor_monitor_1")
])
def test_docker_containers(host, name):
container = host.docker(name)
assert container.is_running
@pytest.mark.parametrize("service", [
("tokenbridge-monitor"),
("rsyslog")
])
def test_services(host, service):
assert host.service(service).is_enabled
assert host.service(service).is_running
@pytest.mark.parametrize("filename", [
("/etc/rsyslog.d/33-monitor-docker.conf"),
("/etc/rsyslog.d/38-monitor-remote-logging.conf")
])
def test_logging(host, filename):
assert host.file(filename).exists
assert host.file(filename).mode == 0o0644
def test_home_exists(host):
assert host.run_test(
'curl -s http://localhost:3003 | '
'grep -q -i "home"'
)
def test_foreign_exists(host):
assert host.run_test(
'curl -s http://localhost:3003 | '
'grep -q -i "foreign"'
)
def test_no_error(host):
assert host.run_expect(
[1],
'curl -s http://localhost:3003 | '
'grep -i -q "error"'
)

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

@ -0,0 +1,36 @@
---
- name: Overwrite Oracle the docker-compose
hosts: oracle
become: true
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: 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 new docker-compose file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "/home/poadocker/bridge/oracle/docker-compose.yml"
- name: start the service
shell: service poabridge start

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

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

@ -0,0 +1,56 @@
---
driver:
name: docker
platforms:
- name: oracle-erc-to-erc-host
groups:
- ultimate
- erc-to-erc
children:
- oracle
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-erc-to-erc-host
groups:
- ultimate
- erc-to-erc
children:
- ui
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-erc-to-erc-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://parity1:8545"
FOREIGN_RPC_URL: "http://parity2:8545"
VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-erc-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://localhost:8541"
FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-erc-to-erc
test_sequence:
- cleanup
- destroy
- syntax
- create
- prepare
- converge

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

@ -0,0 +1,56 @@
---
driver:
name: docker
platforms:
- name: oracle-erc-to-native-host
groups:
- ultimate
- erc-to-native
children:
- oracle
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-erc-to-native-host
groups:
- ultimate
- erc-to-native
children:
- ui
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-erc-to-native-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://parity1:8545"
FOREIGN_RPC_URL: "http://parity2:8545"
VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-native-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://localhost:8541"
FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-erc-to-native
test_sequence:
- cleanup
- destroy
- syntax
- create
- prepare
- converge

@ -4,7 +4,8 @@ driver:
platforms:
- name: oracle-native-to-erc-host
groups:
- example
- ultimate
- native-to-erc
children:
- oracle
image: ubuntu:16.04
@ -12,26 +13,34 @@ platforms:
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-native-to-erc-host
groups:
- ultimate
- native-to-erc
children:
- ui
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: ../../site.yml
converge: ../ultimate-commons/converge.yml
inventory:
host_vars:
oracle-native-to-erc-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://parity1:8545"
FOREIGN_RPC_URL: "http://parity2:8545"
VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
ERC20_TOKEN_ADDRESS: "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B"
QUEUE_URL: "amqp://$DOCKER_LOCALHOST"
REDIS_URL: "redis://$DOCKER_LOCALHOST:6379"
HOME_RPC_URL: "http://$DOCKER_LOCALHOST:8541"
FOREIGN_RPC_URL: "http://$DOCKER_LOCALHOST:8542"
ALLOW_HTTP: yes
LOG_LEVEL: debug
ui-native-to-erc-host:
bridge_repo_branch: $CODEBASE_BRANCH
HOME_RPC_URL: "http://localhost:8541"
FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:

@ -1,5 +1,4 @@
{
"disable-legacy-registry": true,
"live-restore": true,
"no-new-privileges": true
}

@ -6,3 +6,7 @@
force: no
update: no
version: "{{ bridge_repo_branch }}"
- name: Initialize submodules
shell: git submodule update --init
args:
chdir: "{{ bridge_path }}"

@ -0,0 +1,2 @@
---
monitor_cron_schedule: "*/4 * * * *"

@ -0,0 +1,18 @@
---
- name: Parse cron schedule
set_fact:
minute: "{{ monitor_cron_schedule.split(' ')[0] }}"
hour: "{{ monitor_cron_schedule.split(' ')[1] }}"
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'"
- name: Add cron entry
cron:
name: "RUN_MONITOR_CHECKS"
minute: "{{ minute }}"
hour: "{{ hour }}"
day: "{{ day }}"
month: "{{ month }}"
weekday: "{{ weekday }}"
job: "{{ job }}"

@ -0,0 +1,41 @@
---
- name: Slurp docker compose file
slurp:
src: "{{ bridge_path }}/monitor/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 }}/monitor/docker-compose.yml"
- name: Set the local container logs configuration file
template:
src: 33-monitor-docker.conf.j2
dest: /etc/rsyslog.d/33-monitor-docker.conf
owner: root
group: root
mode: 0644
- name: Set the log configuration file to send container logs to remote server
template:
src: 38-monitor-remote-logging.conf.j2
dest: /etc/rsyslog.d/38-monitor-remote-logging.conf
owner: root
group: root
mode: 0644
when: syslog_server_port is defined
- name: restart rsyslog
service:
name: rsyslog
state: restarted

@ -1,4 +1,6 @@
---
- include_tasks: pre_config.yml
- include_tasks: logging.yml
- include_tasks: jumpbox.yml
- include_tasks: servinstall.yml
- include_tasks: cron.yml

@ -22,4 +22,3 @@ FOREIGN_GAS_PRICE_FALLBACK={{ MONITOR_FOREIGN_GAS_PRICE_FALLBACK }}
FOREIGN_GAS_PRICE_FACTOR={{ FOREIGN_GAS_PRICE_FACTOR }}
LEFT_TX_THRESHOLD={{ MONITOR_LEFT_TX_THRESHOLD }}
PORT={{ MONITOR_PORT }}
CRON_SCHEDULE={{ MONITOR_CRON_SCHEDULE }}

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

@ -0,0 +1,15 @@
if $programname startswith 'monitor_' then {
action(
type="omfwd"
protocol="{{ syslog_server_port.split(":")[0] }}"
target="{{ (syslog_server_port.split(":")[1])[2:] }}"
port="{{ syslog_server_port.split(":")[2] }}"
template="RemoteForwardFormat"
queue.SpoolDirectory="/var/spool/rsyslog"
queue.FileName="remote"
queue.MaxDiskSpace="1g"
queue.SaveOnShutdown="on"
queue.Type="LinkedList"
ResendLastMSGOnReconnect="on"
)
}

@ -3,5 +3,4 @@ bridge_path: "/home/{{ compose_service_user }}/bridge"
ALLOW_HTTP: no
QUEUE_URL: amqp://rabbit
REDIS_URL: redis://redis
syslog_server_port: udp://127.0.0.1:514
keyfile_path: "/root/.key"

@ -1,4 +1,4 @@
# POA Token Bridge / E2E-Commons
# POA TokenBridge / E2E-Commons
Common scripts and configuration for the end-to-end tests.
@ -25,3 +25,7 @@ Shut down and cleans up containers, networks, services, running scripts:
| ui | Launches UI containers |
| blocks | Auto mines blocks |
| native-to-erc | Creates infrastructure for ultimate e2e testing, for native-to-erc type of bridge |
#### Ultimate e2e testing
For more information on the Ultimate e2e testing, please refer to [Ultimate](./ULTIMATE.md).

29
e2e-commons/ULTIMATE.md Normal file

@ -0,0 +1,29 @@
# POA TokenBridge / Ultimate E2E
Documentation regarding the Ultimate end-to-end tests.
## Overview
The ultimate e2e test scenario covers native-to-erc type of bridge.
It runs the e2e tests on components deployed using the deployment playbooks.
## Usage
### 1. Prepare the infrastructure
Run the Parity nodes, deploy the bridge contracts, deploy Oracle using the deployment playbook.
```bash
./up.sh deploy native-to-erc
```
### 2. Run the E2E tests
```
docker-compose run e2e yarn workspace oracle-e2e run native-to-erc
```
## Diagram
![diagram](./ultimate.png)

@ -11,6 +11,10 @@
"address": "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b",
"privateKey": "0x8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
},
"blockGenerator": {
"address": "0xB4579fd5AfEaB60B03Db3F408AAdD315035943f7",
"privateKey": "0xd6143d390d8b28c33601bb0fe29392fb1c35c24ccfe8722c09c2bdd6ada2699f"
},
"nativeToErcBridge": {
"home": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
"foreign": "0x2B6871b9B02F73fa24F4864322CdC78604207769",

@ -1,22 +1,33 @@
---
version: '3'
networks:
ultimate:
external: true
services:
parity1:
build: ../parity
ports:
- "8541:8545"
networks:
- ultimate
parity2:
build:
context: ../parity
dockerfile: Dockerfile-foreign
ports:
- "8542:8545"
networks:
- ultimate
redis:
image: "redis:4"
networks:
- ultimate
rabbit:
image: "rabbitmq:3-management"
ports:
- "15672:15672"
networks:
- ultimate
oracle:
build:
context: ..
@ -47,6 +58,8 @@ services:
- FOREIGN_POLLING_INTERVAL=500
- ALLOW_HTTP=yes
command: "true"
networks:
- ultimate
oracle-erc20:
build:
context: ..
@ -78,6 +91,8 @@ services:
- FOREIGN_POLLING_INTERVAL=500
- ALLOW_HTTP=yes
command: "true"
networks:
- ultimate
oracle-erc20-native:
build:
context: ..
@ -109,6 +124,8 @@ services:
- FOREIGN_POLLING_INTERVAL=500
- ALLOW_HTTP=yes
command: "true"
networks:
- ultimate
oracle-amb:
build:
context: ..
@ -137,6 +154,8 @@ services:
- FOREIGN_POLLING_INTERVAL=500
- ALLOW_HTTP=yes
command: "true"
networks:
- ultimate
ui:
build:
context: ..
@ -165,6 +184,8 @@ services:
- REACT_APP_FOREIGN_GAS_PRICE_FACTOR=1
- PORT=3000
command: "true"
networks:
- ultimate
ui-erc20:
build:
context: ..
@ -193,6 +214,8 @@ services:
- REACT_APP_FOREIGN_GAS_PRICE_FACTOR=1
- PORT=3000
command: "true"
networks:
- ultimate
ui-erc20-native:
build:
context: ..
@ -221,6 +244,8 @@ services:
- REACT_APP_FOREIGN_GAS_PRICE_FACTOR=1
- PORT=3000
command: "true"
networks:
- ultimate
monitor:
build:
context: ..
@ -243,12 +268,72 @@ services:
- FOREIGN_GAS_PRICE_FALLBACK=1000000000
- FOREIGN_GAS_PRICE_FACTOR=1
- LEFT_TX_THRESHOLD=100
- PORT=3003
- PORT=3010
entrypoint: yarn start
ports:
- "3003:3003"
- "3010:3010"
networks:
- ultimate
monitor-erc20:
build:
context: ..
dockerfile: monitor/Dockerfile
environment:
- HOME_RPC_URL=http://parity1:8545
- FOREIGN_RPC_URL=http://parity2:8545
- HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E
- FOREIGN_BRIDGE_ADDRESS=0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127
- HOME_DEPLOYMENT_BLOCK=0
- FOREIGN_DEPLOYMENT_BLOCK=0
- HOME_GAS_LIMIT=300000
- HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- HOME_GAS_PRICE_SPEED_TYPE=standard
- HOME_GAS_PRICE_FALLBACK=1000000000
- HOME_GAS_PRICE_FACTOR=1
- FOREIGN_GAS_LIMIT=300000
- FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- FOREIGN_GAS_PRICE_SPEED_TYPE=standard
- FOREIGN_GAS_PRICE_FALLBACK=1000000000
- FOREIGN_GAS_PRICE_FACTOR=1
- LEFT_TX_THRESHOLD=100
- PORT=3011
entrypoint: yarn start
ports:
- "3011:3011"
networks:
- ultimate
monitor-erc20-native:
build:
context: ..
dockerfile: monitor/Dockerfile
environment:
- HOME_RPC_URL=http://parity1:8545
- FOREIGN_RPC_URL=http://parity2:8545
- HOME_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda
- FOREIGN_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda
- HOME_DEPLOYMENT_BLOCK=0
- FOREIGN_DEPLOYMENT_BLOCK=0
- HOME_GAS_LIMIT=300000
- HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- HOME_GAS_PRICE_SPEED_TYPE=standard
- HOME_GAS_PRICE_FALLBACK=1000000000
- HOME_GAS_PRICE_FACTOR=1
- FOREIGN_GAS_LIMIT=300000
- FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/
- FOREIGN_GAS_PRICE_SPEED_TYPE=standard
- FOREIGN_GAS_PRICE_FALLBACK=1000000000
- FOREIGN_GAS_PRICE_FACTOR=1
- LEFT_TX_THRESHOLD=100
- PORT=3012
entrypoint: yarn start
ports:
- "3012:3012"
networks:
- ultimate
e2e:
build:
context: ..
dockerfile: Dockerfile.e2e
command: "true"
networks:
- ultimate

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

@ -3,16 +3,19 @@ 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 {user} = require('../constants.json');
const {user, blockGenerator} = require('../constants.json');
homeWeb3.eth.accounts.wallet.add(user.privateKey)
foreignWeb3.eth.accounts.wallet.add(user.privateKey)
homeWeb3.eth.accounts.wallet.add(blockGenerator.privateKey)
foreignWeb3.eth.accounts.wallet.add(blockGenerator.privateKey)
function main() {
setTimeout(async () => {
generateNewBlock(homeWeb3, user.address)
generateNewBlock(foreignWeb3, user.address)
generateNewBlock(homeWeb3, blockGenerator.address)
generateNewBlock(foreignWeb3, blockGenerator.address)
main()
}, 5000)
}, 1000)
}
main()

BIN
e2e-commons/ultimate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

@ -4,14 +4,10 @@ set -e # exit when any command fails
./down.sh
docker-compose build
docker network create --driver bridge ultimate || true
docker-compose up -d parity1 parity2 e2e
export DOCKER_LOCALHOST="localhost"
while [ "$1" != "" ]; do
if [ "$1" == "macos" ]; then
export DOCKER_LOCALHOST="host.docker.internal"
fi
if [ "$1" == "oracle" ]; then
docker-compose up -d redis rabbit oracle oracle-erc20 oracle-erc20-native oracle-amb
@ -48,12 +44,20 @@ while [ "$1" != "" ]; do
fi
if [ "$1" == "monitor" ]; then
docker-compose up -d monitor
docker-compose up -d monitor monitor-erc20 monitor-erc20-native
fi
if [ "$1" == "native-to-erc" ]; then
../deployment/molecule/molecule.sh ultimate-native-to-erc
fi
if [ "$1" == "erc-to-native" ]; then
../deployment/molecule/molecule.sh ultimate-erc-to-native
fi
if [ "$1" == "erc-to-erc" ]; then
../deployment/molecule/molecule.sh ultimate-erc-to-erc
fi
shift # Shift all the parameters down by one
done

@ -1,6 +1,6 @@
# POA Token Bridge / Monitor-E2E
# POA TokenBridge / Monitor-E2E
End to end tests for the POA Token Bridge [Monitor](../monitor/README.md).
End to end tests for the POA TokenBridge [Monitor](../monitor/README.md).
## Running

26
monitor-e2e/erc-to-erc.sh Executable file

@ -0,0 +1,26 @@
set -e # exit when any command fails
echo "MONITOR E2E - ERC TO ERC"
export URL=localhost:3011
echo "Test case - Web Interface should return balances"
OUTPUT=$(curl -s http://$URL/)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return validators"
OUTPUT=$(curl -s http://$URL/validators)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return eventsStats"
OUTPUT=$(curl -s http://$URL/eventsStats)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return alerts"
OUTPUT=$(curl -s http://$URL/alerts)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")

26
monitor-e2e/erc-to-native.sh Executable file

@ -0,0 +1,26 @@
set -e # exit when any command fails
echo "MONITOR E2E - ERC TO NATIVE"
export URL=localhost:3012
echo "Test case - Web Interface should return balances"
OUTPUT=$(curl -s http://$URL/)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return validators"
OUTPUT=$(curl -s http://$URL/validators)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return eventsStats"
OUTPUT=$(curl -s http://$URL/eventsStats)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return alerts"
OUTPUT=$(curl -s http://$URL/alerts)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")

26
monitor-e2e/native-to-erc.sh Executable file

@ -0,0 +1,26 @@
set -e # exit when any command fails
echo "MONITOR E2E - NATIVE TO ERC"
export URL="localhost:3010"
echo "Test case - Web Interface should return balances"
OUTPUT=$(curl -s http://$URL/)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return validators"
OUTPUT=$(curl -s http://$URL/validators)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return eventsStats"
OUTPUT=$(curl -s http://$URL/eventsStats)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return alerts"
OUTPUT=$(curl -s http://$URL/alerts)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")

@ -17,6 +17,8 @@ check_files_exist() {
for f in "${FILES[@]}"; do
command="test -f responses/$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
done
return $rc
}
@ -24,38 +26,24 @@ check_files_exist() {
##### Initialization #####
../e2e-commons/up.sh oracle deploy monitor
../e2e-commons/up.sh deploy oracle monitor
##### Initial checks #####
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "yarn check-all"
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20 /bin/bash -c "yarn check-all"
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor-erc20-native /bin/bash -c "yarn check-all"
check_files_exist
##### Test cases #####
echo "Test case - CheckWorker scripts should work and create files in responses/ directory"
(! check_files_exist)
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "node checkWorker.js"
docker-compose -f ../e2e-commons/docker-compose.yml exec monitor /bin/bash -c "node checkWorker2.js"
check_files_exist
./native-to-erc.sh
echo "Test case - Web Interface should return balances"
OUTPUT=$(curl -s http://localhost:3003/)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
./erc-to-erc.sh
echo "Test case - Web Interface should return validators"
OUTPUT=$(curl -s http://localhost:3003/validators)
grep -q home <<< "$OUTPUT"
grep -q foreign <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return eventsStats"
OUTPUT=$(curl -s http://localhost:3003/eventsStats)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
echo "Test case - Web Interface should return alerts"
OUTPUT=$(curl -s http://localhost:3003/alerts)
grep -q lastChecked <<< "$OUTPUT"
(! grep -q error <<< "$OUTPUT")
./erc-to-native.sh
##### Cleanup #####

@ -1,12 +1,12 @@
# POA Token Bridge / Monitor
# POA TokenBridge / Monitor
Tool for checking balances and unprocessed events in bridged networks.
## Overview
Please refer to the [POA Token Bridge](../README.md) overview first of all.
Please refer to the [POA TokenBridge](../README.md) overview first of all.
- Deployed version: https://bridge-monitoring.poa.net/
This tool allows you to spin up a NODE.JS server to monitor for health of the Token Bridge contracts: check for the balance difference, discover inconsistency in the validators list, catch unhandled transactions.
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.
## Examples

@ -45,25 +45,15 @@ async function main() {
)
logger.debug('extracting most recent transactionHash')
const { transactionHash: xSignaturesMostRecentTxHash = '' } =
xSignatures.sort(sortEvents).reverse()[0] || {}
const { transactionHash: xAffirmationsMostRecentTxHash = '' } =
xAffirmations.sort(sortEvents).reverse()[0] || {}
const { transactionHash: xSignaturesMostRecentTxHash = '' } = xSignatures.sort(sortEvents).reverse()[0] || {}
const { transactionHash: xAffirmationsMostRecentTxHash = '' } = xAffirmations.sort(sortEvents).reverse()[0] || {}
logger.debug('building transaction objects')
const foreignValidators = await Promise.all(
xSignatures.map(event => findTxSender(web3Foreign)(event))
)
const homeValidators = await Promise.all(
xAffirmations.map(event => findTxSender(web3Home)(event))
)
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.map(normalizeEventInformation).reduce(buildTxList(foreignValidators), {})
const xAffirmationsTxs = xAffirmations.map(normalizeEventInformation).reduce(buildTxList(homeValidators), {})
logger.debug('Done')
@ -181,9 +171,7 @@ const findDifferences = src => dest => {
return (
src
.map(normalizeEventInformation)
.filter(
a => a.referenceTx === b.referenceTx && a.recipient === b.recipient && a.value === b.value
).length === 0
.filter(a => a.referenceTx === b.referenceTx && a.recipient === b.recipient && a.value === b.value).length === 0
)
}

@ -26,18 +26,12 @@ 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)
)
fs.writeFileSync(path.join(__dirname, '/responses/getBalances.json'), JSON.stringify(status, null, 4))
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)
)
fs.writeFileSync(path.join(__dirname, '/responses/validators.json'), JSON.stringify(vBalances, null, 4))
logger.debug('Done')
} catch (e) {
logger.error(e)

@ -9,17 +9,11 @@ async function checkWorker2() {
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)
)
fs.writeFileSync(path.join(__dirname, '/responses/eventsStats.json'), JSON.stringify(evStats, null, 4))
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)
)
fs.writeFileSync(path.join(__dirname, '/responses/alerts.json'), JSON.stringify(_alerts, null, 4))
logger.debug('Done x2')
} catch (e) {
logger.error(e)

@ -9,10 +9,7 @@ async function checkWorker3() {
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)
)
fs.writeFileSync(path.join(__dirname, '/responses/stuckTransfers.json'), JSON.stringify(transfers, null, 4))
logger.debug('Done')
} catch (e) {
logger.error('checkWorker3.js', e)

@ -27,10 +27,7 @@ const {
async function main(bridgeMode) {
if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
const foreignBridge = new web3Foreign.eth.Contract(
FOREIGN_ERC_TO_ERC_ABI,
FOREIGN_BRIDGE_ADDRESS
)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
logger.debug('calling erc20Contract.methods.balanceOf')
@ -55,15 +52,9 @@ async function main(bridgeMode) {
balanceDiff: Number(Web3Utils.fromWei(diff)),
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (
bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC ||
bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1
) {
} else if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
logger.debug('calling web3Home.eth.getBalance')
const foreignBridge = new web3Foreign.eth.Contract(
FOREIGN_NATIVE_TO_ERC_ABI,
FOREIGN_BRIDGE_ADDRESS
)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_NATIVE_TO_ERC_ABI, FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc677token().call()
const homeBalance = await web3Home.eth.getBalance(HOME_BRIDGE_ADDRESS)
const tokenContract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
@ -84,10 +75,7 @@ async function main(bridgeMode) {
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
const foreignBridge = new web3Foreign.eth.Contract(
FOREIGN_ERC_TO_NATIVE_ABI,
FOREIGN_BRIDGE_ADDRESS
)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_NATIVE_ABI, FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
const erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
logger.debug('calling erc20Contract.methods.balanceOf')

@ -3,9 +3,7 @@ const eventsInfo = require('./utils/events')
const { BRIDGE_MODES } = require('../commons')
async function main(bridgeMode) {
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals } = await eventsInfo(
bridgeMode
)
const { foreignDeposits, homeDeposits, homeWithdrawals, foreignWithdrawals } = await eventsInfo(bridgeMode)
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {

@ -91,8 +91,7 @@ app.get('/eventsStats', async (req, res, next) => {
app.get('/alerts', async (req, res, next) => {
try {
const results = await readFile('./responses/alerts.json')
results.ok =
!results.executeAffirmations.mostRecentTxHash && !results.executeSignatures.mostRecentTxHash
results.ok = !results.executeAffirmations.mostRecentTxHash && !results.executeSignatures.mostRecentTxHash
res.json(results)
} catch (e) {
next(e)

@ -3,12 +3,7 @@ module.exports = function logger(name) {
function log(...args) {
const now = new Date()
console.log(
now.toISOString(),
`(+${lastlog ? now.getTime() - lastlog : 0}ms)`,
`[${name}]`,
...args
)
console.log(now.toISOString(), `(+${lastlog ? now.getTime() - lastlog : 0}ms)`, `[${name}]`, ...args)
lastlog = now.getTime()
}

9
monitor/scripts/checkDocker.sh Executable file

@ -0,0 +1,9 @@
#!/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

@ -1,7 +1,7 @@
require('dotenv').config()
const Web3 = require('web3')
const logger = require('./logger')('stuckTransfers.js')
const { foreignViAbi } = require('../commons/abis')
const { FOREIGN_V1_ABI } = require('../commons/abis')
const { FOREIGN_RPC_URL, FOREIGN_BRIDGE_ADDRESS } = process.env
const FOREIGN_DEPLOYMENT_BLOCK = Number(process.env.FOREIGN_DEPLOYMENT_BLOCK) || 0
@ -75,7 +75,7 @@ function compareTransfers(transfersNormal) {
}
async function main() {
const foreignBridge = new web3Foreign.eth.Contract(foreignViAbi, FOREIGN_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_V1_ABI, FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc677token().call()
const tokenContract = new web3Foreign.eth.Contract(ABITransferWithoutData, erc20Address)
const tokenContractWithData = new web3Foreign.eth.Contract(ABIWithData, erc20Address)

@ -1,43 +1,5 @@
const { toBN } = require('web3').utils
const ONE = toBN(1)
const TWO = toBN(2)
async function getPastEvents({ contract, event, fromBlock, toBlock, options }) {
let events
try {
events = await contract.getPastEvents(event, {
...options,
fromBlock,
toBlock
})
} catch (e) {
if (e.message.includes('query returned more than 1000 results')) {
const middle = fromBlock.add(toBlock).divRound(TWO)
const middlePlusOne = middle.add(ONE)
const firstHalfEvents = await getPastEvents({
contract,
event,
fromBlock,
toBlock: middle,
options
})
const secondHalfEvents = await getPastEvents({
contract,
event,
fromBlock: middlePlusOne,
toBlock,
options
})
events = [...firstHalfEvents, ...secondHalfEvents]
} else {
throw new Error(e)
}
}
return events
}
const getBlockNumberCall = web3 => web3.eth.getBlockNumber()
async function getBlockNumber(web3Home, web3Foreign) {
@ -45,6 +7,5 @@ async function getBlockNumber(web3Home, web3Foreign) {
}
module.exports = {
getPastEvents,
getBlockNumber
}

@ -1,18 +0,0 @@
const { ERC_TYPES } = require('../../commons')
const getTokenType = async (contract, bridgeAddress) => {
try {
const bridgeContract = await contract.methods.bridgeContract().call()
if (bridgeContract === bridgeAddress) {
return ERC_TYPES.ERC677
} else {
return ERC_TYPES.ERC20
}
} catch (e) {
return ERC_TYPES.ERC20
}
}
module.exports = {
getTokenType
}

@ -8,9 +8,11 @@ const {
getBridgeABIs,
getBridgeMode,
HOME_ERC_TO_ERC_ABI,
ERC20_ABI
ERC20_ABI,
ERC677_BRIDGE_TOKEN_ABI,
getTokenType,
getPastEvents
} = require('../../commons')
const { getTokenType } = require('./ercUtils')
const { HOME_RPC_URL, FOREIGN_RPC_URL, HOME_BRIDGE_ADDRESS, FOREIGN_BRIDGE_ADDRESS } = process.env
const HOME_DEPLOYMENT_BLOCK = toBN(Number(process.env.HOME_DEPLOYMENT_BLOCK) || 0)
@ -22,7 +24,7 @@ const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const { getPastEvents, getBlockNumber } = require('./contract')
const { getBlockNumber } = require('./contract')
async function main(mode) {
const homeErcBridge = new web3Home.eth.Contract(HOME_ERC_TO_ERC_ABI, HOME_BRIDGE_ADDRESS)
@ -38,7 +40,11 @@ async function main(mode) {
isExternalErc20 = tokenType === ERC_TYPES.ERC20
const erc20MethodName =
bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || v1Bridge ? 'erc677token' : 'erc20token'
const erc20Address = await foreignBridge.methods[erc20MethodName]().call()
const erc20Address = await foreignBridge.methods[erc20MethodName]().call()const tokenType = await getTokenType(
new web3Foreign.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, erc20Address),
FOREIGN_BRIDGE_ADDRESS
)
const isExternalErc20 = tokenType === ERC_TYPES.ERC20
erc20Contract = new web3Foreign.eth.Contract(ERC20_ABI, erc20Address)
}
@ -46,36 +52,29 @@ async function main(mode) {
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
logger.debug("calling homeBridge.getPastEvents('UserRequestForSignature')")
const homeDeposits = await getPastEvents({
contract: homeBridge,
const homeDeposits = await getPastEvents(homeBridge, {
event: v1Bridge ? 'Deposit' : 'UserRequestForSignature',
fromBlock: HOME_DEPLOYMENT_BLOCK,
toBlock: homeBlockNumber,
options: {}
toBlock: homeBlockNumber
})
logger.debug("calling foreignBridge.getPastEvents('RelayedMessage')")
const foreignDeposits = await getPastEvents({
contract: foreignBridge,
const foreignDeposits = await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Deposit' : 'RelayedMessage',
fromBlock: FOREIGN_DEPLOYMENT_BLOCK,
toBlock: foreignBlockNumber,
options: {}
toBlock: foreignBlockNumber
})
logger.debug("calling homeBridge.getPastEvents('AffirmationCompleted')")
const homeWithdrawals = await getPastEvents({
contract: homeBridge,
const homeWithdrawals = await getPastEvents(homeBridge, {
event: v1Bridge ? 'Withdraw' : 'AffirmationCompleted',
fromBlock: HOME_DEPLOYMENT_BLOCK,
toBlock: homeBlockNumber,
options: {}
toBlock: homeBlockNumber
})
logger.debug("calling foreignBridge.getPastEvents('UserRequestForAffirmation')")
const foreignWithdrawals = isExternalErc20
? await getPastEvents({
contract: erc20Contract,
? await getPastEvents(erc20Contract, {
event: 'Transfer',
fromBlock: FOREIGN_DEPLOYMENT_BLOCK,
toBlock: foreignBlockNumber,
@ -83,12 +82,10 @@ async function main(mode) {
filter: { to: FOREIGN_BRIDGE_ADDRESS }
}
})
: await getPastEvents({
contract: foreignBridge,
: await getPastEvents(foreignBridge, {
event: v1Bridge ? 'Withdraw' : 'UserRequestForAffirmation',
fromBlock: FOREIGN_DEPLOYMENT_BLOCK,
toBlock: foreignBlockNumber,
options: {}
toBlock: foreignBlockNumber
})
logger.debug('Done')
return {

@ -1,79 +0,0 @@
/* eslint no-param-reassign: ["error", { "props": false }] */
const { BRIDGE_VALIDATORS_ABI } = require('../../commons')
const logger = require('../logger')('validatorsUtils')
const { getPastEvents } = require('./contract')
const parseValidatorEvent = event => {
if (
event.event === undefined &&
event.raw &&
event.raw.topics &&
(event.raw.topics[0] === '0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987' ||
event.raw.topics[0] === '0x8064a302796c89446a96d63470b5b036212da26bd2debe5bec73e0170a9a5e83')
) {
const rawAddress = event.raw.topics.length > 1 ? event.raw.topics[1] : event.raw.data
const address = '0x' + rawAddress.slice(26)
event.event = 'ValidatorAdded'
event.returnValues.validator = address
} else if (
event.event === undefined &&
event.raw &&
event.raw.topics &&
event.raw.topics[0] === '0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1'
) {
const rawAddress = event.raw.data === '0x' ? event.raw.topics[1] : event.raw.data
const address = '0x' + rawAddress.slice(26)
event.event = 'ValidatorRemoved'
event.returnValues.validator = address
}
}
const processValidatorsEvents = events => {
const validatorList = new Set()
events.forEach(event => {
parseValidatorEvent(event)
if (event.event === 'ValidatorAdded') {
validatorList.add(event.returnValues.validator)
} else if (event.event === 'ValidatorRemoved') {
validatorList.delete(event.returnValues.validator)
}
})
return Array.from(validatorList)
}
const validatorList = async contract => {
try {
return await contract.methods.validatorList().call()
} catch (e) {
return []
}
}
const getValidatorList = async (address, eth, fromBlock, toBlock) => {
logger.debug('getting validatorList')
const validatorsContract = new eth.Contract(BRIDGE_VALIDATORS_ABI, address)
const validators = await validatorList(validatorsContract)
if (validators.length) {
return validators
}
logger.debug('getting validatorsEvents')
const contract = new eth.Contract([], address)
const validatorsEvents = await getPastEvents({
contract,
event: 'allEvents',
fromBlock,
toBlock,
options: {}
})
return processValidatorsEvents(validatorsEvents)
}
module.exports = {
getValidatorList
}

@ -2,8 +2,7 @@ require('dotenv').config()
const Web3 = require('web3')
const fetch = require('node-fetch')
const logger = require('./logger')('validators')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI } = require('../commons')
const { getValidatorList } = require('./utils/validatorUtils')
const { getBridgeABIs, BRIDGE_VALIDATORS_ABI, getValidatorList, gasPriceFromOracle } = require('../commons')
const { getBlockNumber } = require('./utils/contract')
const {
@ -33,75 +32,51 @@ const web3Home = new Web3(homeProvider)
const foreignProvider = new Web3.providers.HttpProvider(FOREIGN_RPC_URL)
const web3Foreign = new Web3(foreignProvider)
const homeGasOracleOpts = {
speedType: HOME_GAS_PRICE_SPEED_TYPE,
factor: HOME_GAS_PRICE_FACTOR,
logger
}
const foreignGasOracleOpts = {
speedType: FOREIGN_GAS_PRICE_SPEED_TYPE,
factor: FOREIGN_GAS_PRICE_FACTOR,
logger
}
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
function getGasPrices(url, type, factor, fallback) {
if (!url) {
return Web3Utils.toBN(fallback)
}
return fetchGasPrices(url, type, factor, fallback)
}
async function fetchGasPrices(url, type, factor, fallback) {
try {
const response = await fetch(url)
const json = await response.json()
logger.log('Fetched gasprice: ' + json[type])
const gasPrice = json[type]
if (!gasPrice) {
throw new Error(`Response from Oracle didn't include gas price for ${type} type.`)
}
return normalizeGasPrice(gasPrice, factor)
} catch (e) {
logger.error('Gas Price API is not available', e)
return Web3Utils.toBN(fallback)
}
}
function normalizeGasPrice(oracleGasPrice, factor) {
const gasPrice = oracleGasPrice * factor
return Web3Utils.toBN(Web3Utils.toWei(gasPrice.toFixed(2).toString(), 'gwei'))
}
async function main(bridgeMode) {
const { HOME_ABI, FOREIGN_ABI } = getBridgeABIs(bridgeMode)
const homeBridge = new web3Home.eth.Contract(HOME_ABI, HOME_BRIDGE_ADDRESS)
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ABI, FOREIGN_BRIDGE_ADDRESS)
const homeValidatorsAddress = await homeBridge.methods.validatorContract().call()
const homeBridgeValidators = new web3Home.eth.Contract(
BRIDGE_VALIDATORS_ABI,
homeValidatorsAddress
)
const homeBridgeValidators = new web3Home.eth.Contract(BRIDGE_VALIDATORS_ABI, homeValidatorsAddress)
logger.debug('getting last block numbers')
const [homeBlockNumber, foreignBlockNumber] = await getBlockNumber(web3Home, web3Foreign)
logger.debug('calling foreignBridge.methods.validatorContract().call()')
const foreignValidatorsAddress = await foreignBridge.methods.validatorContract().call()
const foreignBridgeValidators = new web3Foreign.eth.Contract(
BRIDGE_VALIDATORS_ABI,
foreignValidatorsAddress
)
const foreignBridgeValidators = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress)
logger.debug('calling foreignBridgeValidators getValidatorList()')
const foreignValidators = await getValidatorList(
foreignValidatorsAddress,
web3Foreign.eth,
FOREIGN_DEPLOYMENT_BLOCK,
foreignBlockNumber
)
const foreignValidators = await getValidatorList(foreignValidatorsAddress, web3Foreign.eth, {
from: FOREIGN_DEPLOYMENT_BLOCK,
to: foreignBlockNumber,
logger
})
logger.debug('calling homeBridgeValidators getValidatorList()')
const homeValidators = await getValidatorList(
homeValidatorsAddress,
web3Home.eth,
HOME_DEPLOYMENT_BLOCK,
homeBlockNumber
)
const homeValidators = await getValidatorList(homeValidatorsAddress, web3Home.eth, {
from: HOME_DEPLOYMENT_BLOCK,
to: homeBlockNumber,
logger
})
const homeBalances = {}
logger.debug('calling asyncForEach homeValidators homeBalances')
@ -112,22 +87,16 @@ async function main(bridgeMode) {
const homeVBalances = {}
logger.debug('calling home getGasPrices')
const homeGasPrice = await getGasPrices(
HOME_GAS_PRICE_ORACLE_URL,
HOME_GAS_PRICE_SPEED_TYPE,
HOME_GAS_PRICE_FACTOR,
HOME_GAS_PRICE_FALLBACK
)
const homeGasPrice =
(await gasPriceFromOracle(() => fetch(HOME_GAS_PRICE_ORACLE_URL), homeGasOracleOpts)) ||
Web3Utils.toBN(HOME_GAS_PRICE_FALLBACK)
const homeGasPriceGwei = Web3Utils.fromWei(homeGasPrice.toString(), 'gwei')
const homeTxCost = homeGasPrice.mul(Web3Utils.toBN(HOME_GAS_LIMIT))
logger.debug('calling foreign getGasPrices')
const foreignGasPrice = await getGasPrices(
FOREIGN_GAS_PRICE_ORACLE_URL,
FOREIGN_GAS_PRICE_SPEED_TYPE,
FOREIGN_GAS_PRICE_FACTOR,
FOREIGN_GAS_PRICE_FALLBACK
)
const foreignGasPrice =
(await gasPriceFromOracle(() => fetch(FOREIGN_GAS_PRICE_ORACLE_URL), foreignGasOracleOpts)) ||
Web3Utils.toBN(FOREIGN_GAS_PRICE_FALLBACK)
const foreignGasPriceGwei = Web3Utils.fromWei(foreignGasPrice.toString(), 'gwei')
const foreignTxCost = foreignGasPrice.mul(Web3Utils.toBN(FOREIGN_GAS_LIMIT))

@ -1,6 +1,6 @@
# POA Token Bridge / Oracle-E2E
# POA TokenBridge / Oracle-E2E
End to end tests for the POA Token Bridge [Oracle](../oracle/README.md).
End to end tests for the POA TokenBridge [Oracle](../oracle/README.md).
## Prerequisites

@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"start": "mocha",
"lint": "eslint . --ignore-path ../.eslintignore"
"lint": "eslint . --ignore-path ../.eslintignore",
"native-to-erc": "mocha test/nativeToErc.js"
},
"author": "",
"license": "ISC",

@ -16,10 +16,7 @@ const { toBN } = foreignWeb3.utils
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 erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.foreignToken)
const erc677Token = new homeWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToErcBridge.homeToken)
describe('erc to erc', () => {

@ -16,10 +16,7 @@ const { toBN } = foreignWeb3.utils
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 erc20Token = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ercToNativeBridge.foreignToken)
describe('erc to native', () => {
it('should convert tokens in foreign to coins in home', async () => {

@ -1,8 +1,8 @@
# POA Token Bridge / Oracle
# POA TokenBridge / Oracle
Oracle responsible for listening to bridge related events and authorizing asset transfers.
## Overview
Please refer to the [POA Token Bridge](../README.md) overview first of all.
Please refer to the [POA TokenBridge](../README.md) overview first of all.
The Oracle is deployed on specified validator nodes (only nodes whose private keys correspond to addresses specified in the smart contracts) in the network. It connects to two chains via a Remote Procedure Call (RPC) and is responsible for:
- listening to events related to bridge contracts

@ -1,6 +1,5 @@
const baseConfig = require('./base.config')
const { ERC20_ABI } = require('../../commons')
const { ERC_TYPES } = require('../src/utils/constants')
const { ERC20_ABI, ERC_TYPES } = require('../../commons')
const initialChecksJson = process.argv[3]
@ -22,8 +21,7 @@ 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.id === 'erc-erc' && initialChecks.foreignERC === ERC_TYPES.ERC20) || baseConfig.id === 'erc-native'
? {
...baseConfig.bridgeConfig,
...baseConfig.foreignConfig,

@ -56,8 +56,7 @@ let maxProcessingTime = null
if (String(process.env.MAX_PROCESSING_TIME) === '0') {
maxProcessingTime = 0
} else if (!process.env.MAX_PROCESSING_TIME) {
maxProcessingTime =
4 * Math.max(process.env.HOME_POLLING_INTERVAL, process.env.FOREIGN_POLLING_INTERVAL)
maxProcessingTime = 4 * Math.max(process.env.HOME_POLLING_INTERVAL, process.env.FOREIGN_POLLING_INTERVAL)
} else {
maxProcessingTime = Number(process.env.MAX_PROCESSING_TIME)
}

@ -61,10 +61,7 @@ async function main() {
console.log('Signature Requests')
console.log(signatureRequestsStats)
const collectedSignaturesStats = computeCollectedSignaturesStats(
collectedSignatures,
senderForeign
)
const collectedSignaturesStats = computeCollectedSignaturesStats(collectedSignatures, senderForeign)
console.log('Collected Signatures')
console.log(collectedSignaturesStats)
}

@ -14,8 +14,7 @@ const {
HOME_TEST_TX_GAS_PRICE
} = process.env
const NUMBER_OF_WITHDRAWALS_TO_SEND =
process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1
const NUMBER_OF_WITHDRAWALS_TO_SEND = process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1
const BRIDGEABLE_TOKEN_ABI = [
{

@ -17,10 +17,7 @@ async function getStartBlock(rpcUrl, bridgeAddress, bridgeAbi) {
const deployedAtBlock = await bridgeContract.methods.deployedAtBlock().call()
const validatorContractAddress = await bridgeContract.methods.validatorContract().call()
const validatorContract = new web3Instance.eth.Contract(
BRIDGE_VALIDATORS_ABI,
validatorContractAddress
)
const validatorContract = new web3Instance.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
const validatorDeployedAtBlock = await validatorContract.methods.deployedAtBlock().call()

@ -1,7 +1,6 @@
require('../env')
const Web3 = require('web3')
const { ERC677_BRIDGE_TOKEN_ABI } = require('../../commons')
const { ERC_TYPES } = require('../src/utils/constants')
const { ERC677_BRIDGE_TOKEN_ABI, getTokenType } = require('../../commons')
async function initialChecks() {
const { ERC20_TOKEN_ADDRESS, BRIDGE_MODE, FOREIGN_RPC_URL, FOREIGN_BRIDGE_ADDRESS } = process.env
@ -9,17 +8,9 @@ async function initialChecks() {
if (BRIDGE_MODE === 'ERC_TO_ERC') {
const foreignWeb3 = new Web3(new Web3.providers.HttpProvider(FOREIGN_RPC_URL))
const tokenContract = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ERC20_TOKEN_ADDRESS)
try {
const bridgeContract = await tokenContract.methods.bridgeContract().call()
if (bridgeContract === FOREIGN_BRIDGE_ADDRESS) {
result.foreignERC = ERC_TYPES.ERC677
} else {
result.foreignERC = ERC_TYPES.ERC20
}
} catch (e) {
result.foreignERC = ERC_TYPES.ERC20
}
const bridgeTokenContract = new foreignWeb3.eth.Contract(ERC677_BRIDGE_TOKEN_ABI, ERC20_TOKEN_ADDRESS)
result.foreignERC = await getTokenType(bridgeTokenContract, FOREIGN_BRIDGE_ADDRESS)
}
console.log(JSON.stringify(result))
return result

@ -13,8 +13,7 @@ const {
FOREIGN_TEST_TX_GAS_PRICE
} = process.env
const NUMBER_OF_WITHDRAWALS_TO_SEND =
process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1
const NUMBER_OF_WITHDRAWALS_TO_SEND = process.argv[2] || process.env.NUMBER_OF_WITHDRAWALS_TO_SEND || 1
const ERC677_ABI = [
{

@ -1,28 +1,14 @@
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const logger = require('../../services/logger').child({
module: 'processAffirmationRequests:estimateGas'
})
async function estimateGas({
web3,
homeBridge,
validatorContract,
recipient,
value,
txHash,
address
}) {
async function estimateGas({ web3, homeBridge, validatorContract, recipient, value, txHash, address }) {
try {
const gasEstimate = await homeBridge.methods
.executeAffirmation(recipient, value, txHash)
.estimateGas({
from: address
})
const gasEstimate = await homeBridge.methods.executeAffirmation(recipient, value, txHash).estimateGas({
from: address
})
return gasEstimate
} catch (e) {
@ -36,9 +22,7 @@ async function estimateGas({
// Check if minimum number of validations was already reached
logger.debug('Check if minimum number of validations was already reached')
const numAffirmationsSigned = await homeBridge.methods.numAffirmationsSigned(messageHash).call()
const alreadyProcessed = await homeBridge.methods
.isAlreadyProcessed(numAffirmationsSigned)
.call()
const alreadyProcessed = await homeBridge.methods.isAlreadyProcessed(numAffirmationsSigned).call()
if (alreadyProcessed) {
throw new AlreadyProcessedError(e.message)

@ -7,11 +7,7 @@ const { web3Home } = require('../../services/web3')
const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
@ -40,10 +36,7 @@ function processAffirmationRequestsBuilder(config) {
eventTransactionHash: affirmationRequest.transactionHash
})
logger.info(
{ sender: recipient, value },
`Processing affirmationRequest ${affirmationRequest.transactionHash}`
)
logger.info({ sender: recipient, value }, `Processing affirmationRequest ${affirmationRequest.transactionHash}`)
let gasEstimate
try {
@ -60,9 +53,7 @@ function processAffirmationRequestsBuilder(config) {
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
@ -71,9 +62,7 @@ function processAffirmationRequestsBuilder(config) {
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`affirmationRequest ${
affirmationRequest.transactionHash
} was already processed by other validators`
`affirmationRequest ${affirmationRequest.transactionHash} was already processed by other validators`
)
return
} else {

@ -1,10 +1,6 @@
const Web3 = require('web3')
const { HttpListProviderError } = require('http-list-provider')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const { parseMessage } = require('../../utils/message')
const logger = require('../../services/logger').child({
module: 'processCollectedSignatures:estimateGas'
@ -13,19 +9,9 @@ const logger = require('../../services/logger').child({
const web3 = new Web3()
const { toBN } = Web3.utils
async function estimateGas({
foreignBridge,
validatorContract,
message,
numberOfCollectedSignatures,
v,
r,
s
}) {
async function estimateGas({ foreignBridge, validatorContract, message, numberOfCollectedSignatures, v, r, s }) {
try {
const gasEstimate = await foreignBridge.methods
.executeSignatures(v, r, s, message)
.estimateGas()
const gasEstimate = await foreignBridge.methods.executeSignatures(v, r, s, message).estimateGas()
return gasEstimate
} catch (e) {
if (e instanceof HttpListProviderError) {

@ -6,11 +6,7 @@ const rootLogger = require('../../services/logger')
const { web3Home, web3Foreign } = require('../../services/web3')
const { signatureToVRS } = require('../../utils/message')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
IncompatibleContractError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, IncompatibleContractError, InvalidValidatorError } = require('../../utils/errors')
const { MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const limit = promiseLimit(MAX_CONCURRENT_EVENTS)
@ -20,10 +16,7 @@ let validatorContract = null
function processCollectedSignaturesBuilder(config) {
const homeBridge = new web3Home.eth.Contract(config.homeBridgeAbi, config.homeBridgeAddress)
const foreignBridge = new web3Foreign.eth.Contract(
config.foreignBridgeAbi,
config.foreignBridgeAddress
)
const foreignBridge = new web3Foreign.eth.Contract(config.foreignBridgeAbi, config.foreignBridgeAddress)
return async function processCollectedSignatures(signatures) {
const txToSend = []
@ -33,28 +26,19 @@ function processCollectedSignaturesBuilder(config) {
const validatorContractAddress = await foreignBridge.methods.validatorContract().call()
rootLogger.debug({ validatorContractAddress }, 'Validator contract address obtained')
validatorContract = new web3Foreign.eth.Contract(
BRIDGE_VALIDATORS_ABI,
validatorContractAddress
)
validatorContract = new web3Foreign.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorContractAddress)
}
rootLogger.debug(`Processing ${signatures.length} CollectedSignatures events`)
const callbacks = signatures.map(colSignature =>
limit(async () => {
const {
authorityResponsibleForRelay,
messageHash,
NumberOfCollectedSignatures
} = colSignature.returnValues
const { authorityResponsibleForRelay, messageHash, NumberOfCollectedSignatures } = colSignature.returnValues
const logger = rootLogger.child({
eventTransactionHash: colSignature.transactionHash
})
if (
authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(config.validatorAddress)
) {
if (authorityResponsibleForRelay === web3Home.utils.toChecksumAddress(config.validatorAddress)) {
logger.info(`Processing CollectedSignatures ${colSignature.transactionHash}`)
const message = await homeBridge.methods.message(messageHash).call()
@ -90,16 +74,11 @@ function processCollectedSignaturesBuilder(config) {
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof AlreadyProcessedError) {
logger.info(`Already processed CollectedSignatures ${colSignature.transactionHash}`)
return
} else if (
e instanceof IncompatibleContractError ||
e instanceof InvalidValidatorError
) {
} else if (e instanceof IncompatibleContractError || e instanceof InvalidValidatorError) {
logger.error(`The message couldn't be processed; skipping: ${e.message}`)
return
} else {
@ -115,11 +94,7 @@ function processCollectedSignaturesBuilder(config) {
to: config.foreignBridgeAddress
})
} else {
logger.info(
`Validator not responsible for relaying CollectedSignatures ${
colSignature.transactionHash
}`
)
logger.info(`Validator not responsible for relaying CollectedSignatures ${colSignature.transactionHash}`)
}
})
)

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

@ -6,11 +6,7 @@ const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const { createMessage } = require('../../utils/message')
const estimateGas = require('./estimateGas')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const { VALIDATOR_ADDRESS_PRIVATE_KEY } = process.env
@ -47,10 +43,7 @@ function processSignatureRequestsBuilder(config) {
eventTransactionHash: signatureRequest.transactionHash
})
logger.info(
{ sender: recipient, value },
`Processing signatureRequest ${signatureRequest.transactionHash}`
)
logger.info({ sender: recipient, value }, `Processing signatureRequest ${signatureRequest.transactionHash}`)
const message = createMessage({
recipient,
@ -76,9 +69,7 @@ function processSignatureRequestsBuilder(config) {
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
@ -87,9 +78,7 @@ function processSignatureRequestsBuilder(config) {
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`signatureRequest ${
signatureRequest.transactionHash
} was already processed by other validators`
`signatureRequest ${signatureRequest.transactionHash} was already processed by other validators`
)
return
} else {

@ -4,11 +4,7 @@ const { HttpListProviderError } = require('http-list-provider')
const { BRIDGE_VALIDATORS_ABI } = require('../../../../commons')
const rootLogger = require('../../services/logger')
const { web3Home } = require('../../services/web3')
const {
AlreadyProcessedError,
AlreadySignedError,
InvalidValidatorError
} = require('../../utils/errors')
const { AlreadyProcessedError, AlreadySignedError, InvalidValidatorError } = require('../../utils/errors')
const { EXIT_CODES, MAX_CONCURRENT_EVENTS } = require('../../utils/constants')
const estimateGas = require('../processAffirmationRequests/estimateGas')
@ -56,9 +52,7 @@ function processTransfersBuilder(config) {
logger.debug({ gasEstimate }, 'Gas estimated')
} catch (e) {
if (e instanceof HttpListProviderError) {
throw new Error(
'RPC Connection Error: submitSignature Gas Estimate cannot be obtained.'
)
throw new Error('RPC Connection Error: submitSignature Gas Estimate cannot be obtained.')
} else if (e instanceof InvalidValidatorError) {
logger.fatal({ address: config.validatorAddress }, 'Invalid validator')
process.exit(EXIT_CODES.INCOMPATIBILITY)
@ -66,9 +60,7 @@ function processTransfersBuilder(config) {
logger.info(`Already signed transfer ${transfer.transactionHash}`)
return
} else if (e instanceof AlreadyProcessedError) {
logger.info(
`transfer ${transfer.transactionHash} was already processed by other validators`
)
logger.info(`transfer ${transfer.transactionHash} was already processed by other validators`)
return
} else {
logger.error(e, 'Unknown error while processing transaction')

@ -63,9 +63,7 @@ async function initialize() {
}
function resume(newBalance) {
logger.info(
`Validator balance changed. New balance is ${newBalance}. Resume messages processing.`
)
logger.info(`Validator balance changed. New balance is ${newBalance}. Resume messages processing.`)
initialize()
}
@ -164,9 +162,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry }) {
logger.debug(`Finished processing msg`)
if (insufficientFunds) {
logger.warn(
'Insufficient funds. Stop sending transactions until the account has the minimum balance'
)
logger.warn('Insufficient funds. Stop sending transactions until the account has the minimum balance')
channel.close()
waitForFunds(web3Instance, VALIDATOR_ADDRESS, minimumBalance, resume, logger)
}

@ -59,14 +59,7 @@ function connectSenderToQueue({ queueName, cb }) {
})
}
async function generateRetry({
data,
msgRetries,
channelWrapper,
channel,
queueName,
deadLetterExchange
}) {
async function generateRetry({ data, msgRetries, channelWrapper, channel, queueName, deadLetterExchange }) {
const retries = msgRetries + 1
const delay = getRetrySequence(retries) * 1000
const retryQueue = `${queueName}-retry-${delay}`

@ -1,18 +1,13 @@
require('../../env')
const fetch = require('node-fetch')
const Web3Utils = require('web3-utils')
const { web3Home, web3Foreign } = require('../services/web3')
const { bridgeConfig } = require('../../config/base.config')
const logger = require('../services/logger').child({
module: 'gasPrice'
})
const { setIntervalAndRun } = require('../utils/utils')
const {
DEFAULT_UPDATE_INTERVAL,
GAS_PRICE_BOUNDARIES,
DEFAULT_GAS_PRICE_FACTOR
} = require('../utils/constants')
const { GAS_PRICE_OPTIONS } = require('../../../commons')
const { DEFAULT_UPDATE_INTERVAL, GAS_PRICE_BOUNDARIES, DEFAULT_GAS_PRICE_FACTOR } = require('../utils/constants')
const { gasPriceFromOracle, gasPriceFromContract, GAS_PRICE_OPTIONS } = require('../../../commons')
const HomeABI = bridgeConfig.homeBridgeAbi
const ForeignABI = bridgeConfig.foreignBridgeAbi
@ -39,62 +34,20 @@ const foreignBridge = new web3Foreign.eth.Contract(ForeignABI, FOREIGN_BRIDGE_AD
let cachedGasPrice = null
let cachedGasPriceOracleSpeeds = null
function gasPriceWithinLimits(gasPrice) {
if (gasPrice < GAS_PRICE_BOUNDARIES.MIN) {
return GAS_PRICE_BOUNDARIES.MIN
} else if (gasPrice > GAS_PRICE_BOUNDARIES.MAX) {
return GAS_PRICE_BOUNDARIES.MAX
} else {
return gasPrice
}
}
function normalizeGasPrice(oracleGasPrice, factor) {
const gasPriceGwei = oracleGasPrice * factor
const gasPrice = gasPriceWithinLimits(gasPriceGwei)
return Web3Utils.toWei(gasPrice.toFixed(2).toString(), 'gwei')
}
async function fetchGasPriceFromOracle(oracleUrl, speedType, factor) {
const response = await fetch(oracleUrl)
const json = await response.json()
const oracleGasPrice = json[speedType]
if (!oracleGasPrice) {
throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`)
}
return {
oracleGasPrice: normalizeGasPrice(oracleGasPrice, factor),
oracleResponse: json
}
}
async function fetchGasPrice({ bridgeContract, oracleFn }) {
let gasPrice = null
let oracleGasPriceSpeeds = null
try {
const { oracleGasPrice, oracleResponse } = await oracleFn()
gasPrice = oracleGasPrice
oracleGasPriceSpeeds = oracleResponse
logger.debug({ gasPrice }, 'Gas price updated using the oracle')
} catch (e) {
logger.error(`Gas Price API is not available. ${e.message}`)
try {
gasPrice = await bridgeContract.methods.gasPrice().call()
logger.debug({ gasPrice }, 'Gas price updated using the contracts')
} catch (e) {
logger.error(`There was a problem getting the gas price from the contract. ${e.message}`)
}
}
return {
gasPrice,
oracleGasPriceSpeeds
}
}
let fetchGasPriceInterval = null
const fetchGasPrice = async (speedType, factor, bridgeContract, oracleFetchFn) => {
const contractOptions = { logger }
const oracleOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger, returnAllSpeeds: true }
const { gasPrice, oracleGasPriceSpeeds } = await gasPriceFromOracle(oracleFetchFn, oracleOptions)
cachedGasPrice =
gasPrice ||
(await gasPriceFromContract(bridgeContract, contractOptions)) ||
cachedGasPrice
cachedGasPriceOracleSpeeds = oracleGasPriceSpeeds || cachedGasPriceOracleSpeeds
return cachedGasPrice
}
async function start(chainId) {
clearInterval(fetchGasPriceInterval)
@ -123,14 +76,10 @@ async function start(chainId) {
throw new Error(`Unrecognized chainId '${chainId}'`)
}
fetchGasPriceInterval = setIntervalAndRun(async () => {
const { gasPrice, oracleGasPriceSpeeds } = await fetchGasPrice({
bridgeContract,
oracleFn: () => fetchGasPriceFromOracle(oracleUrl, speedType, factor)
})
cachedGasPrice = gasPrice || cachedGasPrice
cachedGasPriceOracleSpeeds = oracleGasPriceSpeeds || cachedGasPriceOracleSpeeds
}, updateInterval)
fetchGasPriceInterval = setIntervalAndRun(
() => fetchGasPrice(speedType, factor, bridgeContract, () => fetch(oracleUrl)),
updateInterval
)
}
function getPrice(options) {
@ -152,9 +101,7 @@ function processGasPriceOptions({ options, cachedGasPrice, cachedGasPriceOracleS
module.exports = {
start,
fetchGasPrice,
getPrice,
processGasPriceOptions,
gasPriceWithinLimits,
normalizeGasPrice
fetchGasPrice
}

@ -1,8 +1,7 @@
const pino = require('pino')
const path = require('path')
const config =
process.env.NODE_ENV !== 'test' ? require(path.join('../../config/', process.argv[2])) : {}
const config = process.env.NODE_ENV !== 'test' ? require(path.join('../../config/', process.argv[2])) : {}
const logger = pino({
enabled: process.env.NODE_ENV !== 'test',

@ -3,18 +3,7 @@ const fetch = require('node-fetch')
const rpcUrlsManager = require('../services/getRpcUrlsManager')
// eslint-disable-next-line consistent-return
async function sendTx({
chain,
privateKey,
data,
nonce,
gasPrice,
amount,
gasLimit,
to,
chainId,
web3
}) {
async function sendTx({ chain, privateKey, data, nonce, gasPrice, amount, gasLimit, to, chainId, web3 }) {
const serializedTx = await web3.eth.accounts.signTransaction(
{
nonce: Number(nonce),

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