Merge the develop branch to the master branch, preparation to v3.0.0-rc1

This merge contains the following set of changes:
  * [Monitor, Improvement] Add statistics about used AMB information requests (#577)
  * [ALM, Improvement] Add safe-execute button in ALM (#580), closes #573, closes #551
  * [Oracle, Improvement] Improve confirm-relay feature (#582), closes #569
  * [Monitor, Fix] Prune print of long error messages about missing file (#579), closes #578
  * [Oracle, Fix] Use safe approach for eth_getLogs requests (#581)
  * [Oracle, Fix] Fix logging in gas price service (#583), closes #552
  * [Oracle, Fix] Fix oracle error patterns and oracle e2e tests (#585)
  * [Common, Other] Fix dependencies versions and update example.yml (#566)
  * [Common, Other] Upload services logs in e2e and ultimate tests (#568)
  * [Oracle, Other] added example of emergency shutdown controller contract (#572)
  * [Oracle, Other] Refactor oracle configuration (#584)
This commit is contained in:
Alexander Kolotov 2021-07-10 10:10:22 +03:00 committed by GitHub
commit c92f80c484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 950 additions and 2458 deletions

@ -149,6 +149,12 @@ jobs:
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }} run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: yarn run ${{ matrix.task }} - name: yarn run ${{ matrix.task }}
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }} run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@v2
with:
name: logs-${{ matrix.task }}
path: e2e-commons/logs
deployment: deployment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
@ -199,7 +205,7 @@ jobs:
- name: Login to docker registry - name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }} run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: Deploy contracts - name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
- name: Pull e2e oracle image - name: Pull e2e oracle image
run: | run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb
@ -210,3 +216,12 @@ jobs:
run: sudo chown -R $USER:docker /var/run/docker.sock run: sudo chown -R $USER:docker /var/run/docker.sock
- name: Run oracle e2e tests - name: Run oracle e2e tests
run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }} run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}
- name: Save logs
if: always()
run: e2e-commons/down.sh
- name: Upload logs
if: always()
uses: actions/upload-artifact@v2
with:
name: logs-ultimate-${{ matrix.task }}
path: e2e-commons/logs

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

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

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

@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size' import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants' import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
@ -9,6 +9,7 @@ import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table' import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton' import { ManualExecutionButton } from './ManualExecutionButton'
import { useStateProvider } from '../state/StateProvider'
const StyledExecutionConfirmation = styled.div` const StyledExecutionConfirmation = styled.div`
margin-top: 30px; margin-top: 30px;
@ -33,6 +34,8 @@ export const ExecutionConfirmation = ({
executionEventsFetched, executionEventsFetched,
setPendingExecution setPendingExecution
}: ExecutionConfirmationParams) => { }: ExecutionConfirmationParams) => {
const { foreign } = useStateProvider()
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
const availableManualExecution = const availableManualExecution =
!isHome && !isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING || (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
@ -48,6 +51,22 @@ export const ExecutionConfirmation = ({
const formattedValidator = const formattedValidator =
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
useEffect(
() => {
if (!availableManualExecution || !foreign.bridgeContract) return
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
p.then(({ major, minor }: any) => {
major = parseInt(major, 10)
minor = parseInt(minor, 10)
if (major < 5 || (major === 5 && minor < 7)) return
setSafeExecutionAvailable(true)
})
},
[availableManualExecution, foreign.bridgeContract]
)
const getExecutionStatusElement = (validatorStatus = '') => { const getExecutionStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
@ -105,6 +124,7 @@ export const ExecutionConfirmation = ({
{availableManualExecution && ( {availableManualExecution && (
<td> <td>
<ManualExecutionButton <ManualExecutionButton
safeExecutionAvailable={safeExecutionAvailable}
messageData={messageData} messageData={messageData}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]} signatureCollected={signatureCollected as string[]}

@ -15,16 +15,19 @@ import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent' import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
const StyledButton = styled.button` const ActionButton = styled.button`
color: var(--button-color); color: var(--button-color);
border-color: var(--font-color); border-color: var(--font-color);
margin-top: 10px; margin-top: 10px;
min-width: 120px;
padding: 1rem;
&:focus { &:focus {
outline: var(--button-color); outline: var(--button-color);
} }
` `
interface ManualExecutionButtonParams { interface ManualExecutionButtonParams {
safeExecutionAvailable: boolean
messageData: string messageData: string
setExecutionData: Function setExecutionData: Function
signatureCollected: string[] signatureCollected: string[]
@ -32,6 +35,7 @@ interface ManualExecutionButtonParams {
} }
export const ManualExecutionButton = ({ export const ManualExecutionButton = ({
safeExecutionAvailable,
messageData, messageData,
setExecutionData, setExecutionData,
signatureCollected, signatureCollected,
@ -40,6 +44,7 @@ export const ManualExecutionButton = ({
const { foreign, setError } = useStateProvider() const { foreign, setError } = useStateProvider()
const { library, activate, account, active } = useWeb3React() const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false) const [manualExecution, setManualExecution] = useState(false)
const [allowFailures, setAllowFailures] = useState(false)
useEffect( useEffect(
() => { () => {
@ -72,7 +77,11 @@ export const ManualExecutionButton = ({
const signatures = packSignatures(signatureCollected.map(signatureToVRS)) const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const messageId = messageData.slice(0, 66) const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI() const executeMethod =
safeExecutionAvailable && !allowFailures
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
: bridge.methods.executeSignatures
const data = executeMethod(messageData, signatures).encodeABI()
setManualExecution(false) setManualExecution(false)
library.eth library.eth
@ -132,15 +141,35 @@ export const ManualExecutionButton = ({
messageData, messageData,
signatureCollected, signatureCollected,
setExecutionData, setExecutionData,
setPendingExecution setPendingExecution,
safeExecutionAvailable,
allowFailures
] ]
) )
return ( return (
<div>
<div className="is-center"> <div className="is-center">
<StyledButton className="button outline" onClick={() => setManualExecution(true)}> <ActionButton className="button outline" onClick={() => setManualExecution(true)}>
Execute Execute
</StyledButton> </ActionButton>
</div>
{safeExecutionAvailable && (
<div
title="Allow executed message to fail and record its failure on-chain without reverting the whole transaction.
Use fixed gas limit for execution."
className="is-center"
style={{ paddingTop: 10 }}
>
<input
type="checkbox"
id="allow-failures"
checked={allowFailures}
onChange={e => setAllowFailures(e.target.checked)}
/>
<label htmlFor="allow-failures">Unsafe mode</label>
</div>
)}
</div> </div>
) )
} }

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

@ -9,7 +9,8 @@
}, },
"dependencies": { "dependencies": {
"gas-price-oracle": "^0.1.5", "gas-price-oracle": "^0.1.5",
"web3-utils": "^1.3.0" "web3-utils": "^1.3.0",
"node-fetch": "^2.1.2"
}, },
"devDependencies": { "devDependencies": {
"bn-chai": "^1.0.1", "bn-chai": "^1.0.1",

@ -1,5 +1,6 @@
const { toWei, toBN, BN } = require('web3-utils') const { toWei, toBN, BN } = require('web3-utils')
const { GasPriceOracle } = require('gas-price-oracle') const { GasPriceOracle } = require('gas-price-oracle')
const fetch = require('node-fetch')
const { BRIDGE_MODES } = require('./constants') const { BRIDGE_MODES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis') const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
@ -178,17 +179,16 @@ const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei')) return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
} }
// fetchFn has to be supplied (instead of just url to oracle), const gasPriceFromSupplier = async (url, options = {}) => {
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
try { try {
let json let json
if (fetchFn) { if (url === 'gas-price-oracle') {
const response = await fetchFn() json = await gasPriceOracle.fetchGasPricesOffChain()
} else if (url) {
const response = await fetch(url, { timeout: 2000 })
json = await response.json() json = await response.json()
} else { } else {
json = await gasPriceOracle.fetchGasPricesOffChain() return null
} }
const oracleGasPrice = json[options.speedType] const oracleGasPrice = json[options.speedType]

@ -34,7 +34,7 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
multiple-host: multiple-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
MONITOR_PORT: 3003 MONITOR_PORT: 3003
syslog_server_port: "udp://127.0.0.1:514" syslog_server_port: "udp://127.0.0.1:514"
verifier: verifier:

@ -33,7 +33,7 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
oracle-host: oracle-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
syslog_server_port: "udp://127.0.0.1:514" syslog_server_port: "udp://127.0.0.1:514"
verifier: verifier:
name: testinfra name: testinfra

@ -5,13 +5,13 @@ ORACLE_LOG_LEVEL: debug
## Home contract ## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network" COMMON_HOME_RPC_URL: "https://sokol.poa.network"
COMMON_HOME_BRIDGE_ADDRESS: "0x98aFdE294f1C46aA0a27Cc4049ED337F879d8976" COMMON_HOME_BRIDGE_ADDRESS: "0x59ba90A588ce732AB33FD32Aab1b58c21400A0f6"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000 ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract ## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network" COMMON_FOREIGN_RPC_URL: "https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x5a584f4C30B36f282848dAc9a2b20E7BEF481981" COMMON_FOREIGN_BRIDGE_ADDRESS: "0xdA4a49a00F4fF4A5988b9AceE95f99e3b2c208b6"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000 ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
## Home Gasprice ## Home Gasprice
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/" COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
@ -31,8 +31,8 @@ ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
MONITOR_BRIDGE_NAME: "bridge" MONITOR_BRIDGE_NAME: "bridge"
MONITOR_PORT: 3003 MONITOR_PORT: 3003
MONITOR_CACHE_EVENTS: "false" MONITOR_CACHE_EVENTS: "false"
MONITOR_HOME_START_BLOCK: 0 MONITOR_HOME_START_BLOCK: 20821049
MONITOR_FOREIGN_START_BLOCK: 0 MONITOR_FOREIGN_START_BLOCK: 24773297
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000 MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000 MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100 MONITOR_TX_NUMBER_THRESHOLD: 100

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

@ -6,14 +6,12 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545 COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C COMMON_HOME_BRIDGE_ADDRESS=0x8397be90BCF57b0B71219f555Fe121b22e5a994C
COMMON_FOREIGN_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E COMMON_FOREIGN_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b COMMON_HOME_GAS_PRICE_SUPPLIER_URL=
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1000000000 COMMON_HOME_GAS_PRICE_FALLBACK=1000000000
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000 ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL=600000
COMMON_HOME_GAS_PRICE_FACTOR=1 COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=10000000000 COMMON_FOREIGN_GAS_PRICE_FALLBACK=10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000 ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000

@ -6,8 +6,6 @@ COMMON_HOME_RPC_URL=http://parity1:8545
COMMON_FOREIGN_RPC_URL=http://parity2:8545 COMMON_FOREIGN_RPC_URL=http://parity2:8545
COMMON_HOME_BRIDGE_ADDRESS=0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c COMMON_HOME_BRIDGE_ADDRESS=0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c
COMMON_FOREIGN_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624 COMMON_FOREIGN_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624
ORACLE_VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/ COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=1 COMMON_HOME_GAS_PRICE_FALLBACK=1

@ -49,6 +49,12 @@
"blockedHomeBox": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977", "blockedHomeBox": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977",
"monitor": "http://monitor-amb:3013/bridge" "monitor": "http://monitor-amb:3013/bridge"
}, },
"amb2": {
"home": "0x5A42E119990c3F3A80Fea20aAF4c3Ff4DB240Cc9",
"foreign": "0x897527391ad3837604973d78D3514f44c36AB9FC",
"homeBox": "0xb008E9076fCbDB2C3AF84225Bc07Eb35B2bE5ECD",
"foreignBox": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
},
"homeRPC": { "homeRPC": {
"URL": "http://parity1:8545", "URL": "http://parity1:8545",
"ID": "77" "ID": "77"

@ -1,7 +1,33 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd $(dirname $0) cd $(dirname $0)
if [ $CI ]; then exit $rc; fi if [ $CI ]; then
rm -rf logs || true
mkdir ./logs
for project in "" validator{1,2,3}; do
for container in $(docker-compose -p "$project" ps | tail -n +3 | awk '{print $1}') ; do
if [[ -z "$project" ]]; then
path="./logs/$container.log"
else
mkdir -p "./logs/$project"
path="./logs/$project/$container.log"
fi
docker logs "$container" > "$path" 2>&1
done
done
touch ../oracle/.env
for file in ../oracle/docker-compose-{amb,transfer}.yml; do
for container in $(docker-compose -f "$file" ps | tail -n +3 | awk '{print $1}') ; do
mkdir -p "./logs/oracle"
docker logs "$container" > "./logs/oracle/$container.log" 2>&1
done
done
exit $rc;
fi
ps | grep node | grep -v grep | grep -v yarn | awk '{print "kill " $1}' | /bin/bash ps | grep node | grep -v grep | grep -v yarn | awk '{print "kill " $1}' | /bin/bash
docker-compose down docker-compose down

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

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

@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "mocha --timeout 120000", "start": "mocha --timeout 120000 --exit",
"lint": "eslint . --ignore-path ../.eslintignore" "lint": "eslint . --ignore-path ../.eslintignore"
}, },
"author": "", "author": "",

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

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

@ -129,6 +129,27 @@ async function main(mode) {
})).map(normalizeEvent) })).map(normalizeEvent)
foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew] foreignToHomeRequests = [...foreignToHomeRequests, ...foreignToHomeRequestsNew]
let informationRequests
let informationResponses
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
logger.debug("calling homeBridge.getPastEvents('UserRequestForInformation')")
informationRequests = (await getPastEvents(homeBridge, {
event: 'UserRequestForInformation',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
logger.debug("calling foreignBridge.getPastEvents('InformationRetrieved')")
informationResponses = (await getPastEvents(homeBridge, {
event: 'InformationRetrieved',
fromBlock: MONITOR_HOME_START_BLOCK,
toBlock: homeBlockNumber,
safeToBlock: homeDelayedBlockNumber,
chain: 'home'
})).map(normalizeEvent)
}
if (isExternalErc20) { if (isExternalErc20) {
logger.debug("calling erc20Contract.getPastEvents('Transfer')") logger.debug("calling erc20Contract.getPastEvents('Transfer')")
let transferEvents = (await getPastEvents(erc20Contract, { let transferEvents = (await getPastEvents(erc20Contract, {
@ -225,6 +246,8 @@ async function main(mode) {
homeToForeignConfirmations, homeToForeignConfirmations,
foreignToHomeConfirmations, foreignToHomeConfirmations,
foreignToHomeRequests, foreignToHomeRequests,
informationRequests,
informationResponses,
isExternalErc20, isExternalErc20,
bridgeMode, bridgeMode,
homeBlockNumber, homeBlockNumber,

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

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

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

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

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

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

@ -2,7 +2,7 @@ const Web3 = require('web3')
const assert = require('assert') const assert = require('assert')
const { user, homeRPC, foreignRPC, amb, validator } = require('../../e2e-commons/constants.json') const { user, homeRPC, foreignRPC, amb, validator } = require('../../e2e-commons/constants.json')
const { uniformRetry } = require('../../e2e-commons/utils') const { uniformRetry } = require('../../e2e-commons/utils')
const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI } = require('../../commons') const { BOX_ABI, HOME_AMB_ABI, FOREIGN_AMB_ABI, ambInformationSignatures } = require('../../commons')
const { delay, setRequiredSignatures } = require('./utils') const { delay, setRequiredSignatures } = require('./utils')
const { toBN } = Web3.utils const { toBN } = Web3.utils
@ -29,24 +29,7 @@ const foreignBridge = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, amb.foreign,
describe('arbitrary message bridging', () => { describe('arbitrary message bridging', () => {
let requiredSignatures = 1 let requiredSignatures = 1
before(async () => { before(async () => {
const allowedMethods = [ for (const method of ambInformationSignatures) {
'eth_call(address,bytes)',
'eth_call(address,bytes,uint256)',
'eth_call(address,address,uint256,bytes)',
'eth_blockNumber()',
'eth_getBlockByNumber()',
'eth_getBlockByNumber(uint256)',
'eth_getBlockByHash(bytes32)',
'eth_getBalance(address)',
'eth_getBalance(address,uint256)',
'eth_getTransactionCount(address)',
'eth_getTransactionCount(address,uint256)',
'eth_getTransactionByHash(bytes32)',
'eth_getTransactionReceipt(bytes32)',
'eth_getStorageAt(address,bytes32)',
'eth_getStorageAt(address,bytes32,uint256)'
]
for (const method of allowedMethods) {
const selector = homeWeb3.utils.soliditySha3(method) const selector = homeWeb3.utils.soliditySha3(method)
await homeBridge.methods.enableAsyncRequestSelector(selector, true).send({ from: validator.address }) await homeBridge.methods.enableAsyncRequestSelector(selector, true).send({ from: validator.address })
} }
@ -80,6 +63,19 @@ describe('arbitrary message bridging', () => {
} }
}) })
}) })
if (process.env.ULTIMATE !== 'true') {
describe('Confirm Relay', () => {
it('should process lost affirmation-request via confirm relay', async () => {
const value = await homeBox.methods.value().call()
assert(value === '789', 'incorrect value')
})
it('should process lost signature-request & collected-signatures via confirm relay', async () => {
const value = await foreignBox.methods.value().call()
assert(value === '123', 'incorrect value')
})
})
}
describe('Home to Foreign', () => { describe('Home to Foreign', () => {
describe('Subsidized Mode', () => { describe('Subsidized Mode', () => {
it('should bridge message', async () => { it('should bridge message', async () => {
@ -88,12 +84,13 @@ describe('arbitrary message bridging', () => {
const initialValue = await foreignBox.methods.value().call() const initialValue = await foreignBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value') assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
await homeBox.methods const res = await homeBox.methods
.setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox) .setValueOnOtherNetwork(newValue, amb.home, amb.foreignBox)
.send() .send()
.catch(e => { .catch(e => {
console.error(e) console.error(e)
}) })
console.log(res.transactionHash)
// check that value changed and balance decreased // check that value changed and balance decreased
await uniformRetry(async retry => { await uniformRetry(async retry => {
@ -186,12 +183,13 @@ describe('arbitrary message bridging', () => {
const initialValue = await homeBox.methods.value().call() const initialValue = await homeBox.methods.value().call()
assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value') assert(!toBN(initialValue).eq(toBN(newValue)), 'initial value should be different from new value')
await foreignBox.methods const res = await foreignBox.methods
.setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox) .setValueOnOtherNetwork(newValue, amb.foreign, amb.homeBox)
.send() .send()
.catch(e => { .catch(e => {
console.error(e) console.error(e)
}) })
console.log(res.transactionHash)
// check that value changed and balance decreased // check that value changed and balance decreased
await uniformRetry(async retry => { await uniformRetry(async retry => {
@ -285,7 +283,7 @@ describe('arbitrary message bridging', () => {
const selector = homeWeb3.utils.soliditySha3('eth_call(address,bytes,uint256)') const selector = homeWeb3.utils.soliditySha3('eth_call(address,bytes,uint256)')
const data1 = homeWeb3.eth.abi.encodeParameters( const data1 = homeWeb3.eth.abi.encodeParameters(
['address', 'bytes', 'uint256'], ['address', 'bytes', 'uint256'],
[amb.foreignBox, foreignBox.methods.value().encodeABI(), 60] [amb.foreignBox, foreignBox.methods.value().encodeABI(), 25]
) )
const data2 = homeWeb3.eth.abi.encodeParameters( const data2 = homeWeb3.eth.abi.encodeParameters(
['address', 'bytes', 'uint256'], ['address', 'bytes', 'uint256'],
@ -463,7 +461,7 @@ describe('arbitrary message bridging', () => {
}) })
it('should make async eth_getTransactionByHash', async () => { it('should make async eth_getTransactionByHash', async () => {
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6' const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const tx = await foreignWeb3.eth.getTransaction(txHash) const tx = await foreignWeb3.eth.getTransaction(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionByHash(bytes32)') const selector = homeWeb3.utils.soliditySha3('eth_getTransactionByHash(bytes32)')
@ -496,7 +494,7 @@ describe('arbitrary message bridging', () => {
}) })
it('should make async eth_getTransactionReceipt', async () => { it('should make async eth_getTransactionReceipt', async () => {
const txHash = '0x09dfb947dbd17e27bcc117773b6e133829f7cef9646199a93ef019c4f7c0fec6' const txHash = '0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
const receipt = await foreignWeb3.eth.getTransactionReceipt(txHash) const receipt = await foreignWeb3.eth.getTransactionReceipt(txHash)
const selector = homeWeb3.utils.soliditySha3('eth_getTransactionReceipt(bytes32)') const selector = homeWeb3.utils.soliditySha3('eth_getTransactionReceipt(bytes32)')

@ -100,6 +100,8 @@ describe('erc to native', () => {
const transferValue = homeWeb3.utils.toWei('0.05') const transferValue = homeWeb3.utils.toWei('0.05')
// transfer that should not be processed by the filter
await erc20Token.methods.transfer(secondUser.address, transferValue).send({ from: user.address, gas: 100000 })
// send tokens to foreign bridge // send tokens to foreign bridge
await erc20Token.methods await erc20Token.methods
.transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue) .transfer(COMMON_FOREIGN_BRIDGE_ADDRESS, transferValue)

@ -1 +1 @@
--timeout 120000 --timeout 120000 --exit

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

@ -8,7 +8,8 @@ const {
FOREIGN_AMB_ABI FOREIGN_AMB_ABI
} = require('../../commons') } = require('../../commons')
const { web3Home, web3Foreign } = require('../src/services/web3') const { web3Home, web3Foreign } = require('../src/services/web3')
const { privateKeyToAddress } = require('../src/utils/utils') const { add0xPrefix, privateKeyToAddress } = require('../src/utils/utils')
const { EXIT_CODES } = require('../src/utils/constants')
const { const {
ORACLE_BRIDGE_MODE, ORACLE_BRIDGE_MODE,
@ -79,9 +80,22 @@ const foreignConfig = {
const maxProcessingTime = const maxProcessingTime =
parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval) parseInt(ORACLE_MAX_PROCESSING_TIME, 10) || 4 * Math.max(homeConfig.pollingInterval, foreignConfig.pollingInterval)
let validatorPrivateKey
if (ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY) {
validatorPrivateKey = add0xPrefix(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
const derived = privateKeyToAddress(validatorPrivateKey)
if (ORACLE_VALIDATOR_ADDRESS && derived.toLowerCase() !== ORACLE_VALIDATOR_ADDRESS.toLowerCase()) {
console.error(
`Derived address from private key - ${derived} is different from ORACLE_VALIDATOR_ADDRESS=${ORACLE_VALIDATOR_ADDRESS}`
)
process.exit(EXIT_CODES.INCOMPATIBILITY)
}
}
module.exports = { module.exports = {
eventFilter: {}, eventFilter: {},
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY), validatorPrivateKey,
validatorAddress: ORACLE_VALIDATOR_ADDRESS || privateKeyToAddress(validatorPrivateKey),
maxProcessingTime, maxProcessingTime,
shutdownKey: 'oracle-shutdown', shutdownKey: 'oracle-shutdown',
home: homeConfig, home: homeConfig,

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

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

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

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

52
oracle/esController.sol Normal file

@ -0,0 +1,52 @@
//SPDX-License-Identifier: GPL-3.0-only
/*
This contract can be used together with the emergency shutdown
functionality of the TokenBridge oracles.
*/
pragma solidity 0.7.6;
contract PauseController {
address public manager;
bool internal paused;
bytes32 private immutable ID;
constructor (string memory _id, address _manager) {
require(bytes(_id).length <= 32);
bytes32 id;
assembly {
id := mload(add(_id, 32))
}
ID = id;
manager = _manager;
}
modifier onlyManager() {
require(msg.sender == manager);
_;
}
function changeManager(address _newmanager) external onlyManager {
require(_newmanager != address(0));
manager = _newmanager;
}
function pause() external onlyManager {
paused = true;
}
function play() external onlyManager {
paused = false;
}
function isPaused() external view returns(bool) {
return paused;
}
function id() external view returns(string memory) {
return string(abi.encodePacked(ID));
}
}

@ -13,6 +13,10 @@
"sender:home": "./scripts/start-worker.sh sender home-sender", "sender:home": "./scripts/start-worker.sh sender home-sender",
"sender:foreign": "./scripts/start-worker.sh sender foreign-sender", "sender:foreign": "./scripts/start-worker.sh sender foreign-sender",
"confirm:transfer": "./scripts/start-worker.sh confirmRelay transfer-watcher", "confirm:transfer": "./scripts/start-worker.sh confirmRelay transfer-watcher",
"confirm:affirmation-request": "./scripts/start-worker.sh confirmRelay affirmation-request-watcher",
"confirm:signature-request": "./scripts/start-worker.sh confirmRelay signature-request-watcher",
"confirm:collected-signatures": "./scripts/start-worker.sh confirmRelay collected-signatures-watcher",
"confirm:information-request": "./scripts/start-worker.sh confirmRelay information-request-watcher",
"manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager", "manager:shutdown": "./scripts/start-worker.sh shutdownManager shutdown-manager",
"dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'", "dev": "concurrently -n 'watcher:signature-request,watcher:collected-signatures,watcher:affirmation-request,watcher:transfer, sender:home,sender:foreign' -c 'red,green,yellow,blue,magenta,cyan' 'yarn watcher:signature-request' 'yarn watcher:collected-signatures' 'yarn watcher:affirmation-request' 'yarn watcher:transfer' 'yarn sender:home' 'yarn sender:foreign'",
"test": "NODE_ENV=test mocha", "test": "NODE_ENV=test mocha",

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

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

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

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

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

@ -10,18 +10,18 @@ const { getNonce, getChainId } = require('./tx/web3')
const { const {
addExtraGas, addExtraGas,
checkHTTPS, checkHTTPS,
privateKeyToAddress,
syncForEach, syncForEach,
waitForFunds, waitForFunds,
waitForUnsuspend, waitForUnsuspend,
watchdog, watchdog,
nonceError isGasPriceError,
isSameTransactionError,
isInsufficientBalanceError,
isNonceError
} = require('./utils/utils') } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants') const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants')
const { ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, ORACLE_TX_REDUNDANCY } = process.env const { ORACLE_TX_REDUNDANCY } = process.env
const ORACLE_VALIDATOR_ADDRESS = privateKeyToAddress(ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY)
if (process.argv.length < 3) { if (process.argv.length < 3) {
logger.error('Please check the number of arguments, config file was not provided') logger.error('Please check the number of arguments, config file was not provided')
@ -40,7 +40,7 @@ async function initialize() {
try { try {
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(config.chain)) web3.currentProvider.subProvider.urls.forEach(checkHttps(config.id))
GasPrice.start(config.id) GasPrice.start(config.id)
@ -84,7 +84,7 @@ async function readNonce(forceUpdate) {
logger.debug('Reading nonce') logger.debug('Reading nonce')
if (forceUpdate) { if (forceUpdate) {
logger.debug('Forcing update of nonce') logger.debug('Forcing update of nonce')
return getNonce(web3, ORACLE_VALIDATOR_ADDRESS) return getNonce(web3, config.validatorAddress)
} }
const nonce = await redis.get(nonceKey) const nonce = await redis.get(nonceKey)
@ -93,7 +93,7 @@ async function readNonce(forceUpdate) {
return Number(nonce) return Number(nonce)
} else { } else {
logger.warn("Nonce wasn't found in the DB") logger.warn("Nonce wasn't found in the DB")
return getNonce(web3, ORACLE_VALIDATOR_ADDRESS) return getNonce(web3, config.validatorAddress)
} }
} }
@ -168,7 +168,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
gasPrice, gasPrice,
amount: '0', amount: '0',
gasLimit, gasLimit,
privateKey: ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY, privateKey: config.validatorPrivateKey,
to: job.to, to: job.to,
chainId, chainId,
web3: web3Redundant web3: web3Redundant
@ -192,12 +192,11 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
e.message e.message
) )
const message = e.message.toLowerCase() if (isGasPriceError(e)) {
if (message.includes('replacement transaction underpriced')) {
logger.info('Replacement transaction underpriced, forcing gas price update') logger.info('Replacement transaction underpriced, forcing gas price update')
GasPrice.start(config.id) GasPrice.start(config.id)
failedTx.push(job) failedTx.push(job)
} else if (isResend || message.includes('transaction with the same hash was already imported')) { } else if (isResend || isSameTransactionError(e)) {
resendJobs.push(job) resendJobs.push(job)
} else { } else {
// if initial transaction sending has failed not due to the same hash error // if initial transaction sending has failed not due to the same hash error
@ -206,14 +205,14 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
failedTx.push(job) failedTx.push(job)
} }
if (message.includes('insufficient funds')) { if (isInsufficientBalanceError(e)) {
insufficientFunds = true insufficientFunds = true
const currentBalance = await web3.eth.getBalance(ORACLE_VALIDATOR_ADDRESS) const currentBalance = await web3.eth.getBalance(config.validatorAddress)
minimumBalance = gasLimit.multipliedBy(gasPrice) minimumBalance = gasLimit.multipliedBy(gasPrice)
logger.error( logger.error(
`Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.` `Insufficient funds: ${currentBalance}. Stop processing messages until the balance is at least ${minimumBalance}.`
) )
} else if (nonceError(e)) { } else if (isNonceError(e)) {
nonce = await readNonce(true) nonce = await readNonce(true)
} }
} }
@ -238,7 +237,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
if (insufficientFunds) { 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() channel.close()
waitForFunds(web3, ORACLE_VALIDATOR_ADDRESS, minimumBalance, resume, logger) waitForFunds(web3, config.validatorAddress, minimumBalance, resume, logger)
} }
} catch (e) { } catch (e) {
logger.error(e) logger.error(e)

@ -2,6 +2,8 @@ const fetch = require('node-fetch')
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants') const { FALLBACK_RPC_URL_SWITCH_TIMEOUT } = require('../utils/constants')
const { onInjected } = require('./injectedLogger')
// From EIP-1474 and Infura documentation // From EIP-1474 and Infura documentation
const JSONRPC_ERROR_CODES = [-32603, -32002, -32005] const JSONRPC_ERROR_CODES = [-32603, -32002, -32005]
@ -33,14 +35,10 @@ function HttpListProvider(urls, options = {}) {
this.options = { ...defaultOptions, ...options } this.options = { ...defaultOptions, ...options }
this.currentIndex = 0 this.currentIndex = 0
this.lastTimeUsedPrimary = 0 this.lastTimeUsedPrimary = 0
this.logger = {
debug: () => {},
info: () => {}
}
}
HttpListProvider.prototype.setLogger = function(logger) { onInjected(logger => {
this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` }) this.logger = logger.child({ module: `HttpListProvider:${this.options.name}` })
})
} }
HttpListProvider.prototype.send = async function send(payload, callback) { HttpListProvider.prototype.send = async function send(payload, callback) {

@ -1,6 +1,7 @@
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const { promiseAny } = require('../utils/utils') const { promiseAny } = require('../utils/utils')
const { defaultOptions, HttpListProviderError, send } = require('./HttpListProvider') const { defaultOptions, HttpListProviderError, send } = require('./HttpListProvider')
const { onInjected } = require('./injectedLogger')
function RedundantHttpListProvider(urls, options = {}) { function RedundantHttpListProvider(urls, options = {}) {
if (!(this instanceof RedundantHttpListProvider)) { if (!(this instanceof RedundantHttpListProvider)) {
@ -13,10 +14,9 @@ function RedundantHttpListProvider(urls, options = {}) {
this.urls = urls this.urls = urls
this.options = { ...defaultOptions, ...options } this.options = { ...defaultOptions, ...options }
} onInjected(logger => {
RedundantHttpListProvider.prototype.setLogger = function(logger) {
this.logger = logger.child({ module: `RedundantHttpListProvider:${this.options.name}` }) this.logger = logger.child({ module: `RedundantHttpListProvider:${this.options.name}` })
})
} }
RedundantHttpListProvider.prototype.send = async function send(payload, callback) { RedundantHttpListProvider.prototype.send = async function send(payload, callback) {

@ -0,0 +1,41 @@
const { hexToNumber, isHexStrict } = require('web3').utils
const { onInjected } = require('./injectedLogger')
function SafeEthLogsProvider(provider) {
this.subProvider = provider
onInjected(logger => {
this.logger = logger.child({ module: 'SafeEthLogsProvider' })
})
}
SafeEthLogsProvider.prototype.send = function send(payload, callback) {
if (payload.method === 'eth_getLogs' && isHexStrict(payload.params[0].toBlock)) {
this.logger.debug('Modifying eth_getLogs request to include batch eth_blockNumber request')
const newPayload = [payload, { jsonrpc: '2.0', id: payload.id + 1, method: 'eth_blockNumber', params: [] }]
this.subProvider.send(newPayload, (err, res) => {
if (err) {
callback(err, null)
} else {
const rawLogs = res.find(({ id }) => id === payload.id)
const rawBlockNumber = res.find(({ id }) => id === payload.id + 1)
const blockNumber = hexToNumber(rawBlockNumber.result)
const toBlock = hexToNumber(payload.params[0].toBlock)
if (blockNumber < toBlock) {
this.logger.warn({ toBlock, blockNumber }, 'Returned block number is less than the specified toBlock')
callback(new Error('block number too low'), null)
} else {
callback(null, rawLogs)
}
}
})
} else {
this.subProvider.send(payload, callback)
}
}
module.exports = {
SafeEthLogsProvider
}

@ -1,5 +1,4 @@
require('../../env') require('../../env')
const fetch = require('node-fetch')
const { home, foreign } = require('../../config/base.config') const { home, foreign } = require('../../config/base.config')
const logger = require('../services/logger').child({ const logger = require('../services/logger').child({
module: 'gasPrice' module: 'gasPrice'
@ -25,11 +24,11 @@ let cachedGasPrice = null
let fetchGasPriceInterval = null let fetchGasPriceInterval = null
const fetchGasPrice = async (speedType, factor, bridgeContract, gasPriceSupplierFetchFn) => { const fetchGasPrice = async (speedType, factor, bridgeContract, gasPriceSupplierUrl) => {
const contractOptions = { logger } const contractOptions = { logger }
const supplierOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger } const supplierOptions = { speedType, factor, limits: GAS_PRICE_BOUNDARIES, logger }
cachedGasPrice = cachedGasPrice =
(await gasPriceFromSupplier(gasPriceSupplierFetchFn, supplierOptions)) || (await gasPriceFromSupplier(gasPriceSupplierUrl, supplierOptions)) ||
(await gasPriceFromContract(bridgeContract, contractOptions)) || (await gasPriceFromContract(bridgeContract, contractOptions)) ||
cachedGasPrice cachedGasPrice
return cachedGasPrice return cachedGasPrice
@ -63,16 +62,15 @@ async function start(chainId, fetchOnce) {
throw new Error(`Unrecognized chainId '${chainId}'`) throw new Error(`Unrecognized chainId '${chainId}'`)
} }
let fetchFn = null if (!gasPriceSupplierUrl) {
if (gasPriceSupplierUrl !== 'gas-price-oracle') { logger.warn({ chainId }, 'Gas price API is not configured, will fallback to the contract-supplied gas price')
fetchFn = () => fetch(gasPriceSupplierUrl, { timeout: 2000 })
} }
if (fetchOnce) { if (fetchOnce) {
await fetchGasPrice(speedType, factor, contract, fetchFn) await fetchGasPrice(speedType, factor, contract, gasPriceSupplierUrl)
} else { } else {
fetchGasPriceInterval = await setIntervalAndRun( fetchGasPriceInterval = await setIntervalAndRun(
() => fetchGasPrice(speedType, factor, contract, fetchFn), () => fetchGasPrice(speedType, factor, contract, gasPriceSupplierUrl),
updateInterval updateInterval
) )
} }

@ -0,0 +1,27 @@
// workaround to avoid circular dependencies in module imports
// e.g. logger -> config -> web3 -> provider -> logger
// transforms to the following import chain
// logger -> config -> web3 -> provider -> injectedLogger
// logger -> injectedLogger
let logger
const callbacks = []
function onInjected(cb) {
if (logger) {
cb(logger)
} else {
callbacks.push(cb)
}
}
function setLogger(newLogger) {
logger = newLogger
callbacks.forEach(cb => cb(logger))
}
module.exports = {
onInjected,
setLogger
}

@ -1,15 +1,7 @@
const pino = require('pino') const pino = require('pino')
const path = require('path') const path = require('path')
const {
web3Home, const { setLogger } = require('./injectedLogger')
web3Foreign,
web3ForeignArchive,
web3Side,
web3HomeFallback,
web3ForeignFallback,
web3HomeRedundant,
web3ForeignRedundant
} = require('./web3')
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])) : {}
@ -17,27 +9,11 @@ const logger = pino({
enabled: process.env.NODE_ENV !== 'test', enabled: process.env.NODE_ENV !== 'test',
name: config.name, name: config.name,
level: process.env.ORACLE_LOG_LEVEL || 'debug', level: process.env.ORACLE_LOG_LEVEL || 'debug',
base: base: {
process.env.NODE_ENV === 'production' validator: config.validatorAddress
? {
validator: process.env.ORACLE_VALIDATOR_ADDRESS
} }
: {}
}) })
web3Home.currentProvider.setLogger(logger) setLogger(logger)
web3Foreign.currentProvider.setLogger(logger)
web3HomeFallback.currentProvider.setLogger(logger)
web3ForeignFallback.currentProvider.setLogger(logger)
web3HomeRedundant.currentProvider.setLogger(logger)
web3ForeignRedundant.currentProvider.setLogger(logger)
if (web3ForeignArchive) {
web3ForeignArchive.currentProvider.setLogger(logger)
}
if (web3Side) {
web3Side.currentProvider.setLogger(logger)
}
module.exports = logger module.exports = logger

@ -1,5 +1,6 @@
const Web3 = require('web3') const Web3 = require('web3')
const { HttpListProvider } = require('./HttpListProvider') const { HttpListProvider } = require('./HttpListProvider')
const { SafeEthLogsProvider } = require('./SafeEthLogsProvider')
const { RedundantHttpListProvider } = require('./RedundantHttpListProvider') const { RedundantHttpListProvider } = require('./RedundantHttpListProvider')
const { RETRY_CONFIG } = require('../utils/constants') const { RETRY_CONFIG } = require('../utils/constants')
@ -37,10 +38,10 @@ const foreignOptions = {
retry: RETRY_CONFIG retry: RETRY_CONFIG
} }
const homeProvider = new HttpListProvider(homeUrls, homeOptions) const homeProvider = new SafeEthLogsProvider(new HttpListProvider(homeUrls, homeOptions))
const web3Home = new Web3(homeProvider) const web3Home = new Web3(homeProvider)
const foreignProvider = new HttpListProvider(foreignUrls, foreignOptions) const foreignProvider = new SafeEthLogsProvider(new HttpListProvider(foreignUrls, foreignOptions))
const web3Foreign = new Web3(foreignProvider) const web3Foreign = new Web3(foreignProvider)
let web3ForeignArchive = null let web3ForeignArchive = null

@ -11,7 +11,7 @@ async function sendTx({ privateKey, data, nonce, gasPrice, amount, gasLimit, to,
gasPrice, gasPrice,
gas: gasLimit gas: gasLimit
}, },
`0x${privateKey}` privateKey
) )
return new Promise((res, rej) => return new Promise((res, rej) =>

@ -102,6 +102,7 @@ async function getEventsFromTx({ web3, contract, event, txHash, filter }) {
const eventAbi = contract.options.jsonInterface.find(abi => abi.name === event) const eventAbi = contract.options.jsonInterface.find(abi => abi.name === event)
const decodeAbi = contract._decodeEventABI.bind(eventAbi) const decodeAbi = contract._decodeEventABI.bind(eventAbi)
const pastEvents = logs const pastEvents = logs
.filter(event => event.address.toLowerCase() === contractAddress.toLowerCase())
.filter(event => event.topics[0] === eventAbi.signature) .filter(event => event.topics[0] === eventAbi.signature)
.map(decodeAbi) .map(decodeAbi)
.filter(event => .filter(event =>

@ -99,7 +99,22 @@ function privateKeyToAddress(privateKey) {
return privateKey ? new Web3().eth.accounts.privateKeyToAccount(add0xPrefix(privateKey)).address : null return privateKey ? new Web3().eth.accounts.privateKeyToAccount(add0xPrefix(privateKey)).address : null
} }
function nonceError(e) { function isGasPriceError(e) {
const message = e.message.toLowerCase()
return message.includes('replacement transaction underpriced')
}
function isSameTransactionError(e) {
const message = e.message.toLowerCase()
return message.includes('transaction with the same hash was already imported') || message.includes('already known')
}
function isInsufficientBalanceError(e) {
const message = e.message.toLowerCase()
return message.includes('insufficient funds')
}
function isNonceError(e) {
const message = e.message.toLowerCase() const message = e.message.toLowerCase()
return ( return (
message.includes('transaction nonce is too low') || message.includes('transaction nonce is too low') ||
@ -153,8 +168,12 @@ module.exports = {
addExtraGas, addExtraGas,
setIntervalAndRun, setIntervalAndRun,
watchdog, watchdog,
add0xPrefix,
privateKeyToAddress, privateKeyToAddress,
nonceError, isGasPriceError,
isSameTransactionError,
isInsufficientBalanceError,
isNonceError,
getRetrySequence, getRetrySequence,
promiseAny, promiseAny,
readAccessListFile, readAccessListFile,

@ -34,7 +34,7 @@ async function initialize() {
try { try {
const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger) const checkHttps = checkHTTPS(process.env.ORACLE_ALLOW_HTTP_FOR_RPC, logger)
web3.currentProvider.urls.forEach(checkHttps(chain)) web3.currentProvider.subProvider.urls.forEach(checkHttps(chain))
await getLastProcessedBlock() await getLastProcessedBlock()
connectWatcherToQueue({ connectWatcherToQueue({

@ -3,77 +3,75 @@ const { expect } = require('chai')
const proxyquire = require('proxyquire').noPreserveCache() const proxyquire = require('proxyquire').noPreserveCache()
const { DEFAULT_UPDATE_INTERVAL } = require('../src/utils/constants') const { DEFAULT_UPDATE_INTERVAL } = require('../src/utils/constants')
describe('gasPrice', () => {
describe('start', () => {
const utils = { setIntervalAndRun: sinon.spy() } const utils = { setIntervalAndRun: sinon.spy() }
const fetchStub = () => ({
json: () => ({
standard: '103'
})
})
const fakeLogger = { error: sinon.spy(), warn: sinon.spy(), child: () => fakeLogger }
fetchStub['@global'] = true
const gasPriceDefault = proxyquire('../src/services/gasPrice', {
'../utils/utils': utils,
'node-fetch': fetchStub,
'../services/logger': { child: () => fakeLogger }
})
process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL = 15000
process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL = 30000
process.env.COMMON_HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', {
'../utils/utils': utils,
'node-fetch': fetchStub,
'../services/logger': { child: () => fakeLogger }
})
describe('gasPrice', () => {
beforeEach(() => { beforeEach(() => {
utils.setIntervalAndRun.resetHistory() utils.setIntervalAndRun.resetHistory()
fakeLogger.error.resetHistory()
fakeLogger.warn.resetHistory()
}) })
it('should call setIntervalAndRun with ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL interval value on Home', async () => {
// given
process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL = 15000
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
describe('start', () => {
it('should call setIntervalAndRun with ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL interval value on Home', async () => {
// when // when
await gasPrice.start('home') await gasPrice.start('home')
// then // then
expect(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL).to.equal('15000')
expect(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL).to.not.equal(DEFAULT_UPDATE_INTERVAL.toString())
expect(utils.setIntervalAndRun.args[0][1]).to.equal(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL.toString()) expect(utils.setIntervalAndRun.args[0][1]).to.equal(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL.toString())
}) })
it('should call setIntervalAndRun with ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL interval value on Foreign', async () => { it('should call setIntervalAndRun with ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL interval value on Foreign', async () => {
// given
process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL = 15000
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
// when // when
await gasPrice.start('foreign') await gasPrice.start('foreign')
// then // then
expect(process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL).to.equal('15000')
expect(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL).to.not.equal(DEFAULT_UPDATE_INTERVAL.toString())
expect(utils.setIntervalAndRun.args[0][1]).to.equal( expect(utils.setIntervalAndRun.args[0][1]).to.equal(
process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL.toString() process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL.toString()
) )
}) })
it('should call setIntervalAndRun with default interval value on Home', async () => { it('should call setIntervalAndRun with default interval value on Home', async () => {
// given
delete process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
// when // when
await gasPrice.start('home') await gasPriceDefault.start('home')
// then // then
expect(process.env.ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL).to.equal(undefined)
expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL) expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL)
}) })
it('should call setIntervalAndRun with default interval value on Foreign', async () => { it('should call setIntervalAndRun with default interval value on Foreign', async () => {
// given
delete process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
// when // when
await gasPrice.start('foreign') await gasPriceDefault.start('foreign')
// then // then
expect(process.env.ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL).to.equal(undefined)
expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL) expect(utils.setIntervalAndRun.args[0][1]).to.equal(DEFAULT_UPDATE_INTERVAL)
}) })
}) })
describe('fetching gas price', () => { describe('fetching gas price', () => {
const utils = { setIntervalAndRun: () => {} }
it('should fall back to default if contract and supplier are not working', async () => { it('should fall back to default if contract and supplier are not working', async () => {
// given // given
process.env.COMMON_HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home') await gasPrice.start('home')
// when // when
await gasPrice.fetchGasPrice('standard', 1, null, () => null) await gasPrice.fetchGasPrice('standard', 1, null, null)
// then // then
expect(gasPrice.getPrice()).to.equal('101000000000') expect(gasPrice.getPrice()).to.equal('101000000000')
@ -81,18 +79,10 @@ describe('gasPrice', () => {
it('should fetch gas from supplier', async () => { it('should fetch gas from supplier', async () => {
// given // given
process.env.COMMON_HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home') await gasPrice.start('home')
const gasPriceSupplierFetchFn = () => ({
json: () => ({
standard: '103'
})
})
// when // when
await gasPrice.fetchGasPrice('standard', 1, null, gasPriceSupplierFetchFn) await gasPrice.fetchGasPrice('standard', 1, null, 'url')
// then // then
expect(gasPrice.getPrice().toString()).to.equal('103000000000') expect(gasPrice.getPrice().toString()).to.equal('103000000000')
@ -100,8 +90,6 @@ describe('gasPrice', () => {
it('should fetch gas from contract', async () => { it('should fetch gas from contract', async () => {
// given // given
process.env.COMMON_HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home') await gasPrice.start('home')
const bridgeContractMock = { const bridgeContractMock = {
@ -113,7 +101,7 @@ describe('gasPrice', () => {
} }
// when // when
await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, () => {}) await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, null)
// then // then
expect(gasPrice.getPrice().toString()).to.equal('102000000000') expect(gasPrice.getPrice().toString()).to.equal('102000000000')
@ -121,8 +109,6 @@ describe('gasPrice', () => {
it('should fetch the gas price from the oracle first', async () => { it('should fetch the gas price from the oracle first', async () => {
// given // given
process.env.COMMON_HOME_GAS_PRICE_FALLBACK = '101000000000'
const gasPrice = proxyquire('../src/services/gasPrice', { '../utils/utils': utils })
await gasPrice.start('home') await gasPrice.start('home')
const bridgeContractMock = { const bridgeContractMock = {
@ -133,33 +119,23 @@ describe('gasPrice', () => {
} }
} }
const gasPriceSupplierFetchFn = () => ({
json: () => ({
standard: '103'
})
})
// when // when
await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, gasPriceSupplierFetchFn) await gasPrice.fetchGasPrice('standard', 1, bridgeContractMock, 'url')
// then // then
expect(gasPrice.getPrice().toString()).to.equal('103000000000') expect(gasPrice.getPrice().toString()).to.equal('103000000000')
}) })
it('log errors using the logger', async () => { it('log error using the logger', async () => {
// given // given
const fakeLogger = { error: sinon.spy() }
const gasPrice = proxyquire('../src/services/gasPrice', {
'../utils/utils': utils,
'../services/logger': { child: () => fakeLogger }
})
await gasPrice.start('home') await gasPrice.start('home')
// when // when
await gasPrice.fetchGasPrice('standard', 1, null, () => {}) await gasPrice.fetchGasPrice('standard', 1, null, null)
// then // then
expect(fakeLogger.error.calledTwice).to.equal(true) // two errors expect(fakeLogger.warn.calledOnce).to.equal(true) // one warning
expect(fakeLogger.error.calledOnce).to.equal(true) // one error
}) })
}) })
}) })

2374
yarn.lock

File diff suppressed because it is too large Load Diff