Compare commits

..

23 Commits

Author SHA1 Message Date
Alexander Kolotov
9da1d7ab0a Merge the develop branch to the master branch, preparation to v2.7.0-rc2
This merge contains the following set of changes:
  * [Monitor, Improvement] Include stats about failed and pending messages in mediators monitor endpoint (#544), closes #522
  * [Oracle, Fix] Fix remote shutdown sender behaviour (#550)
  * [Oracle, Fix] Force update of gas price when getting underprice tx error (#554), closes #545
  * [Oracle, Fix] Add oracle configuration option for max block range length (#557)
  * [Monitor, Fix] Limit block range of logs request (#543), closes #537
  * [Monitor, Fix] Add missing cache file save (#549)
  * [UI, Other] Remove UI from monorepo (#553)
  * [Common, Other] Update the contract's submodule to the release 5.7.0-rc0 (#548)
  * [Common, Other] Final version of the FT OB contract security audit report by ChainSecurity (#559)
2021-05-04 07:42:06 -06:00
Alexander Kolotov
78bcd7568b Final version of the FT OB contract security audit report by ChainSecurity (#559) 2021-05-04 07:25:02 -06:00
Kirill Fedoseev
429312500a Add oracle configuration option for max block range length (#557) 2021-04-26 10:47:38 -06:00
Kirill Fedoseev
59e0bf7565 Force update of gas price when getting underprice tx error (#554) 2021-04-18 12:30:40 -06:00
Kirill Fedoseev
ab51370d5a Remove UI from monorepo (#553) 2021-04-16 03:31:12 -06:00
Kirill Fedoseev
9dfb0510c4 Fix remote shutdown sender behaviour (#550) 2021-04-14 14:35:43 -06:00
Kirill Fedoseev
f65e8f9244 Add missing cache file save (#549) 2021-04-14 08:55:13 -06:00
Alexander Kolotov
5c8b595382 Update the contract's submodule to the release 5.7.0-rc0 (#548) 2021-04-13 14:50:52 -06:00
Kirill Fedoseev
71bf9d5583 Limit block range of logs request (#543) 2021-04-13 08:05:01 -06:00
Kirill Fedoseev
329ded4beb Include stats about failed and pending messages in mediators monitor endpoint (#544) 2021-04-13 07:59:49 -06:00
Alexander Kolotov
dc70247e2c Merge the develop branch to the master branch, preparation to v2.7.0-rc1 2021-04-13 04:09:38 -06:00
Kirill Fedoseev
f95beee5dc Add oracle service for remote shutdown (#531) 2021-04-12 14:38:42 -06:00
Leonid Tyurin
ae83c76be9 Fix monitor metrics (#533) 2021-04-02 03:59:49 -06:00
Kirill Fedoseev
dc3026e584 Fix undefined is not an object exception (#535) 2021-03-26 12:16:23 -06:00
Kirill Fedoseev
b6ba0744b9 Fix ALM react subscriptions leading to duplicated RPC requests (#534) 2021-03-18 00:04:33 -06:00
Kirill Fedoseev
4dba9a50e8 Extract tx resend intervals in env variables (#532) 2021-03-17 21:48:40 -06:00
Kirill Fedoseev
818bc4675d Fallback eth_getLogs to explorer API for BSC (#530) 2021-03-14 07:39:23 -06:00
Alexander Kolotov
f93ab330cc Merge the develop branch to the master branch, preparation to v2.7.0-rc0 (#515)
This merge contains the following set of changes:
  * [Improvement] Add /metrics endpoint for prometheus support (#512)
  * [Improvement] Add monitor script for detecting failed AMB messages (#513)
  * [Improvement] Improve performance of BS requests in ALM (#516)
  * [Fix] Add pretty error messages for manual execution transaction reverts (#511)
  * [Fix] Fix resend of stuck pending transactions (#514)
2021-02-25 20:42:51 -06:00
Leonid Tyurin
f64f8b1c91 Add /metrics endpoint for prometheus support (#512) 2021-02-25 20:39:48 -06:00
Kirill Fedoseev
9fd3f6ab82 Improve performance of BS requests in ALM (#516) 2021-02-25 20:38:13 -06:00
Kirill Fedoseev
626f9376b2 Fix resend of stuck pending transactions (#514) 2021-02-24 16:11:28 -06:00
Kirill Fedoseev
894134ba26 Add monitor script for detecting failed AMB messages (#513) 2021-02-18 19:26:07 -06:00
Kirill Fedoseev
e1536755f4 Add pretty error messages for manual execution transaction reverts (#511) 2021-02-18 19:25:01 -06:00
363 changed files with 1785 additions and 14129 deletions

View File

@@ -61,31 +61,6 @@ jobs:
key: ${{ needs.initialize.outputs.cache_key }}
- name: yarn run ${{ matrix.task }}
run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
ui-coverage:
runs-on: ubuntu-latest
needs:
- initialize
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')
steps:
- uses: actions/setup-node@v1
with:
node-version: 10
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/cache@v2
id: cache-repo
with:
path: |
**/node_modules
contracts/build
key: ${{ needs.initialize.outputs.cache_key }}
- name: yarn workspace ui run coverage
run: ${{ steps.cache-repo.outputs.cache-hit }} && yarn workspace ui run coverage
- uses: coverallsapp/github-action@master
with:
github-token: ${{ github.token }}
path-to-lcov: ./ui/coverage/lcov.info
build-e2e-images:
runs-on: ubuntu-latest
steps:
@@ -98,7 +73,6 @@ jobs:
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- name: Rebuild and push updated images
run: |
@@ -109,7 +83,6 @@ jobs:
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
if ! check_if_image_exists oracle ${ORACLE_TAG}; then updated+=("oracle"); fi
if ! check_if_image_exists monitor ${MONITOR_TAG}; then updated+=("monitor"); fi
if ! check_if_image_exists ui ${UI_TAG}; then updated+=("ui"); fi
if ! check_if_image_exists alm ${ALM_TAG}; then updated+=("alm"); fi
if [ ${#updated[@]} -gt 0 ]; then
echo "Updated services: ${updated[@]}"
@@ -149,12 +122,10 @@ jobs:
strategy:
fail-fast: false
matrix:
task: [oracle-e2e, monitor-e2e, alm-e2e, 'ui-e2e:ci']
task: [oracle-e2e, monitor-e2e, alm-e2e]
include:
- task: alm-e2e
use-cache: true
- task: 'ui-e2e:ci'
use-cache: true
steps:
- uses: actions/checkout@v2
with:
@@ -165,7 +136,6 @@ jobs:
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- if: ${{ matrix.use-cache }}
uses: actions/cache@v2
@@ -187,7 +157,7 @@ jobs:
strategy:
fail-fast: false
matrix:
task: [oracle, ui, monitor, multiple, repo]
task: [oracle, monitor, multiple, repo]
steps:
- uses: actions/checkout@v2
with:
@@ -206,20 +176,7 @@ jobs:
strategy:
fail-fast: false
matrix:
task: [amb, erc-to-erc, erc-to-native, native-to-erc, amb-stake-erc-to-erc]
include:
- task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC'
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE'
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC'
ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
task: [amb, erc-to-erc, erc-to-native, native-to-erc]
steps:
- uses: actions/checkout@v2
with:
@@ -230,7 +187,6 @@ jobs:
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $GITHUB_ENV
echo "MONITOR_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'monitor') }}" >> $GITHUB_ENV
echo "UI_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'ui') }}" >> $GITHUB_ENV
echo "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
- uses: actions/cache@v2
@@ -248,24 +204,9 @@ jobs:
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
- if: ${{ matrix.ui-e2e-grep }}
name: Pull e2e ui image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull ui
docker build \
--build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
--build-arg UI_TAG=${UI_TAG} \
--build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
-f ./e2e-commons/Dockerfile.ui \
-t ui_ui:latest \
.
- name: Deploy oracle and ui
- name: Deploy oracle
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- name: Reset docker socket permissions
run: sudo chown -R $USER:docker /var/run/docker.sock
- name: Run ui e2e tests
if: ${{ matrix.ui-e2e-grep }}
run: cd ui-e2e && xvfb-run yarn mocha -g "${{ matrix.ui-e2e-grep }}" -b ./test.js
- name: Run oracle e2e tests
if: ${{ !matrix.ui-e2e-grep }}
run: docker-compose -f ./e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run ${{ matrix.task }}
run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}

View File

@@ -42,29 +42,15 @@ ORACLE_HOME_TO_FOREIGN_BLOCK_LIST | Filename with a list of addresses, separated
ORACLE_HOME_TO_FOREIGN_CHECK_SENDER | If set to `true`, instructs the oracle to do an extra check for transaction origin in the block/allowance list. `false` by default. | `true` / `false`
ORACLE_ALWAYS_RELAY_SIGNATURES | If set to `true`, the oracle will always relay signatures even if it was not the last who finilized the signatures collecting process. The default is `false`. | `true` / `false`
ORACLE_RPC_REQUEST_TIMEOUT | Timeout in milliseconds for a single RPC request. Default value is `ORACLE_*_RPC_POLLING_INTERVAL * 2`. | integer
## UI configuration
name | description | value
--- | --- | ---
UI_TITLE | The title for the bridge UI page. `%c` will be replaced by the name of the network. | string
UI_OG_TITLE | The meta title for the deployed bridge page. | string
UI_DESCRIPTION | The meta description for the deployed bridge page. | string
UI_NATIVE_TOKEN_DISPLAY_NAME | name of the home native coin | string
UI_HOME_NETWORK_DISPLAY_NAME | name to be displayed for home network | string
UI_FOREIGN_NETWORK_DISPLAY_NAME | name to be displayed for foreign network | string
UI_HOME_WITHOUT_EVENTS | `true` if home network doesn't support events | true/false
UI_FOREIGN_WITHOUT_EVENTS | `true` if foreign network doesn't support events | true/false
UI_HOME_EXPLORER_TX_TEMPLATE | template link to transaction on home explorer. `%s` will be replaced by transaction hash | URL template
UI_FOREIGN_EXPLORER_TX_TEMPLATE | template link to transaction on foreign explorer. `%s` will be replaced by transaction hash | URL template
UI_HOME_EXPLORER_ADDRESS_TEMPLATE | template link to address on home explorer. `%s` will be replaced by address | URL template
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explorer. `%s` will be replaced by address | URL template
UI_HOME_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Home Bridge contract. | integer
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer
UI_PORT | The port for the UI app. | integer
UI_STYLES | The set of styles to render the bridge UI page. | core/classic/stake
UI_PUBLIC_URL | The public url for the deployed bridge page | string
ORACLE_HOME_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Home sender service. Defaults to 20 minutes. | integer
ORACLE_FOREIGN_TX_RESEND_INTERVAL | Interval in milliseconds for automatic resending of stuck transactions for Foreign sender service. Defaults to 20 minutes. | integer
ORACLE_SHUTDOWN_SERVICE_URL | Optional external URL to some other service/monitor/configuration manager that controls the remote shutdown process. GET request should return `application/json` message with the following schema: `{ shutdown: true/false }`. | URL
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | Optional interval in milliseconds used to request the side RPC node or external shutdown service. Default is 120000. | integer
ORACLE_SIDE_RPC_URL | Optional HTTPS URL(s) for communication with the external shutdown service or side RPC nodes, used for shutdown manager activities. Several URLs can be specified, delimited by spaces. If the connection to one of these nodes is lost the next URL is used for connection. | URL(s)
ORACLE_SHUTDOWN_CONTRACT_ADDRESS | Optional contract address in the side chain accessible through `ORACLE_SIDE_RPC_URL`, where the method passed in `ORACLE_SHUTDOWN_CONTRACT_METHOD` is implemented. | `address`
ORACLE_SHUTDOWN_CONTRACT_METHOD | Method signature to be used in the side chain to identify the current shutdown status. Method should return boolean. Default value is `isShutdown()`. | `function signature`
ORACLE_FOREIGN_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Foreign chain. Infinite, if not provided. | `integer`
ORACLE_HOME_RPC_BLOCK_POLLING_LIMIT | Max length for the block range used in `eth_getLogs` requests for polling contract events for the Home chain. Infinite, if not provided. | `integer`
## Monitor configuration

View File

@@ -20,12 +20,10 @@ Sub-repositories maintained within this monorepo are listed below.
| Sub-repository | Description |
| --- | --- |
| [Oracle](oracle/README.md) | Oracle responsible for listening to bridge related events and authorizing asset transfers. |
| [UI](ui/README.md) | DApp interface to transfer tokens and coins between chains. |
| [Monitor](monitor/README.md) | Tool for checking balances and unprocessed events in bridged networks. |
| [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle |
| [Monitor-E2E](monitor-e2e/README.md) | End to end tests for the Monitor |
| [UI-E2E](ui-e2e/README.md) | End to end tests for the UI |
| [Deployment-E2E](deployment-e2e/README.md) | End to end tests for the Deployment |
| [Commons](commons/README.md) | Interfaces, constants and utilities shared between the sub-repositories |
| [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
@@ -68,7 +66,7 @@ Clone the repository:
git clone https://github.com/poanetwork/tokenbridge
```
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts:
If there is no need to build docker images for the TokenBridge components (oracle, monitor), initialize submodules, install dependencies, compile the Smart Contracts:
```
yarn initialize
```
@@ -91,7 +89,7 @@ Running tests for all projects:
yarn test
```
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [UI](ui-e2e/README.md).
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [Monitor](monitor-e2e/README.md).
For details on building, running and developing please refer to respective READMEs in sub-repositories.

View File

@@ -19,7 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
COPY commons/package.json ./commons/
COPY alm/package.json ./alm/
COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile
COPY ./commons ./commons
COPY ./alm ./alm

View File

@@ -58,6 +58,7 @@
]
},
"devDependencies": {
"eslint-plugin-prettier": "^3.1.3"
"eslint-plugin-prettier": "^3.1.3",
"node-fetch": "^2.6.1"
}
}

View File

@@ -3,6 +3,8 @@ const { BRIDGE_VALIDATORS_ABI, HOME_AMB_ABI } = require('commons')
const path = require('path')
require('dotenv').config()
const Web3 = require('web3')
const fetch = require('node-fetch')
const { URL } = require('url')
const fs = require('fs')
@@ -10,7 +12,9 @@ const {
COMMON_HOME_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_RPC_URL,
COMMON_FOREIGN_BRIDGE_ADDRESS
COMMON_FOREIGN_BRIDGE_ADDRESS,
ALM_FOREIGN_EXPLORER_API,
ALM_HOME_EXPLORER_API
} = process.env
const generateSnapshot = async (side, url, bridgeAddress) => {
@@ -19,6 +23,31 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const snapshot = {}
const web3 = new Web3(new Web3.providers.HttpProvider(url))
const api = side === 'home' ? ALM_HOME_EXPLORER_API : ALM_FOREIGN_EXPLORER_API
const getPastEventsWithFallback = (contract, eventName, options) =>
contract.getPastEvents(eventName, options).catch(async e => {
if (e.message.includes('exceed maximum block range')) {
const abi = contract.options.jsonInterface.find(abi => abi.type === 'event' && abi.name === eventName)
const url = new URL(api)
url.searchParams.append('module', 'logs')
url.searchParams.append('action', 'getLogs')
url.searchParams.append('address', contract.options.address)
url.searchParams.append('fromBlock', options.fromBlock)
url.searchParams.append('toBlock', options.toBlock || 'latest')
url.searchParams.append('topic0', web3.eth.abi.encodeEventSignature(abi))
const logs = await fetch(url).then(res => res.json())
return logs.result.map(log => ({
transactionHash: log.transactionHash,
blockNumber: parseInt(log.blockNumber.slice(2), 16),
returnValues: web3.eth.abi.decodeLog(abi.inputs, log.data, log.topics.slice(1))
}))
}
throw e
})
const currentBlockNumber = await web3.eth.getBlockNumber()
snapshot.snapshotBlockNumber = currentBlockNumber
@@ -29,10 +58,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
// Save RequiredBlockConfirmationChanged events
let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', {
fromBlock: 0,
toBlock: currentBlockNumber
})
let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback(
bridgeContract,
'RequiredBlockConfirmationChanged',
{
fromBlock: 0,
toBlock: currentBlockNumber
}
)
// In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
// manually generate an event for this. Example Sokol - Kovan bridge
@@ -59,10 +92,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
// Save RequiredSignaturesChanged events
const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', {
fromBlock: 0,
toBlock: currentBlockNumber
})
const RequiredSignaturesChangedEvents = await getPastEventsWithFallback(
validatorContract,
'RequiredSignaturesChanged',
{
fromBlock: 0,
toBlock: currentBlockNumber
}
)
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
blockNumber: e.blockNumber,
returnValues: {
@@ -71,7 +108,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
}))
// Save ValidatorAdded events
const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', {
const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', {
fromBlock: 0,
toBlock: currentBlockNumber
})
@@ -85,7 +122,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
}))
// Save ValidatorRemoved events
const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', {
const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', {
fromBlock: 0,
toBlock: currentBlockNumber
})

View File

@@ -39,10 +39,17 @@ export interface ConfirmationsContainerParams {
message: MessageObject
receipt: Maybe<TransactionReceipt>
fromHome: boolean
timestamp: number
homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
}
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
export const ConfirmationsContainer = ({
message,
receipt,
fromHome,
homeStartBlock,
foreignStartBlock
}: ConfirmationsContainerParams) => {
const {
home: { name: homeName },
foreign: { name: foreignName }
@@ -62,7 +69,8 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
message,
receipt,
fromHome,
timestamp,
homeStartBlock,
foreignStartBlock,
requiredSignatures,
validatorList,
blockConfirmations

View File

@@ -36,10 +36,12 @@ export const ExecutionConfirmation = ({
const availableManualExecution =
!isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED ||
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
executionEventsFetched &&
!!executionData.validator))
const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const showAgeColumn = !requiredManualExecution || executionData.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -71,7 +73,7 @@ export const ExecutionConfirmation = ({
<tr>
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
<th className="text-center">Status</th>
{!requiredManualExecution && <th className="text-center">Age</th>}
{showAgeColumn && <th className="text-center">Age</th>}
{availableManualExecution && <th className="text-center">Actions</th>}
</tr>
</Thead>
@@ -87,7 +89,7 @@ export const ExecutionConfirmation = ({
)}
</td>
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
{!requiredManualExecution && (
{showAgeColumn && (
<AgeTd className="text-center">
{executionData.timestamp > 0 ? (
<ExplorerTxLink href={txExplorerLink} target="_blank">

View File

@@ -2,9 +2,18 @@ import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from '@web3-react/core'
import { INCORRECT_CHAIN_ERROR, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import {
DOUBLE_EXECUTION_ATTEMPT_ERROR,
EXECUTION_FAILED_ERROR,
EXECUTION_OUT_OF_GAS_ERROR,
FOREIGN_EXPLORER_API,
INCORRECT_CHAIN_ERROR,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth'
const StyledButton = styled.button`
color: var(--button-color);
@@ -61,7 +70,9 @@ export const ManualExecutionButton = ({
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS))
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI()
setManualExecution(false)
library.eth
@@ -80,7 +91,33 @@ export const ManualExecutionButton = ({
})
setPendingExecution(true)
})
.on('error', (e: Error) => setError(e.message))
.on('error', async (e: Error, receipt: TransactionReceipt) => {
if (e.message.includes('Transaction has been reverted by the EVM')) {
const successExecutionData = await getSuccessExecutionData(
bridge,
'RelayedMessage',
library,
messageId,
FOREIGN_EXPLORER_API
)
if (successExecutionData) {
setExecutionData(successExecutionData)
setError(DOUBLE_EXECUTION_ATTEMPT_ERROR)
} else {
const { gas } = await library.eth.getTransaction(receipt.transactionHash)
setExecutionData({
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validator: account,
txHash: receipt.transactionHash,
timestamp: Math.floor(new Date().getTime() / 1000.0),
executionResult: false
})
setError(gas === receipt.gasUsed ? EXECUTION_OUT_OF_GAS_ERROR : EXECUTION_FAILED_ERROR)
}
} else {
setError(e.message)
}
})
},
[
manualExecution,

View File

@@ -10,6 +10,7 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { ConfirmationsContainer } from './ConfirmationsContainer'
import { TransactionReceipt } from 'web3-eth'
import { BackButton } from './commons/BackButton'
import { useClosestBlock } from '../hooks/useClosestBlock'
export interface StatusContainerParam {
onBackToMain: () => void
@@ -23,12 +24,15 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const { chainId, txHash, messageIdParam } = useParams()
const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString()
const validParameters = validChainId && validTxHash(txHash)
const isHome = chainId === home.chainId.toString()
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
txHash: validParameters ? txHash : '',
chainId: validParameters ? parseInt(chainId) : 0,
receiptParam
})
const homeStartBlock = useClosestBlock(true, isHome, receipt, timestamp)
const foreignStartBlock = useClosestBlock(false, isHome, receipt, timestamp)
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam
@@ -64,7 +68,6 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash
const formattedMessageId = formatTxHash(displayReference)
const isHome = chainId === home.chainId.toString()
const txExplorerLink = getExplorerTxUrl(txHash, isHome)
const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND
@@ -101,7 +104,13 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
)}
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}
{displayConfirmations && (
<ConfirmationsContainer message={messageToConfirm} receipt={receipt} fromHome={isHome} timestamp={timestamp} />
<ConfirmationsContainer
message={messageToConfirm}
receipt={receipt}
fromHome={isHome}
homeStartBlock={homeStartBlock}
foreignStartBlock={foreignStartBlock}
/>
)}
<BackButton onBackToMain={onBackToMain} />
</div>

View File

@@ -2,6 +2,7 @@ import React from 'react'
import styled from 'styled-components'
import { InfoIcon } from './InfoIcon'
import { CloseIcon } from './CloseIcon'
import { ExplorerTxLink } from './ExplorerTxLink'
const StyledErrorAlert = styled.div`
border: 1px solid var(--failed-color);
@@ -15,17 +16,33 @@ const CloseIconContainer = styled.div`
`
const TextContainer = styled.div`
white-space: pre-wrap;
flex-direction: column;
`
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">{error}</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
const errorArray = error.split('%link')
const text = errorArray[0]
let link
if (errorArray.length > 1) {
link = (
<ExplorerTxLink href={errorArray[1]} target="_blank" rel="noopener noreferrer">
{errorArray[1]}
</ExplorerTxLink>
)
}
return (
<div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row">
<InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10">
{text}
{link}
</TextContainer>
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
<CloseIcon color="var(--failed-color)" />
</CloseIconContainer>
</StyledErrorAlert>
</div>
)
}

View File

@@ -18,9 +18,8 @@ export const ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION: boolean =
export const HOME_RPC_POLLING_INTERVAL: number = 5000
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
export const BLOCK_RANGE: number = 50
export const ONE_DAY_TIMESTAMP: number = 86400
export const THREE_DAYS_TIMESTAMP: number = 259200
export const BLOCK_RANGE: number = 500
export const MAX_TX_SEARCH_BLOCK_RANGE: number = 10000
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
export const SUBMIT_SIGNATURE_HASH = '630cea8e'
@@ -66,3 +65,12 @@ export const VALIDATOR_CONFIRMATION_STATUS = {
export const SEARCHING_TX = 'Searching Transaction...'
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`
export const DOUBLE_EXECUTION_ATTEMPT_ERROR = `Your execution transaction has been reverted.
However, the execution completed successfully in the transaction sent by a different party.`
export const EXECUTION_FAILED_ERROR = `Your execution transaction has been reverted.
Please, contact the support by messaging on %linkhttps://forum.poa.network/c/support`
export const EXECUTION_OUT_OF_GAS_ERROR = `Your execution transaction has been reverted due to Out-of-Gas error.
Please, resend the transaction and provide more gas to it.`

View File

@@ -4,6 +4,8 @@ import { useStateProvider } from '../state/StateProvider'
import { Contract } from 'web3-eth-contract'
import { getRequiredBlockConfirmations } from '../utils/contract'
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
import Web3 from 'web3'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
export interface UseBlockConfirmationsParams {
fromHome: boolean
@@ -19,9 +21,11 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio
contract: Contract,
receipt: TransactionReceipt,
setResult: Function,
snapshotProvider: SnapshotProvider
snapshotProvider: SnapshotProvider,
web3: Web3,
api: string
) => {
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider)
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider, web3, api)
setResult(result)
}
@@ -29,10 +33,12 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio
() => {
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
if (!bridgeContract || !receipt) return
callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider)
const web3 = fromHome ? home.web3 : foreign.web3
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
if (!bridgeContract || !receipt || !web3) return
callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider, web3, api)
},
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome]
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome, home.web3, foreign.web3]
)
return {

View File

@@ -0,0 +1,68 @@
import { useEffect, useState } from 'react'
import { TransactionReceipt } from 'web3-eth'
import { useStateProvider } from '../state/StateProvider'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
import { getClosestBlockByTimestamp } from '../utils/explorer'
export function useClosestBlock(
searchHome: boolean,
fromHome: boolean,
receipt: Maybe<TransactionReceipt>,
timestamp: number
) {
const { home, foreign } = useStateProvider()
const [blockNumber, setBlockNumber] = useState<number | null>(null)
useEffect(
() => {
if (!receipt || blockNumber || !timestamp) return
if (fromHome === searchHome) {
setBlockNumber(receipt.blockNumber)
return
}
const web3 = searchHome ? home.web3 : foreign.web3
if (!web3) return
const getBlock = async () => {
// try to fast-fetch closest block number from the chain explorer
try {
const api = searchHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
setBlockNumber(await getClosestBlockByTimestamp(api, timestamp))
return
} catch {}
const lastBlock = await web3.eth.getBlock('latest')
if (lastBlock.timestamp <= timestamp) {
setBlockNumber(lastBlock.number)
return
}
const oldBlock = await web3.eth.getBlock(Math.max(lastBlock.number - 10000, 1))
const blockDiff = lastBlock.number - oldBlock.number
const timeDiff = (lastBlock.timestamp as number) - (oldBlock.timestamp as number)
const averageBlockTime = timeDiff / blockDiff
let currentBlock = lastBlock
let prevBlockDiff = Infinity
while (true) {
const timeDiff = (currentBlock.timestamp as number) - timestamp
const blockDiff = Math.ceil(timeDiff / averageBlockTime)
if (Math.abs(blockDiff) < 5 || Math.abs(blockDiff) >= Math.abs(prevBlockDiff)) {
setBlockNumber(currentBlock.number - blockDiff - 5)
break
}
prevBlockDiff = blockDiff
currentBlock = await web3.eth.getBlock(currentBlock.number - blockDiff)
}
}
getBlock()
},
[blockNumber, foreign.web3, fromHome, home.web3, receipt, searchHome, timestamp]
)
return blockNumber
}

View File

@@ -11,9 +11,6 @@ import {
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { homeBlockNumberProvider, foreignBlockNumberProvider } from '../services/BlockNumberProvider'
import { checkSignaturesWaitingForBLocks } from '../utils/signatureWaitingForBlocks'
import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEvent'
import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks'
import { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
import { getFinalizationEvent } from '../utils/getFinalizationEvent'
import {
@@ -28,7 +25,8 @@ export interface useMessageConfirmationsParams {
message: MessageObject
receipt: Maybe<TransactionReceipt>
fromHome: boolean
timestamp: number
homeStartBlock: Maybe<number>
foreignStartBlock: Maybe<number>
requiredSignatures: number
validatorList: string[]
blockConfirmations: number
@@ -56,17 +54,18 @@ export const useMessageConfirmations = ({
message,
receipt,
fromHome,
timestamp,
homeStartBlock,
foreignStartBlock,
requiredSignatures,
validatorList,
blockConfirmations
}: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider()
const [confirmations, setConfirmations] = useState([])
const [confirmations, setConfirmations] = useState<ConfirmationParam[]>([])
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
const [waitingBlocks, setWaitingBlocks] = useState(false)
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
const [signatureCollected, setSignatureCollected] = useState(false)
const [signatureCollected, setSignatureCollected] = useState<boolean | string[]>(false)
const [executionEventsFetched, setExecutionEventsFetched] = useState(false)
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
const [executionData, setExecutionData] = useState<ExecutionData>({
@@ -83,150 +82,175 @@ export const useMessageConfirmations = ({
const [pendingConfirmations, setPendingConfirmations] = useState(false)
const [pendingExecution, setPendingExecution] = useState(false)
const existsConfirmation = (confirmationArray: ConfirmationParam[]) => {
const filteredList = confirmationArray.filter(
const existsConfirmation = (confirmationArray: ConfirmationParam[]) =>
confirmationArray.some(
c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING
)
return filteredList.length > 0
}
// start watching blocks at the start
useEffect(
() => {
if (!home.web3 || !foreign.web3) return
homeBlockNumberProvider.start(home.web3)
foreignBlockNumberProvider.start(foreign.web3)
},
[foreign.web3, home.web3]
)
// Check if the validators are waiting for block confirmations to verify the message
useEffect(
() => {
if (!receipt || !blockConfirmations || waitingBlocksResolved) return
const subscriptions: Array<number> = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
let timeoutId: number
const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider
const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL
const web3 = fromHome ? home.web3 : foreign.web3
blockProvider.start(web3)
const targetBlock = receipt.blockNumber + blockConfirmations
const validatorsWaiting = validatorList.map(validator => ({
validator,
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
txHash: '',
timestamp: 0
}))
checkSignaturesWaitingForBLocks(
targetBlock,
setWaitingBlocks,
setWaitingBlocksResolved,
validatorList,
setConfirmations,
blockProvider,
interval,
subscriptions
)
const checkSignaturesWaitingForBLocks = () => {
const currentBlock = blockProvider.get()
return () => {
unsubscribe()
blockProvider.stop()
if (currentBlock && currentBlock >= targetBlock) {
setWaitingBlocksResolved(true)
setWaitingBlocks(false)
} else if (currentBlock) {
setWaitingBlocks(true)
setConfirmations(validatorsWaiting)
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, interval)
} else {
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, 500)
}
}
checkSignaturesWaitingForBLocks()
return () => clearTimeout(timeoutId)
},
[
blockConfirmations,
foreign.web3,
fromHome,
validatorList,
home.web3,
receipt,
setConfirmations,
waitingBlocksResolved
]
[blockConfirmations, fromHome, receipt, validatorList, waitingBlocksResolved]
)
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
// the execution tx on the foreign network is waiting for block confirmations
// This is executed if the message is in Home to Foreign direction only
const hasCollectedSignatures = !!signatureCollected // true or string[]
useEffect(
() => {
if (!fromHome || !receipt || !home.web3 || !signatureCollected) return
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !hasCollectedSignatures) return
const subscriptions: Array<number> = []
let timeoutId: number
let isCancelled = false
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
const contract = home.bridgeContract
const getCollectedSignaturesEvent = async (fromBlock: number, toBlock: number) => {
const currentBlock = homeBlockNumberProvider.get()
if (currentBlock) {
// prevent errors if the toBlock parameter is bigger than the latest
const securedToBlock = toBlock >= currentBlock ? currentBlock : toBlock
const events = await contract.getPastEvents('CollectedSignatures', {
fromBlock,
toBlock: securedToBlock
})
const event = events.find(e => e.returnValues.messageHash === messageHash)
if (event) {
setCollectedSignaturesEvent(event)
} else if (!isCancelled) {
timeoutId = setTimeout(() => getCollectedSignaturesEvent(securedToBlock, securedToBlock + BLOCK_RANGE), 500)
}
} else if (!isCancelled) {
timeoutId = setTimeout(() => getCollectedSignaturesEvent(fromBlock, toBlock), 500)
}
}
homeBlockNumberProvider.start(home.web3)
const fromBlock = receipt.blockNumber
const toBlock = fromBlock + BLOCK_RANGE
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
getCollectedSignaturesEvent(
home.web3,
home.bridgeContract,
fromBlock,
toBlock,
messageHash,
setCollectedSignaturesEvent,
subscriptions
)
getCollectedSignaturesEvent(receipt.blockNumber, receipt.blockNumber + BLOCK_RANGE)
return () => {
unsubscribe()
homeBlockNumberProvider.stop()
clearTimeout(timeoutId)
isCancelled = true
}
},
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
[fromHome, home.bridgeContract, home.web3, message.data, receipt, hasCollectedSignatures]
)
// Check if the responsible validator is waiting for block confirmations to execute the message on foreign network
// This is executed if the message is in Home to Foreign direction only
useEffect(
() => {
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return
if (!fromHome || !home.web3 || !collectedSignaturesEvent || !blockConfirmations) return
if (waitingBlocksForExecutionResolved) return
const subscriptions: Array<number> = []
let timeoutId: number
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
homeBlockNumberProvider.start(home.web3)
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
checkWaitingBlocksForExecution(
homeBlockNumberProvider,
HOME_RPC_POLLING_INTERVAL,
targetBlock,
collectedSignaturesEvent,
setWaitingBlocksForExecution,
setWaitingBlocksForExecutionResolved,
setExecutionData,
subscriptions
)
const checkWaitingBlocksForExecution = () => {
const currentBlock = homeBlockNumberProvider.get()
return () => {
unsubscribe()
homeBlockNumberProvider.stop()
if (currentBlock && currentBlock >= targetBlock) {
const undefinedExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? undefinedExecutionState
: data
)
setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false)
} else if (currentBlock) {
setWaitingBlocksForExecution(true)
const waitingExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? waitingExecutionState
: data
)
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), HOME_RPC_POLLING_INTERVAL)
} else {
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), 500)
}
}
checkWaitingBlocksForExecution()
return () => clearTimeout(timeoutId)
},
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved]
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, waitingBlocksForExecutionResolved]
)
// Checks if validators verified the message
// To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations
useEffect(
() => {
if (!waitingBlocksResolved || !timestamp || !requiredSignatures) return
if (!waitingBlocksResolved || !homeStartBlock || !requiredSignatures || !home.web3 || !home.bridgeContract) return
if (!validatorList || !validatorList.length) return
const subscriptions: Array<number> = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
let timeoutId: number
let isCancelled = false
getConfirmationsForTx(
message.data,
@@ -237,9 +261,9 @@ export const useMessageConfirmations = ({
setConfirmations,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
timestamp,
id => (timeoutId = id),
() => isCancelled,
homeStartBlock,
getValidatorFailedTransactionsForMessage,
setFailedConfirmations,
getValidatorPendingTransactionsForMessage,
@@ -248,7 +272,8 @@ export const useMessageConfirmations = ({
)
return () => {
unsubscribe()
clearTimeout(timeoutId)
isCancelled = true
}
},
[
@@ -259,8 +284,7 @@ export const useMessageConfirmations = ({
home.bridgeContract,
requiredSignatures,
waitingBlocksResolved,
timestamp,
setConfirmations
homeStartBlock
]
)
@@ -271,29 +295,23 @@ export const useMessageConfirmations = ({
() => {
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
const subscriptions: Array<number> = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const contractEvent = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract
const providedWeb3 = fromHome ? foreign.web3 : home.web3
const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
const web3 = fromHome ? foreign.web3 : home.web3
const startBlock = fromHome ? foreignStartBlock : homeStartBlock
if (!startBlock || !bridgeContract || !web3) return
let timeoutId: number
let isCancelled = false
getFinalizationEvent(
fromHome,
bridgeContract,
contractEvent,
providedWeb3,
web3,
setExecutionData,
waitingBlocksResolved,
message,
interval,
subscriptions,
timestamp,
id => (timeoutId = id),
() => isCancelled,
startBlock,
collectedSignaturesEvent,
getExecutionFailedTransactionForMessage,
setFailedExecution,
@@ -303,7 +321,8 @@ export const useMessageConfirmations = ({
)
return () => {
unsubscribe()
clearTimeout(timeoutId)
isCancelled = true
}
},
[
@@ -315,8 +334,9 @@ export const useMessageConfirmations = ({
home.web3,
waitingBlocksResolved,
waitingBlocksForExecutionResolved,
timestamp,
collectedSignaturesEvent
collectedSignaturesEvent,
foreignStartBlock,
homeStartBlock
]
)
@@ -328,6 +348,9 @@ export const useMessageConfirmations = ({
? CONFIRMATIONS_STATUS.SUCCESS
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
setStatus(newStatus)
foreignBlockNumberProvider.stop()
homeBlockNumberProvider.stop()
} else if (signatureCollected) {
if (fromHome) {
if (waitingBlocksForExecution) {

View File

@@ -11,40 +11,23 @@ export const useTransactionFinder = ({ txHash, web3 }: { txHash: string; web3: M
() => {
if (!txHash || !web3) return
const subscriptions: number[] = []
let timeoutId: number
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const getReceipt = async (
web3: Web3,
txHash: string,
setReceipt: Function,
setStatus: Function,
subscriptions: number[]
) => {
const getReceipt = async () => {
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
setReceipt(txReceipt)
if (!txReceipt) {
setStatus(TRANSACTION_STATUS.NOT_FOUND)
const timeoutId = setTimeout(
() => getReceipt(web3, txHash, setReceipt, setStatus, subscriptions),
HOME_RPC_POLLING_INTERVAL
)
subscriptions.push(timeoutId)
timeoutId = setTimeout(getReceipt, HOME_RPC_POLLING_INTERVAL)
} else {
setStatus(TRANSACTION_STATUS.FOUND)
}
}
getReceipt(web3, txHash, setReceipt, setStatus, subscriptions)
return () => {
unsubscribe()
}
getReceipt()
return () => clearTimeout(timeoutId)
},
[txHash, web3]
)

View File

@@ -31,19 +31,14 @@ export const useTransactionStatus = ({
useEffect(
() => {
const subscriptions: Array<number> = []
if (!chainId || !txHash || !home.chainId || !foreign.chainId || !home.web3 || !foreign.web3) return
const isHome = chainId === home.chainId
const web3 = isHome ? home.web3 : foreign.web3
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
let timeoutId: number
const getReceipt = async () => {
if (!chainId || !txHash || !home.chainId || !foreign.chainId || !home.web3 || !foreign.web3) return
setLoading(true)
const isHome = chainId === home.chainId
const web3 = isHome ? home.web3 : foreign.web3
let txReceipt
@@ -59,8 +54,7 @@ export const useTransactionStatus = ({
setStatus(TRANSACTION_STATUS.NOT_FOUND)
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.NOT_FOUND))
setMessages([{ id: txHash, data: '' }])
const timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
subscriptions.push(timeoutId)
timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
} else {
const blockNumber = txReceipt.blockNumber
const block = await getBlock(web3, blockNumber)
@@ -70,9 +64,9 @@ export const useTransactionStatus = ({
if (txReceipt.status) {
let bridgeMessages: Array<MessageObject>
if (isHome) {
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, home.web3, home.bridgeAddress)
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, web3, home.bridgeAddress)
} else {
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, foreign.web3, foreign.bridgeAddress)
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, web3, foreign.bridgeAddress)
}
if (bridgeMessages.length === 0) {
@@ -98,14 +92,9 @@ export const useTransactionStatus = ({
setLoading(false)
}
// unsubscribe from previous txHash
unsubscribe()
getReceipt()
return () => {
// unsubscribe when unmount component
unsubscribe()
}
return () => clearTimeout(timeoutId)
},
[
txHash,

View File

@@ -6,6 +6,7 @@ import { BRIDGE_VALIDATORS_ABI } from '../abis'
import { useStateProvider } from '../state/StateProvider'
import { TransactionReceipt } from 'web3-eth'
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
export interface useValidatorContractParams {
fromHome: boolean
@@ -30,10 +31,12 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
contract: Maybe<Contract>,
receipt: TransactionReceipt,
setResult: Function,
snapshotProvider: SnapshotProvider
snapshotProvider: SnapshotProvider,
web3: Web3,
api: string
) => {
if (!contract) return
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider)
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider, web3, api)
setResult(result)
}
@@ -41,32 +44,35 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
contract: Maybe<Contract>,
receipt: TransactionReceipt,
setResult: Function,
snapshotProvider: SnapshotProvider
snapshotProvider: SnapshotProvider,
web3: Web3,
api: string
) => {
if (!contract) return
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider)
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider, web3, api)
setResult(result)
}
const web3 = fromHome ? home.web3 : foreign.web3
const api = fromHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
useEffect(
() => {
const web3 = fromHome ? home.web3 : foreign.web3
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
if (!web3 || !bridgeContract) return
callValidatorContract(bridgeContract, web3, setValidatorContract)
},
[home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome]
[web3, bridgeContract]
)
useEffect(
() => {
if (!receipt) return
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider)
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider)
if (!web3 || !receipt) return
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider, web3, api)
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider, web3, api)
},
[validatorContract, receipt, fromHome]
[validatorContract, receipt, web3, snapshotProvider, api]
)
return {

View File

@@ -1,6 +1,6 @@
import Web3 from 'web3'
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'
import { HOME_RPC_POLLING_INTERVAL } from '../config/constants'
import { FOREIGN_RPC_POLLING_INTERVAL, HOME_RPC_POLLING_INTERVAL } from '../config/constants'
export class BlockNumberProvider {
private running: number
@@ -61,4 +61,4 @@ export class BlockNumberProvider {
}
export const homeBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
export const foreignBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
export const foreignBlockNumberProvider = new BlockNumberProvider(FOREIGN_RPC_POLLING_INTERVAL)

View File

@@ -16,7 +16,7 @@ describe('getRequiredBlockConfirmations', () => {
test('Should call requiredBlockConfirmations method if no events present', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
methods: methodsBuilder('1')
@@ -37,7 +37,7 @@ describe('getRequiredBlockConfirmations', () => {
})
test('Should not call to get events if block number was included in the snapshot', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => []),
getPastEvents: jest.fn().mockImplementation(async () => []),
methods: methodsBuilder('3')
} as unknown) as Contract
@@ -64,7 +64,7 @@ describe('getRequiredBlockConfirmations', () => {
})
test('Should call to get events if block number was not included in the snapshot', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => [
getPastEvents: jest.fn().mockImplementation(async () => [
{
blockNumber: 9,
returnValues: {
@@ -102,7 +102,7 @@ describe('getRequiredBlockConfirmations', () => {
})
test('Should use the most updated event', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => [
getPastEvents: jest.fn().mockImplementation(async () => [
{
blockNumber: 9,
returnValues: {
@@ -141,7 +141,7 @@ describe('getRequiredBlockConfirmations', () => {
describe('getRequiredSignatures', () => {
test('Should not call to get events if block number was included in the snapshot', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => [])
getPastEvents: jest.fn().mockImplementation(async () => [])
} as unknown) as Contract
const snapshotProvider = ({
@@ -173,7 +173,7 @@ describe('getRequiredSignatures', () => {
})
test('Should call to get events if block number is higher than the snapshot block number', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => [
getPastEvents: jest.fn().mockImplementation(async () => [
{
blockNumber: 15,
returnValues: {
@@ -216,7 +216,7 @@ describe('getRequiredSignatures', () => {
})
test('Should use the most updated event before the block number', async () => {
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => [
getPastEvents: jest.fn().mockImplementation(async () => [
{
blockNumber: 15,
returnValues: {
@@ -270,7 +270,7 @@ describe('getValidatorList', () => {
test('Should return the current validator list if no events found', async () => {
const currentValidators = [validator1, validator2, validator3]
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => []),
getPastEvents: jest.fn().mockImplementation(async () => []),
methods: methodsBuilder(currentValidators)
} as unknown) as Contract
@@ -301,7 +301,7 @@ describe('getValidatorList', () => {
test('If validator was added later from snapshot it should not include it', async () => {
const currentValidators = [validator1, validator2, validator3]
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => []),
getPastEvents: jest.fn().mockImplementation(async () => []),
methods: methodsBuilder(currentValidators)
} as unknown) as Contract
@@ -340,7 +340,7 @@ describe('getValidatorList', () => {
test('If validator was added later from chain it should not include it', async () => {
const currentValidators = [validator1, validator2, validator3]
const contract = ({
getPastEvents: jest.fn().mockImplementation(event => {
getPastEvents: jest.fn().mockImplementation(async event => {
if (event === 'ValidatorAdded') {
return [
{
@@ -385,7 +385,7 @@ describe('getValidatorList', () => {
test('If validator was removed later from snapshot it should include it', async () => {
const currentValidators = [validator1, validator2]
const contract = ({
getPastEvents: jest.fn().mockImplementation(() => []),
getPastEvents: jest.fn().mockImplementation(async () => []),
methods: methodsBuilder(currentValidators)
} as unknown) as Contract
@@ -424,7 +424,7 @@ describe('getValidatorList', () => {
test('If validator was removed later from chain it should include it', async () => {
const currentValidators = [validator1, validator2]
const contract = ({
getPastEvents: jest.fn().mockImplementation(event => {
getPastEvents: jest.fn().mockImplementation(async event => {
if (event === 'ValidatorRemoved') {
return [
{

View File

@@ -17,19 +17,33 @@ const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
describe('getFailedTransactions', () => {
test('should only return failed transactions', async () => {
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }]
const to = otherAddress
const transactions = [
{ isError: '0', to },
{ isError: '1', to },
{ isError: '0', to },
{ isError: '1', to },
{ isError: '1', to }
]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getFailedTransactions('', '', 0, 1, '', fetchAccountTransactions)
const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(3)
})
})
describe('getSuccessTransactions', () => {
test('should only return success transactions', async () => {
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }]
const to = otherAddress
const transactions = [
{ isError: '0', to },
{ isError: '1', to },
{ isError: '0', to },
{ isError: '1', to },
{ isError: '1', to }
]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getSuccessTransactions('', '', 0, 1, '', fetchAccountTransactions)
const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(2)
})
})
@@ -74,8 +88,8 @@ describe('getExecutionFailedTransactionForMessage', () => {
account: '',
to: '',
messageData,
startTimestamp: 0,
endTimestamp: 1
startBlock: 0,
endBlock: 1
},
fetchAccountTransactions
)

View File

@@ -34,7 +34,7 @@ const bridgeContract = {
}
} as Contract
const requiredSignatures = 2
const waitingBlocksResolved = true
const isCancelled = () => false
let subscriptions: Array<number> = []
const timestamp = 1594045859
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
@@ -94,8 +94,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -177,8 +177,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -241,8 +241,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -340,8 +340,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -453,8 +453,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -557,8 +557,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -678,8 +678,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,
@@ -746,8 +746,8 @@ describe('getConfirmationsForTx', () => {
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
getFailedTransactions,
setFailedConfirmations,

View File

@@ -4,7 +4,6 @@ import Web3 from 'web3'
import { getFinalizationEvent } from '../getFinalizationEvent'
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
const eventName = 'RelayedMessage'
const timestamp = 1594045859
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156'
@@ -20,12 +19,11 @@ const web3 = ({
toChecksumAddress: (a: string) => a
}
} as unknown) as Web3
const waitingBlocksResolved = true
const message = {
id: '0x123',
data: '0x123456789'
}
const interval = 10000
const isCancelled = () => false
let subscriptions: Array<number> = []
const event = {
@@ -50,7 +48,7 @@ beforeEach(() => {
describe('getFinalizationEvent', () => {
test('should get finalization event and not try to get failed or pending transactions', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return [event]
}
} as unknown) as Contract
@@ -64,14 +62,13 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
@@ -101,7 +98,7 @@ describe('getFinalizationEvent', () => {
})
test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
}
} as unknown) as Contract
@@ -115,14 +112,13 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
@@ -145,7 +141,7 @@ describe('getFinalizationEvent', () => {
})
test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -166,14 +162,13 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
@@ -196,7 +191,7 @@ describe('getFinalizationEvent', () => {
})
test('should retry to get finalization event and not to try to get failed transaction if pending transactions found if home to foreign transaction', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -217,14 +212,13 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
@@ -254,7 +248,7 @@ describe('getFinalizationEvent', () => {
})
test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => {
const contract = ({
getPastEvents: () => {
getPastEvents: async () => {
return []
},
options: {
@@ -275,14 +269,13 @@ describe('getFinalizationEvent', () => {
const setExecutionEventsFetched = jest.fn()
await getFinalizationEvent(
true,
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
subscriptions.push.bind(subscriptions),
isCancelled,
timestamp,
collectedSignaturesEvent,
getFailedExecution,

View File

@@ -1,18 +1,33 @@
import { Contract } from 'web3-eth-contract'
import { EventData } from 'web3-eth-contract'
import { SnapshotProvider } from '../services/SnapshotProvider'
import { getLogs } from './explorer'
import Web3 from 'web3'
const getPastEventsWithFallback = (
api: string,
web3: Web3 | null,
contract: Contract,
eventName: string,
options: any
) =>
contract
.getPastEvents(eventName, options)
.catch(() => (api && web3 ? getLogs(api, web3, contract, eventName, options) : []))
export const getRequiredBlockConfirmations = async (
contract: Contract,
blockNumber: number,
snapshotProvider: SnapshotProvider
snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => {
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
let contractEvents: EventData[] = []
if (blockNumber > snapshotBlockNumber) {
contractEvents = await contract.getPastEvents('RequiredBlockConfirmationChanged', {
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredBlockConfirmationChanged', {
fromBlock: snapshotBlockNumber + 1,
toBlock: blockNumber
})
@@ -38,14 +53,16 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
export const getRequiredSignatures = async (
contract: Contract,
blockNumber: number,
snapshotProvider: SnapshotProvider
snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => {
const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
let contractEvents: EventData[] = []
if (blockNumber > snapshotBlockNumber) {
contractEvents = await contract.getPastEvents('RequiredSignaturesChanged', {
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredSignaturesChanged', {
fromBlock: snapshotBlockNumber + 1,
toBlock: blockNumber
})
@@ -59,7 +76,13 @@ export const getRequiredSignatures = async (
return parseInt(requiredSignatures)
}
export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => {
export const getValidatorList = async (
contract: Contract,
blockNumber: number,
snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => {
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
@@ -67,10 +90,10 @@ export const getValidatorList = async (contract: Contract, blockNumber: number,
const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber
const [currentList, added, removed] = await Promise.all([
contract.methods.validatorList().call(),
contract.getPastEvents('ValidatorAdded', {
getPastEventsWithFallback(api, web3, contract, 'ValidatorAdded', {
fromBlock
}),
contract.getPastEvents('ValidatorRemoved', {
getPastEventsWithFallback(api, web3, contract, 'ValidatorRemoved', {
fromBlock
})
])

View File

@@ -1,71 +0,0 @@
import { BlockNumberProvider } from '../services/BlockNumberProvider'
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { EventData } from 'web3-eth-contract'
export const checkWaitingBlocksForExecution = async (
blockProvider: BlockNumberProvider,
interval: number,
targetBlock: number,
collectedSignaturesEvent: EventData,
setWaitingBlocksForExecution: Function,
setWaitingBlocksForExecutionResolved: Function,
setExecutionData: Function,
subscriptions: number[]
) => {
const currentBlock = blockProvider.get()
if (currentBlock && currentBlock >= targetBlock) {
const undefinedExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? undefinedExecutionState
: data
)
setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false)
blockProvider.stop()
} else {
let nextInterval = interval
if (!currentBlock) {
nextInterval = 500
} else {
setWaitingBlocksForExecution(true)
const waitingExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? waitingExecutionState
: data
)
}
const timeoutId = setTimeout(
() =>
checkWaitingBlocksForExecution(
blockProvider,
interval,
targetBlock,
collectedSignaturesEvent,
setWaitingBlocksForExecution,
setWaitingBlocksForExecutionResolved,
setExecutionData,
subscriptions
),
nextInterval
)
subscriptions.push(timeoutId)
}
}

View File

@@ -1,10 +1,15 @@
import {
BLOCK_RANGE,
EXECUTE_AFFIRMATION_HASH,
EXECUTE_SIGNATURES_HASH,
FOREIGN_EXPLORER_API,
HOME_EXPLORER_API,
MAX_TX_SEARCH_BLOCK_RANGE,
SUBMIT_SIGNATURE_HASH
} from '../config/constants'
import { AbiItem } from 'web3-utils'
import Web3 from 'web3'
import { Contract } from 'web3-eth-contract'
export interface APITransaction {
timeStamp: string
@@ -12,6 +17,7 @@ export interface APITransaction {
input: string
to: string
hash: string
blockNumber: string
}
export interface APIPendingTransaction {
@@ -27,110 +33,54 @@ export interface PendingTransactionsParams {
export interface AccountTransactionsParams {
account: string
to: string
startTimestamp: number
endTimestamp: number
startBlock: number
endBlock: number
api: string
}
export interface GetFailedTransactionParams {
account: string
to: string
messageData: string
startTimestamp: number
endTimestamp: number
}
export interface GetPendingTransactionParams {
account: string
to: string
messageData: string
}
export const fetchAccountTransactionsFromBlockscout = async ({
account,
to,
startTimestamp,
endTimestamp,
api
}: AccountTransactionsParams): Promise<APITransaction[]> => {
const url = `${api}?module=account&action=txlist&address=${account}&filterby=from=${account}&to=${to}&starttimestamp=${startTimestamp}&endtimestamp=${endTimestamp}`
try {
const result = await fetch(url).then(res => res.json())
if (result.status === '0') {
return []
}
return result.result
} catch (e) {
console.log(e)
return []
}
export interface GetTransactionParams extends GetPendingTransactionParams {
startBlock: number
endBlock: number
}
export const getBlockByTimestampUrl = (api: string, timestamp: number) =>
`${api}&module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before`
export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => {
const url = new URL(api)
url.searchParams.append('module', 'account')
url.searchParams.append('action', 'txlist')
url.searchParams.append('address', account)
url.searchParams.append('filterby', 'from')
url.searchParams.append('startblock', startBlock.toString())
url.searchParams.append('endblock', endBlock.toString())
export const fetchAccountTransactionsFromEtherscan = async ({
account,
to,
startTimestamp,
endTimestamp,
api
}: AccountTransactionsParams): Promise<APITransaction[]> => {
const startBlockUrl = getBlockByTimestampUrl(api, startTimestamp)
const endBlockUrl = getBlockByTimestampUrl(api, endTimestamp)
let fromBlock = 0
let toBlock = 9999999999999
try {
const [fromBlockResult, toBlockResult] = await Promise.all([
fetch(startBlockUrl).then(res => res.json()),
fetch(endBlockUrl).then(res => res.json())
])
const result = await fetch(url.toString()).then(res => res.json())
if (fromBlockResult.status !== '0') {
fromBlock = parseInt(fromBlockResult.result)
}
if (toBlockResult.status !== '0') {
toBlock = parseInt(toBlockResult.result)
}
} catch (e) {
console.log(e)
if (result.message === 'No transactions found') {
return []
}
const url = `${api}&module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}`
try {
const result = await fetch(url).then(res => res.json())
if (result.status === '0') {
return []
}
const toAddressLowerCase = to.toLowerCase()
const transactions: APITransaction[] = result.result
return transactions.filter(t => t.to.toLowerCase() === toAddressLowerCase)
} catch (e) {
console.log(e)
return []
}
}
export const fetchAccountTransactions = (api: string) => {
return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan
return result.result
}
export const fetchPendingTransactions = async ({
account,
api
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
const url = `${api}?module=account&action=pendingtxlist&address=${account}`
if (!api.includes('blockscout')) {
return []
}
const url = new URL(api)
url.searchParams.append('module', 'account')
url.searchParams.append('action', 'pendingtxlist')
url.searchParams.append('address', account)
try {
const result = await fetch(url).then(res => res.json())
const result = await fetch(url.toString()).then(res => res.json())
if (result.status === '0') {
return []
}
@@ -141,30 +91,135 @@ export const fetchPendingTransactions = async ({
}
}
export const getClosestBlockByTimestamp = async (api: string, timestamp: number): Promise<number> => {
if (api.includes('blockscout')) {
throw new Error('Blockscout does not support getblocknobytime')
}
const url = new URL(api)
url.searchParams.append('module', 'block')
url.searchParams.append('action', 'getblocknobytime')
url.searchParams.append('timestamp', timestamp.toString())
url.searchParams.append('closest', 'before')
const blockNumber = await fetch(url.toString()).then(res => res.json())
return parseInt(blockNumber.result)
}
// fast version of fetchAccountTransactions
// sequentially fetches transactions in small batches
// caches the result
const transactionsCache: { [key: string]: { lastBlock: number; transactions: APITransaction[] } } = {}
export const getAccountTransactions = async ({
account,
startBlock,
endBlock,
api
}: AccountTransactionsParams): Promise<APITransaction[]> => {
const key = `${account}-${startBlock}-${api}`
// initialize empty cache if it doesn't exist yet
if (!transactionsCache[key]) {
transactionsCache[key] = { lastBlock: startBlock - 1, transactions: [] }
}
// if cache contains events up to block X,
// new batch is fetched for range [X + 1, X + 1 + BLOCK_RANGE]
const newStartBlock = transactionsCache[key].lastBlock + 1
const newEndBlock = newStartBlock + BLOCK_RANGE
// search for new transactions only if max allowed block range is not yet exceeded
if (newEndBlock <= startBlock + MAX_TX_SEARCH_BLOCK_RANGE) {
const newTransactions = await fetchAccountTransactions({
account,
startBlock: newStartBlock,
endBlock: newEndBlock,
api
})
const transactions = transactionsCache[key].transactions.concat(...newTransactions)
// cache updated transactions list
transactionsCache[key].transactions = transactions
// enbBlock is assumed to be the current block number of the chain
// if the whole range is finalized, last block can be safely updated to the end of the range
// this works even if there are no transactions in the list
if (newEndBlock < endBlock) {
transactionsCache[key].lastBlock = newEndBlock
} else if (transactions.length > 0) {
transactionsCache[key].lastBlock = parseInt(transactions[transactions.length - 1].blockNumber, 10)
}
return transactions
}
console.warn(`Reached max transaction searching range, returning previously cached transactions for ${account}`)
return transactionsCache[key].transactions
}
export const getLogs = async (
api: string,
web3: Web3,
contract: Contract,
event: string,
options: { fromBlock: number; toBlock: number | 'latest'; topics: (string | null)[] }
) => {
const abi = contract.options.jsonInterface.find((abi: AbiItem) => abi.type === 'event' && abi.name === event)!
const url = new URL(api)
url.searchParams.append('module', 'logs')
url.searchParams.append('action', 'getLogs')
url.searchParams.append('address', contract.options.address)
url.searchParams.append('fromBlock', options.fromBlock.toString())
url.searchParams.append('toBlock', (options.toBlock || 'latest').toString())
const topics = [web3.eth.abi.encodeEventSignature(abi), ...(options.topics || [])]
for (let i = 0; i < topics.length; i++) {
if (topics[i] !== null) {
url.searchParams.append(`topic${i}`, topics[i] as string)
for (let j = 0; j < i; j++) {
url.searchParams.append(`topic${j}_${i}_opr`, 'and')
}
}
}
const logs = await fetch(url.toString()).then(res => res.json())
return logs.result.map((log: any) => ({
transactionHash: log.transactionHash,
blockNumber: parseInt(log.blockNumber.slice(2), 16),
returnValues: web3.eth.abi.decodeLog(abi.inputs!, log.data, log.topics.slice(1))
}))
}
const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase()
export const getFailedTransactions = async (
account: string,
to: string,
startTimestamp: number,
endTimestamp: number,
startBlock: number,
endBlock: number,
api: string,
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => {
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError !== '0')
return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to))
}
export const getSuccessTransactions = async (
account: string,
to: string,
startTimestamp: number,
endTimestamp: number,
startBlock: number,
endBlock: number,
api: string,
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => {
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api })
return transactions.filter(t => t.isError === '0')
return transactions.filter(t => t.isError === '0').filter(filterReceiver(to))
}
export const filterValidatorSignatureTransaction = (
@@ -183,17 +238,10 @@ export const getValidatorFailedTransactionsForMessage = async ({
account,
to,
messageData,
startTimestamp,
endTimestamp
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactions(
account,
to,
startTimestamp,
endTimestamp,
HOME_EXPLORER_API,
fetchAccountTransactionsFromBlockscout
)
startBlock,
endBlock
}: GetTransactionParams): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
return filterValidatorSignatureTransaction(failedTransactions, messageData)
}
@@ -202,33 +250,19 @@ export const getValidatorSuccessTransactionsForMessage = async ({
account,
to,
messageData,
startTimestamp,
endTimestamp
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
const transactions = await getSuccessTransactions(
account,
to,
startTimestamp,
endTimestamp,
HOME_EXPLORER_API,
fetchAccountTransactionsFromBlockscout
)
startBlock,
endBlock
}: GetTransactionParams): Promise<APITransaction[]> => {
const transactions = await getSuccessTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
return filterValidatorSignatureTransaction(transactions, messageData)
}
export const getExecutionFailedTransactionForMessage = async (
{ account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams,
{ account, to, messageData, startBlock, endBlock }: GetTransactionParams,
getFailedTransactionsMethod = getFailedTransactions
): Promise<APITransaction[]> => {
const failedTransactions = await getFailedTransactionsMethod(
account,
to,
startTimestamp,
endTimestamp,
FOREIGN_EXPLORER_API,
fetchAccountTransactions(FOREIGN_EXPLORER_API)
)
const failedTransactions = await getFailedTransactionsMethod(account, to, startBlock, endBlock, FOREIGN_EXPLORER_API)
const messageDataValue = messageData.replace('0x', '')
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))

View File

@@ -1,53 +0,0 @@
import Web3 from 'web3'
import { Contract, EventData } from 'web3-eth-contract'
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
import { BLOCK_RANGE } from '../config/constants'
export const getCollectedSignaturesEvent = async (
web3: Maybe<Web3>,
contract: Maybe<Contract>,
fromBlock: number,
toBlock: number,
messageHash: string,
setCollectedSignaturesEvent: Function,
subscriptions: number[]
) => {
if (!web3 || !contract) return
const currentBlock = homeBlockNumberProvider.get()
let events: EventData[] = []
let securedToBlock = toBlock
if (currentBlock) {
// prevent errors if the toBlock parameter is bigger than the latest
securedToBlock = toBlock >= currentBlock ? currentBlock : toBlock
events = await contract.getPastEvents('CollectedSignatures', {
fromBlock,
toBlock: securedToBlock
})
}
const filteredEvents = events.filter(e => e.returnValues.messageHash === messageHash)
if (filteredEvents.length) {
const event = filteredEvents[0]
setCollectedSignaturesEvent(event)
homeBlockNumberProvider.stop()
} else {
const newFromBlock = currentBlock ? securedToBlock : fromBlock
const newToBlock = currentBlock ? toBlock + BLOCK_RANGE : toBlock
const timeoutId = setTimeout(
() =>
getCollectedSignaturesEvent(
web3,
contract,
newFromBlock,
newToBlock,
messageHash,
setCollectedSignaturesEvent,
subscriptions
),
500
)
subscriptions.push(timeoutId)
}
}

View File

@@ -1,12 +1,7 @@
import Web3 from 'web3'
import { Contract } from 'web3-eth-contract'
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import {
GetFailedTransactionParams,
APITransaction,
APIPendingTransaction,
GetPendingTransactionParams
} from './explorer'
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
import {
getValidatorConfirmation,
@@ -34,24 +29,22 @@ const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfi
export const getConfirmationsForTx = async (
messageData: string,
web3: Maybe<Web3>,
web3: Web3,
validatorList: string[],
bridgeContract: Maybe<Contract>,
bridgeContract: Contract,
fromHome: boolean,
setResult: Function,
requiredSignatures: number,
setSignatureCollected: Function,
waitingBlocksResolved: boolean,
subscriptions: number[],
timestamp: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setTimeoutId: (timeoutId: number) => void,
isCancelled: () => boolean,
startBlock: number,
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingConfirmations: Function,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => {
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const hashMsg = web3.utils.soliditySha3Raw(messageData)
@@ -102,7 +95,7 @@ export const getConfirmationsForTx = async (
// Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
)
)
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
@@ -136,7 +129,7 @@ export const getConfirmationsForTx = async (
// get transactions from success signatures
const successConfirmationWithData = await Promise.all(
successConfirmations.map(
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions)
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
)
)
@@ -149,28 +142,30 @@ export const getConfirmationsForTx = async (
(!hasEnoughSignatures && missingConfirmations.length > 0) ||
successConfirmationWithTxFound.length < successConfirmationWithData.length
) {
const timeoutId = setTimeout(
() =>
getConfirmationsForTx(
messageData,
web3,
validatorList,
bridgeContract,
fromHome,
setResult,
requiredSignatures,
setSignatureCollected,
waitingBlocksResolved,
subscriptions,
timestamp,
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations,
getSuccessTransactions
),
HOME_RPC_POLLING_INTERVAL
)
subscriptions.push(timeoutId)
if (!isCancelled()) {
const timeoutId = setTimeout(
() =>
getConfirmationsForTx(
messageData,
web3,
validatorList,
bridgeContract,
fromHome,
setResult,
requiredSignatures,
setSignatureCollected,
setTimeoutId,
isCancelled,
startBlock,
getFailedTransactions,
setFailedConfirmations,
getPendingTransactions,
setPendingConfirmations,
getSuccessTransactions
),
HOME_RPC_POLLING_INTERVAL
)
setTimeoutId(timeoutId)
}
}
}

View File

@@ -1,41 +1,51 @@
import { Contract, EventData } from 'web3-eth-contract'
import Web3 from 'web3'
import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import {
CACHE_KEY_EXECUTION_FAILED,
FOREIGN_EXPLORER_API,
FOREIGN_RPC_POLLING_INTERVAL,
HOME_EXPLORER_API,
HOME_RPC_POLLING_INTERVAL,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { ExecutionData } from '../hooks/useMessageConfirmations'
import {
APIPendingTransaction,
APITransaction,
GetFailedTransactionParams,
GetPendingTransactionParams
GetTransactionParams,
GetPendingTransactionParams,
getLogs
} from './explorer'
import { getBlock, MessageObject } from './web3'
import validatorsCache from '../services/ValidatorsCache'
import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider'
export const getFinalizationEvent = async (
contract: Maybe<Contract>,
const getPastEventsWithFallback = (api: string, web3: Web3, contract: Contract, eventName: string, options: any) =>
contract.getPastEvents(eventName, options).catch(
() =>
api
? getLogs(api, web3, contract, eventName, {
fromBlock: options.fromBlock,
toBlock: options.toBlock,
topics: [null, null, options.filter.messageId]
})
: []
)
export const getSuccessExecutionData = async (
contract: Contract,
eventName: string,
web3: Maybe<Web3>,
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
waitingBlocksResolved: boolean,
message: MessageObject,
interval: number,
subscriptions: number[],
timestamp: number,
collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function,
setExecutionEventsFetched: Function
web3: Web3,
messageId: string,
api: string = ''
) => {
if (!contract || !web3 || !waitingBlocksResolved) return
// Since it filters by the message id, only one event will be fetched
// so there is no need to limit the range of the block to reduce the network traffic
const events: EventData[] = await contract.getPastEvents(eventName, {
const events: EventData[] = await getPastEventsWithFallback(api, web3, contract, eventName, {
fromBlock: 0,
toBlock: 'latest',
filter: {
messageId: message.id
messageId
}
})
if (events.length > 0) {
@@ -48,13 +58,40 @@ export const getFinalizationEvent = async (
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
setResult({
return {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator: validatorAddress,
txHash: event.transactionHash,
timestamp: blockTimestamp,
executionResult: event.returnValues.status
})
}
}
return null
}
export const getFinalizationEvent = async (
fromHome: boolean,
contract: Contract,
web3: Web3,
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
message: MessageObject,
setTimeoutId: (timeoutId: number) => void,
isCancelled: () => boolean,
startBlock: number,
collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedExecution: Function,
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
setPendingExecution: Function,
setExecutionEventsFetched: Function
) => {
const eventName = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const api = fromHome ? FOREIGN_EXPLORER_API : HOME_EXPLORER_API
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id, api)
if (successExecutionData) {
setResult(successExecutionData)
} else {
setExecutionEventsFetched(true)
// If event is defined, it means it is a message from Home to Foreign
@@ -84,14 +121,15 @@ export const getFinalizationEvent = async (
} else {
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
const blockProvider = fromHome ? foreignBlockNumberProvider : homeBlockNumberProvider
if (!failedFromCache) {
const failedTransactions = await getFailedExecution({
account: validator,
to: contract.options.address,
messageData: message.data,
startTimestamp: timestamp,
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
startBlock,
endBlock: blockProvider.get() || 0
})
if (failedTransactions.length > 0) {
@@ -114,27 +152,28 @@ export const getFinalizationEvent = async (
}
}
const timeoutId = setTimeout(
() =>
getFinalizationEvent(
contract,
eventName,
web3,
setResult,
waitingBlocksResolved,
message,
interval,
subscriptions,
timestamp,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution,
setExecutionEventsFetched
),
interval
)
subscriptions.push(timeoutId)
if (!isCancelled()) {
const timeoutId = setTimeout(
() =>
getFinalizationEvent(
fromHome,
contract,
web3,
setResult,
message,
setTimeoutId,
isCancelled,
startBlock,
collectedSignaturesEvent,
getFailedExecution,
setFailedExecution,
getPendingExecution,
setPendingExecution,
setExecutionEventsFetched
),
fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
)
setTimeoutId(timeoutId)
}
}
}

View File

@@ -1,50 +0,0 @@
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { BlockNumberProvider } from '../services/BlockNumberProvider'
export const checkSignaturesWaitingForBLocks = async (
targetBlock: number,
setWaitingStatus: Function,
setWaitingBlocksResolved: Function,
validatorList: string[],
setConfirmations: Function,
blockProvider: BlockNumberProvider,
interval: number,
subscriptions: number[]
) => {
const currentBlock = blockProvider.get()
if (currentBlock && currentBlock >= targetBlock) {
setWaitingBlocksResolved(true)
setWaitingStatus(false)
blockProvider.stop()
} else {
let nextInterval = interval
if (!currentBlock) {
nextInterval = 500
} else {
const validatorsWaiting = validatorList.map(validator => {
return {
validator,
status: VALIDATOR_CONFIRMATION_STATUS.WAITING
}
})
setWaitingStatus(true)
setConfirmations(validatorsWaiting)
}
const timeoutId = setTimeout(
() =>
checkSignaturesWaitingForBLocks(
targetBlock,
setWaitingStatus,
setWaitingBlocksResolved,
validatorList,
setConfirmations,
blockProvider,
interval,
subscriptions
),
nextInterval
)
subscriptions.push(timeoutId)
}
}

View File

@@ -2,18 +2,9 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
import validatorsCache from '../services/ValidatorsCache'
import {
CACHE_KEY_FAILED,
CACHE_KEY_SUCCESS,
ONE_DAY_TIMESTAMP,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import {
APIPendingTransaction,
APITransaction,
GetFailedTransactionParams,
GetPendingTransactionParams
} from './explorer'
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
export const getValidatorConfirmation = (
web3: Web3,
@@ -50,8 +41,8 @@ export const getSuccessExecutionTransaction = (
bridgeContract: Contract,
fromHome: boolean,
messageData: string,
timestamp: number,
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
startBlock: number,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
@@ -65,8 +56,8 @@ export const getSuccessExecutionTransaction = (
account: validatorData.validator,
to: bridgeContract.options.address,
messageData,
startTimestamp: timestamp,
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
startBlock,
endBlock: homeBlockNumberProvider.get() || 0
})
let txHashTimestamp = 0
@@ -98,8 +89,8 @@ export const getSuccessExecutionTransaction = (
export const getValidatorFailedTransaction = (
bridgeContract: Contract,
messageData: string,
timestamp: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
startBlock: number,
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
const failedFromCache = validatorsCache.getData(validatorCacheKey)
@@ -112,8 +103,8 @@ export const getValidatorFailedTransaction = (
account: validatorData.validator,
to: bridgeContract.options.address,
messageData,
startTimestamp: timestamp,
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
startBlock,
endBlock: homeBlockNumberProvider.get() || 0
})
const newStatus =
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED

View File

@@ -1,4 +1,4 @@
const { toWei, toBN } = require('web3-utils')
const { toWei, toBN, BN } = require('web3-utils')
const { GasPriceOracle } = require('gas-price-oracle')
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
@@ -150,11 +150,8 @@ const tryCall = async (method, fallbackValue) => {
const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0)
const getPastEvents = async (
contract,
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
) => {
let events
const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, options }) => {
let events = []
try {
events = await contract.getPastEvents(event, {
...options,
@@ -162,19 +159,19 @@ const getPastEvents = async (
toBlock
})
} catch (e) {
if (e.message.includes('query returned more than') && toBlock !== 'latest') {
if (e.message.includes('query returned more than') || e.message.toLowerCase().includes('timeout')) {
const middle = toBN(fromBlock)
.add(toBN(toBlock))
.divRound(toBN(2))
const middlePlusOne = middle.add(toBN(1))
const firstHalfEvents = await getPastEvents(contract, {
const firstHalfEvents = await getPastEventsOrSplit(contract, {
options,
event,
fromBlock,
toBlock: middle
})
const secondHalfEvents = await getPastEvents(contract, {
const secondHalfEvents = await getPastEventsOrSplit(contract, {
options,
event,
fromBlock: middlePlusOne,
@@ -188,6 +185,31 @@ const getPastEvents = async (
return events
}
const getPastEvents = async (
contract,
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
) => {
if (toBlock === 'latest') {
return contract.getPastEvents(event, {
...options,
fromBlock,
toBlock
})
}
const batchSize = 1000000
const to = toBN(toBlock)
const events = []
for (let from = toBN(fromBlock); from.lte(to); from = from.addn(batchSize + 1)) {
const opts = { event, fromBlock: from, toBlock: BN.min(to, from.addn(batchSize)), options }
const batch = await getPastEventsOrSplit(contract, opts)
events.push(batch)
}
return [].concat(...events)
}
const getValidatorList = async (address, eth, options) => {
options.logger && options.logger.debug && options.logger.debug('getting validatorList')

View File

@@ -16,7 +16,7 @@ Alternatively, if there are no changes except the playbooks, you can use the `ma
./molecule.sh <scenario_name>
```
In this case `master` branch will be used as a codebase for Monitor, UI, Oracle and Contracts deployed by your local playbook.
In this case `master` branch will be used as a codebase for Monitor, Oracle and Contracts deployed by your local playbook.
## Run the tests
@@ -29,7 +29,6 @@ Available scenarios:
Scenario | Description
--- | ---
oracle | Deploys and checks standalone Oracle on Ubuntu host
ui | Deploys and checks standalone UI on Ubuntu host
## Ultimate E2E tests

View File

@@ -16,7 +16,6 @@ platforms:
children:
- oracle
- monitor
- ui
image: ubuntu:16.04
privileged: true
network_mode: host

View File

@@ -8,7 +8,6 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
@pytest.mark.parametrize("service", [
("poabridge"),
("tokenbridge-ui"),
("tokenbridge-monitor")
])
def test_services(host, service):
@@ -24,7 +23,7 @@ def test_services(host, service):
("oracle_bridge_affirmation_1"),
("oracle_bridge_senderhome_1"),
("oracle_bridge_senderforeign_1"),
("ui_ui_1"),
("oracle_bridge_shutdown_1"),
("monitor_monitor_1")
])
def test_docker_containers(host, name):

View File

@@ -14,6 +14,7 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
("oracle_bridge_affirmation_1"),
("oracle_bridge_senderhome_1"),
("oracle_bridge_senderforeign_1"),
("oracle_bridge_shutdown_1"),
])
def test_docker_containers(host, name):
container = host.docker(name)

View File

@@ -15,7 +15,6 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
("/home/poadocker/bridge/contracts"),
("/home/poadocker/bridge/oracle"),
("/home/poadocker/bridge/monitor"),
("/home/poadocker/bridge/ui"),
("/home/poadocker/bridge/parity")
])
def test_existing_folders(host, path):
@@ -28,8 +27,7 @@ def test_existing_folders(host, path):
("/home/poadocker/bridge/commons/package.json"),
("/home/poadocker/bridge/contracts/package.json"),
("/home/poadocker/bridge/oracle/package.json"),
("/home/poadocker/bridge/monitor/package.json"),
("/home/poadocker/bridge/ui/package.json")
("/home/poadocker/bridge/monitor/package.json")
])
def test_existing_package_json(host, path):
assert host.file(path).exists
@@ -41,8 +39,7 @@ def test_existing_package_json(host, path):
("/home/poadocker/bridge/contracts/Dockerfile"),
("/home/poadocker/bridge/parity/Dockerfile"),
("/home/poadocker/bridge/oracle/Dockerfile"),
("/home/poadocker/bridge/monitor/Dockerfile"),
("/home/poadocker/bridge/ui/Dockerfile")
("/home/poadocker/bridge/monitor/Dockerfile")
])
def test_existing_docker_files(host, path):
assert host.file(path).exists

View File

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

View File

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

View File

@@ -1,48 +0,0 @@
import os
import pytest
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('ui')
@pytest.mark.parametrize("name", [
("ui_ui_1")
])
def test_docker_containers(host, name):
container = host.docker(name)
assert container.is_running
@pytest.mark.parametrize("service", [
("tokenbridge-ui"),
("rsyslog")
])
def test_services(host, service):
assert host.service(service).is_enabled
assert host.service(service).is_running
@pytest.mark.parametrize("filename", [
("/etc/rsyslog.d/32-ui-docker.conf"),
("/etc/rsyslog.d/37-ui-remote-logging.conf")
])
def test_logging(host, filename):
assert host.file(filename).exists
assert host.file(filename).mode == 0o0644
def test_index_page_title(host):
assert host.run_test(
'curl -s http://localhost:3001 | '
'grep "<title>" | '
'grep -q "TokenBridge UI app"'
)
def test_index_page_error(host):
assert host.run_expect(
[1],
'curl -s http://localhost:3001 | '
'grep -i -q "error"'
)

View File

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

View File

@@ -1,52 +0,0 @@
---
driver:
name: docker
platforms:
- name: oracle-amb-host
groups:
- ultimate
- amb
children:
- oracle
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-amb-stake-erc-to-erc-host
groups:
- ultimate
- amb-stake-erc-to-erc
children:
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
prepare: ../prepare.yml
converge: ../ultimate-commons/converge.yml
inventory:
host_vars:
oracle-amb-host:
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-amb-stake-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:
name: flake8
scenario:
name: ultimate-amb-stake-erc-to-erc
test_sequence:
- cleanup
- destroy
- syntax
- create
- prepare
- converge

View File

@@ -18,5 +18,6 @@
- oracle_net_rabbit_bridge_transfer
- oracle_net_rabbit_bridge_senderhome
- oracle_net_rabbit_bridge_senderforeign
- oracle_net_rabbit_bridge_convert_to_chai_worker
delegate_to: 127.0.0.1
become: false

View File

@@ -13,17 +13,6 @@ platforms:
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-erc-to-erc-host
groups:
- ultimate
- erc-to-erc
children:
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
@@ -34,9 +23,6 @@ provisioner:
oracle-erc-to-erc-host:
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-erc-to-erc-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:

View File

@@ -13,17 +13,6 @@ platforms:
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-erc-to-native-host
groups:
- ultimate
- erc-to-native
children:
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
@@ -36,9 +25,6 @@ provisioner:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_HOME_START_BLOCK: 1
ORACLE_FOREIGN_START_BLOCK: 1
ui-erc-to-native-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:

View File

@@ -13,17 +13,6 @@ platforms:
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: ui-native-to-erc-host
groups:
- ultimate
- native-to-erc
children:
- ui
image: ubuntu:16.04
privileged: true
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
provisioner:
name: ansible
playbooks:
@@ -34,9 +23,6 @@ provisioner:
oracle-native-to-erc-host:
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ui-native-to-erc-host:
COMMON_HOME_RPC_URL: "http://localhost:8541"
COMMON_FOREIGN_RPC_URL: "http://localhost:8542"
verifier:
name: testinfra
lint:

View File

@@ -34,14 +34,6 @@ cp hosts.yml.example hosts.yml
#syslog_server_port: "<protocol>://<ip>:<port>" # When this parameter is set all bridge logs will be redirected to <ip>:<port> address.
<host_ip_B>:
# (...)
ui:
hosts:
<host_ip_B>:
ansible_user: <user>
#syslog_server_port: "<protocol>://<ip>:<port>"
<host_ip_C>:
ansible_user: <user>
#syslog_server_port: "<protocol>://<ip>:<port>"
monitor:
hosts:
<host_ip_B>:
@@ -50,18 +42,7 @@ cp hosts.yml.example hosts.yml
#monitor_cron_schedule: "*/4 * * * *" # When this parameter is set, it will overwrite default schedule for performing checks
```
The config above would install the Oracle on `<host_ip_A>`, UI on `<host_ip_C>`, and both Oracle, UI and Monitor on `<host_ip_B>`.
Example config for installing only UI:
```yaml
<bridge_name>:
children:
oracle:
hosts:
ui:
hosts:
<host_ip>:
ansible_user: <user>
The config above would install the Oracle on `<host_ip_A>`, and both Oracle and Monitor on `<host_ip_B>`.
```
| Value | Description |

View File

@@ -44,7 +44,6 @@ The deployed components have the following services:
Component | Service Name
--- | ---
Oracle | poabridge
UI | tokenbridge-ui
Monitor | tokenbridge-monitor
Use the default `SysVinit` commands to `start`, `stop`, `restart`, and `rebuild` the service and to check the `status` of the service.

View File

@@ -1,4 +0,0 @@
---
COMMON_HOME_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
UI_PORT: 3003

View File

@@ -1,19 +1,14 @@
---
## General settings
ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE"
UI_NATIVE_TOKEN_DISPLAY_NAME: "xDai"
## Home contract
COMMON_HOME_RPC_URL: "https://dai.poa.network"
UI_HOME_NETWORK_DISPLAY_NAME: "xDai chain"
UI_HOME_WITHOUT_EVENTS: false
COMMON_HOME_BRIDGE_ADDRESS: "0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://mainnet.infura.io"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Ethereum Mainnet"
UI_FOREIGN_WITHOUT_EVENTS: false
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
@@ -30,20 +25,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The TokenBridge serves as a method of transferring MakerDAO stable tokens between the Ethereum network to xDai chain in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/dai/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/mainnet/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/dai/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/mainnet/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "https://dai-bridge.poa.network"
## Monitor
MONITOR_BRIDGE_NAME: "xdai"
MONITOR_PORT: 3003

View File

@@ -2,5 +2,4 @@
ORACLE_BRIDGE_MODE: "ERC_TO_ERC"
COMMON_HOME_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
UI_PORT: 3001
MONITOR_PORT: 3011

View File

@@ -2,5 +2,4 @@
ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE"
COMMON_HOME_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
UI_PORT: 3002
MONITOR_PORT: 3012

View File

@@ -2,19 +2,14 @@
## General settings
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC"
ORACLE_LOG_LEVEL: debug
UI_NATIVE_TOKEN_DISPLAY_NAME: "POA"
## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
UI_HOME_WITHOUT_EVENTS: false
COMMON_HOME_BRIDGE_ADDRESS: "0x98aFdE294f1C46aA0a27Cc4049ED337F879d8976"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
UI_FOREIGN_WITHOUT_EVENTS: false
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x5a584f4C30B36f282848dAc9a2b20E7BEF481981"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
@@ -32,20 +27,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 1000000000 # in wei
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/sokol/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "http://localhost:3001"
## Monitor
MONITOR_BRIDGE_NAME: "bridge"
MONITOR_PORT: 3003

View File

@@ -2,5 +2,4 @@
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC"
COMMON_HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
UI_PORT: 3000
MONITOR_PORT: 3010

View File

@@ -1,19 +1,14 @@
---
## General settings
UI_NATIVE_TOKEN_DISPLAY_NAME: "POA"
ORACLE_ALLOW_HTTP_FOR_RPC: yes
ORACLE_LOG_LEVEL: debug
## Home contract
COMMON_HOME_RPC_URL: "http://parity1:8545"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
UI_HOME_WITHOUT_EVENTS: false
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
UI_FOREIGN_WITHOUT_EVENTS: false
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
## Home Gasprice
@@ -30,20 +25,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 1000000000 # in wei
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
#ui
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The POA cross-chain bridge serves as a method of transferring POA native tokens from the POA Network to the Ethereum network in a quick and cost-efficient manner."
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/poa/sokol/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/kovan/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "core"
UI_PUBLIC_URL: "http://localhost"
#monitor
MONITOR_BRIDGE_NAME: "bridge"
MONITOR_CACHE_EVENTS: "true"

View File

@@ -1,19 +1,14 @@
---
## General settings
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC"
UI_NATIVE_TOKEN_DISPLAY_NAME: "ETC"
## Home contract
COMMON_HOME_RPC_URL: "https://www.ethercluster.com/etc"
UI_HOME_NETWORK_DISPLAY_NAME: "Ethereum Classic"
UI_HOME_WITHOUT_EVENTS: false
COMMON_HOME_BRIDGE_ADDRESS: "0x073081832B4Ecdce79d4D6753565c85Ba4b3BeA9"
ORACLE_HOME_RPC_POLLING_INTERVAL: 7000
## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://mainnet.infura.io/"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Ethereum Mainnet"
UI_FOREIGN_WITHOUT_EVENTS: false
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x0cB781EE62F815bdD9CD4c2210aE8600d43e7040"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 7000
@@ -31,20 +26,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 10000000000 # in wei
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
## UI
UI_TITLE: "TokenBridge UI app - %c"
UI_OG_TITLE: "POA Bridge UI"
UI_DESCRIPTION: "The TokenBridge serves as a method of transferring native tokens from the Ethereum Classic Network to the Ethereum network in a quick and cost-efficient manner."
UI_PORT: 3001
UI_HOME_EXPLORER_TX_TEMPLATE: https://blockscout.com/etc/mainnet/tx/%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE: https://blockscout.com/eth/mainnet/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/etc/mainnet/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE: https://blockscout.com/eth/mainnet/address/%s
UI_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
UI_STYLES: "classic"
UI_PUBLIC_URL: "https://wetc.app"
## Monitor
MONITOR_BRIDGE_NAME: "wetc"
MONITOR_PORT: 3003

View File

@@ -7,11 +7,6 @@ sokol-kovan:
ansible_user: ubuntu
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
#syslog_server_port: "udp://127.0.0.1:514"
ui:
hosts:
127.0.0.1:
ansible_user: ubuntu
#syslog_server_port: "udp://127.0.0.1:514"
monitor:
hosts:
127.0.0.1:

View File

@@ -33,7 +33,7 @@
mode: "0755"
- name: Upgrade pip version
shell: pip3 install --upgrade pip
shell: pip3 install --upgrade pip==19.3.1
- name: Install python docker library
shell: pip3 install docker docker-compose setuptools

View File

@@ -1,3 +0,0 @@
---
dependencies:
- role: common

View File

@@ -1,6 +0,0 @@
---
- name: Build the containers
shell: docker-compose build
args:
chdir: "{{ bridge_path }}/ui"
when: skip_build is undefined

View File

@@ -1,41 +0,0 @@
---
- name: Slurp docker compose file
slurp:
src: "{{ bridge_path }}/ui/docker-compose.yml"
register: docker_compose_slurp
- name: Parse docker compose file
set_fact:
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
- name: Set logger to remote server
set_fact:
docker_compose_parsed: "{{ docker_compose_parsed |combine({'services': {item: {'logging': {'driver': 'syslog','options': {'tag': '{{.Name}}/{{.ID}}'}}}}}, recursive=True) }}"
with_items: "{{ docker_compose_parsed.services }}"
- name: Write new docker-compose file
copy:
content: "{{ docker_compose_parsed | to_yaml }}"
dest: "{{ bridge_path }}/ui/docker-compose.yml"
- name: Set the local container logs configuration file
template:
src: 32-ui-docker.conf.j2
dest: /etc/rsyslog.d/32-ui-docker.conf
owner: root
group: root
mode: 0644
- name: Set the log configuration file to send container logs to remote server
template:
src: 37-ui-remote-logging.conf.j2
dest: /etc/rsyslog.d/37-ui-remote-logging.conf
owner: root
group: root
mode: 0644
when: syslog_server_port is defined
- name: restart rsyslog
service:
name: rsyslog
state: restarted

View File

@@ -1,5 +0,0 @@
---
- include_tasks: pre_config.yml
- include_tasks: logging.yml
- include_tasks: jumpbox.yml
- include_tasks: servinstall.yml

View File

@@ -1,7 +0,0 @@
---
- name: Install .env config
template:
src: .env.j2
dest: "{{ bridge_path }}/ui/.env"
owner: "{{ compose_service_user }}"
mode: '0640'

View File

@@ -1,20 +0,0 @@
# This role creates a tokenbridge-ui service which is designed to manage docker-compose ui deployment.
# /etc/init.d/tokenbridge-ui start, status, stop, restart - does what the services usually do in such cases.
# /etc/init.d/tokenbridge-ui rebuild - builds a new ui deployment from scratch.
---
- name: "Set the service"
template:
src: tokenbridge-ui.j2
dest: "/etc/init.d/tokenbridge-ui"
owner: root
mode: 755
- name: "Enable the service"
service:
name: "tokenbridge-ui"
state: started
enabled: yes
use: service
- name: Start the service
shell: service tokenbridge-ui start

View File

@@ -1,42 +0,0 @@
COMMON_HOME_BRIDGE_ADDRESS={{ COMMON_HOME_BRIDGE_ADDRESS }}
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
COMMON_HOME_RPC_URL={{ COMMON_HOME_RPC_URL }}
UI_NATIVE_TOKEN_DISPLAY_NAME={{ UI_NATIVE_TOKEN_DISPLAY_NAME }}
UI_HOME_NETWORK_DISPLAY_NAME={{ UI_HOME_NETWORK_DISPLAY_NAME }}
UI_FOREIGN_NETWORK_DISPLAY_NAME={{ UI_FOREIGN_NETWORK_DISPLAY_NAME }}
UI_HOME_WITHOUT_EVENTS={{ UI_HOME_WITHOUT_EVENTS }}
UI_FOREIGN_WITHOUT_EVENTS={{ UI_FOREIGN_WITHOUT_EVENTS }}
UI_HOME_EXPLORER_TX_TEMPLATE={{ UI_HOME_EXPLORER_TX_TEMPLATE }}
UI_FOREIGN_EXPLORER_TX_TEMPLATE={{ UI_FOREIGN_EXPLORER_TX_TEMPLATE }}
UI_HOME_EXPLORER_ADDRESS_TEMPLATE={{ UI_HOME_EXPLORER_ADDRESS_TEMPLATE }}
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE={{ UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE }}
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
COMMON_HOME_GAS_PRICE_SPEED_TYPE={{ COMMON_HOME_GAS_PRICE_SPEED_TYPE }}
{% endif %}
COMMON_HOME_GAS_PRICE_FALLBACK={{ COMMON_HOME_GAS_PRICE_FALLBACK }}
UI_HOME_GAS_PRICE_UPDATE_INTERVAL={{ UI_HOME_GAS_PRICE_UPDATE_INTERVAL }}
{% if COMMON_HOME_GAS_PRICE_FACTOR | default('') != '' %}
COMMON_HOME_GAS_PRICE_FACTOR={{ COMMON_HOME_GAS_PRICE_FACTOR }}
{% endif %}
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL={{ COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL }}
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE={{ COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE }}
COMMON_FOREIGN_GAS_PRICE_FALLBACK={{ COMMON_FOREIGN_GAS_PRICE_FALLBACK }}
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL={{ UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL }}
COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
# Default
UI_TITLE={{ UI_TITLE }}
UI_OG_TITLE={{ UI_OG_TITLE }}
UI_DESCRIPTION={{ UI_DESCRIPTION }}
UI_PORT={{ UI_PORT }}
UI_PUBLIC_URL={{ UI_PUBLIC_URL }}
UI_STYLES={{ UI_STYLES }}

View File

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

View File

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

View File

@@ -1,76 +0,0 @@
#! /bin/bash
### BEGIN INIT INFO
# Provides: tokenbridge-ui
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start daemon at boot time
# Description: Enable service provided by daemon.
### END INIT INFO
WORKDIR="{{ '/home/' + compose_service_user | default('poadocker') + '/' + bridge_path + '/ui' if bridge_path[:1] != "/" else bridge_path + '/ui' }}"
start(){
echo "Starting TokenBridge UI.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose up --detach
}
stop(){
echo "Stopping TokenBridge UI.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sleep 2
}
status(){
echo "TokenBridge UI status:"
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose ps
}
rebuild(){
echo "Rebuild TokenBridge UI.."
cd $WORKDIR
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose down -v
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose rm -fv
sudo -u "{{ compose_service_user }}" /usr/local/bin/docker-compose up --detach --force-recreate --no-deps --build
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
echo "Restarting TokenBridge UI.."
stop
start
;;
rebuild)
rebuild
;;
*)
echo $"Usage: $0 {start|stop|restart|rebuild|status}"
exit 1
;;
esac
exit 0

View File

@@ -4,11 +4,6 @@
become: true
roles:
- { role: oracle }
- name: Install UI
hosts: ui
become: true
roles:
- { role: ui }
- name: Install Monitor
hosts: monitor
become: true

View File

@@ -1,7 +0,0 @@
ARG DOCKER_IMAGE_BASE
ARG UI_TAG
FROM ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-ui:${UI_TAG}
ARG DOT_ENV_PATH
COPY ${DOT_ENV_PATH} ./.env

View File

@@ -24,14 +24,12 @@ Shut down and cleans up containers, networks, services, running scripts:
| oracle | Launches Oracle containers |
| oracle-validator-2 | Launches Oracle containers for second validator |
| oracle-validator-3 | Launches Oracle containers for third validator |
| ui | Launches UI containers |
| blocks | Auto mines blocks |
| monitor | Launches Monitor containers |
| native-to-erc | Creates infrastructure for ultimate e2e testing, for native-to-erc type of bridge |
| erc-to-native | Creates infrastructure for ultimate e2e testing, for erc-to-native type of bridge |
| erc-to-erc | Creates infrastructure for ultimate e2e testing, for erc-to-erc type of bridge |
| amb | Creates infrastructure for ultimate e2e testing, for arbitrary message type of bridge |
| ultimate-amb-stake-erc-to-erc | Creates infrastructure for ultimate e2e testing, for stake token bridge |
#### Ultimate e2e testing

View File

@@ -15,13 +15,15 @@ It runs the e2e tests on components deployed using the deployment playbooks.
Run the Parity nodes, deploy the bridge contracts, deploy Oracle using the deployment playbook.
```bash
./up.sh deploy native-to-erc blocks
./e2e-commons/up.sh deploy blocks
./deployment-e2e/molecule.sh ultimate-native-to-erc
```
### 2. Run the E2E tests
```
cd ui-e2e; yarn mocha -g "NATIVE_TO_ERC" -b ./test.js
```bash
cd e2e-commons
docker-compose run -e ULTIMATE=true e2e yarn workspace oracle-e2e run native-to-erc
```
## Diagram

View File

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

View File

@@ -8,8 +8,6 @@ while [ "$1" != "" ]; do
docker-compose build oracle
elif [ "$1" == "monitor" ]; then
docker-compose build monitor
elif [ "$1" == "ui" ]; then
docker-compose build ui
elif [ "$1" == "alm" ]; then
docker-compose build alm
fi

View File

@@ -1,23 +0,0 @@
COMMON_HOME_BRIDGE_ADDRESS=0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC
COMMON_FOREIGN_BRIDGE_ADDRESS=0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC
COMMON_FOREIGN_RPC_URL=http://localhost:8542
COMMON_HOME_RPC_URL=http://localhost:8541
UI_NATIVE_TOKEN_DISPLAY_NAME=POA
UI_HOME_NETWORK_DISPLAY_NAME=Sokol
UI_FOREIGN_NETWORK_DISPLAY_NAME=Kovan
UI_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=5000000000
UI_HOME_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3003
UI_STYLES=stake

View File

@@ -1,23 +0,0 @@
COMMON_HOME_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda
COMMON_FOREIGN_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda
COMMON_FOREIGN_RPC_URL=http://localhost:8542
COMMON_HOME_RPC_URL=http://localhost:8541
UI_NATIVE_TOKEN_DISPLAY_NAME=POA
UI_HOME_NETWORK_DISPLAY_NAME=Sokol
UI_FOREIGN_NETWORK_DISPLAY_NAME=Kovan
UI_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=5000000000
UI_HOME_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3002
UI_STYLES=core

View File

@@ -1,23 +0,0 @@
COMMON_HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E
COMMON_FOREIGN_BRIDGE_ADDRESS=0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127
COMMON_FOREIGN_RPC_URL=http://localhost:8542
COMMON_HOME_RPC_URL=http://localhost:8541
UI_NATIVE_TOKEN_DISPLAY_NAME=POA
UI_HOME_NETWORK_DISPLAY_NAME=Sokol
UI_FOREIGN_NETWORK_DISPLAY_NAME=Kovan
UI_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=5000000000
UI_HOME_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3001
UI_STYLES=core

View File

@@ -1,23 +0,0 @@
COMMON_HOME_BRIDGE_ADDRESS=0x32198D570fffC7033641F8A9094FFDCaAEF42624
COMMON_FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769
COMMON_FOREIGN_RPC_URL=http://localhost:8542
COMMON_HOME_RPC_URL=http://localhost:8541
UI_NATIVE_TOKEN_DISPLAY_NAME=POA
UI_HOME_NETWORK_DISPLAY_NAME=Sokol
UI_FOREIGN_NETWORK_DISPLAY_NAME=Kovan
UI_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s
UI_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
UI_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s
COMMON_HOME_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_HOME_GAS_PRICE_SPEED_TYPE=standard
COMMON_HOME_GAS_PRICE_FALLBACK=5000000000
UI_HOME_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_HOME_GAS_PRICE_FACTOR=1
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL=https://gasprice.poa.network/
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE=standard
COMMON_FOREIGN_GAS_PRICE_FALLBACK=5000000000
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000
COMMON_FOREIGN_GAS_PRICE_FACTOR=1
UI_PORT=3000
UI_STYLES=core

View File

@@ -39,7 +39,6 @@
"home": "0x32198D570fffC7033641F8A9094FFDCaAEF42624",
"foreign": "0x2B6871b9B02F73fa24F4864322CdC78604207769",
"foreignToken": "0xdbeE25CbE97e4A5CC6c499875774dc7067E9426B",
"ui": "http://localhost:3000",
"monitor": "http://monitor:3010/bridge"
},
"ercToErcBridge": {
@@ -47,7 +46,6 @@
"foreign": "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127",
"homeToken": "0x792455a6bCb62Ed4C4362D323E0590654CA4765c",
"foreignToken": "0x3C665A31199694Bf723fD08844AD290207B5797f",
"ui": "http://localhost:3001",
"monitor": "http://monitor-erc20:3011/bridge"
},
"ercToNativeBridge": {
@@ -55,7 +53,6 @@
"foreign": "0x488Af810997eD1730cB3a3918cD83b3216E6eAda",
"foreignToken": "0x7cc4b1851c35959d34e635a470f6b5c43ba3c9c9",
"chaiToken": "0x06af07097c9eeb7fd685c692751d5c66db49c215",
"ui": "http://localhost:3002",
"monitor": "http://monitor-erc20-native:3012/bridge"
},
"amb": {
@@ -63,17 +60,9 @@
"foreign": "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0",
"homeBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"foreignBox": "0x6C4EaAb8756d53Bf599FFe2347FAFF1123D6C8A1",
"blockedHomeBox": "0x612E8bd50A7b1F009F43f2b8679E9B8eD91eb5CE",
"blockedHomeBox": "0x6f359aC418a5f7538F7755A33C9c7dDf38785524",
"monitor": "http://monitor-amb:3013/bridge"
},
"ambStakeErcToErc": {
"home": "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC",
"foreign": "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC",
"homeToken": "0x6f359aC418a5f7538F7755A33C9c7dDf38785524",
"foreignToken": "0x6f359aC418a5f7538F7755A33C9c7dDf38785524",
"blockReward": "0xF9698Eb93702dfdd0e2d802088d4c21822a8A977",
"ui": "http://localhost:3003"
},
"homeRPC": {
"URL": "http://parity1:8545",
"ID": "77"

View File

@@ -1,30 +0,0 @@
BRIDGE_MODE=STAKE_AMB_ERC_TO_ERC
DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9
DEPLOYMENT_GAS_LIMIT_EXTRA=0.2
HOME_DEPLOYMENT_GAS_PRICE=10000000000
FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000
GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50
HOME_STAKE_TOKEN_ADDRESS=0x6f359aC418a5f7538F7755A33C9c7dDf38785524
FOREIGN_STAKE_TOKEN_ADDRESS=0x6f359aC418a5f7538F7755A33C9c7dDf38785524
BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977
HOME_RPC_URL=http://parity1:8545
HOME_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
HOME_DAILY_LIMIT=30000000000000000000000000
HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000
HOME_MIN_AMOUNT_PER_TX=10000000000000000
HOME_TRANSACTIONS_FEE=0
FOREIGN_RPC_URL=http://parity2:8545
FOREIGN_BRIDGE_OWNER=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_UPGRADEABLE_ADMIN=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b
FOREIGN_DAILY_LIMIT=15000000000000000000000000
FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000
FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000
HOME_AMB_BRIDGE=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
FOREIGN_AMB_BRIDGE=0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0
HOME_MEDIATOR_REQUEST_GAS_LIMIT=2000000
FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT=2000000
BRIDGEABLE_TOKEN_NAME='not used'
BRIDGEABLE_TOKEN_SYMBOL='not used'
BRIDGEABLE_TOKEN_DECIMALS='18'

View File

@@ -68,49 +68,6 @@ services:
- '../e2e-commons/access-lists/allowance_list.txt:/mono/oracle/access-lists/allowance_list.txt'
networks:
- ultimate
ui:
image: ${DOCKER_IMAGE_BASE:-tokenbridge}/tokenbridge-e2e-ui:${UI_TAG:-local}
build:
context: ..
dockerfile: ui/Dockerfile
args:
DOT_ENV_PATH: e2e-commons/components-envs/ui.env
command: "true"
networks:
- ultimate
ui-erc20:
build:
context: ..
dockerfile: e2e-commons/Dockerfile.ui
args:
DOCKER_IMAGE_BASE: ${DOCKER_IMAGE_BASE:-tokenbridge}
UI_TAG: ${UI_TAG:-local}
DOT_ENV_PATH: e2e-commons/components-envs/ui-erc20.env
command: "true"
networks:
- ultimate
ui-erc20-native:
build:
context: ..
dockerfile: e2e-commons/Dockerfile.ui
args:
DOCKER_IMAGE_BASE: ${DOCKER_IMAGE_BASE:-tokenbridge}
UI_TAG: ${UI_TAG:-local}
DOT_ENV_PATH: e2e-commons/components-envs/ui-erc20-native.env
command: "true"
networks:
- ultimate
ui-amb-stake-erc20-erc20:
build:
context: ..
dockerfile: e2e-commons/Dockerfile.ui
args:
DOCKER_IMAGE_BASE: ${DOCKER_IMAGE_BASE:-tokenbridge}
UI_TAG: ${UI_TAG:-local}
DOT_ENV_PATH: e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env
command: "true"
networks:
- ultimate
alm:
image: ${DOCKER_IMAGE_BASE:-tokenbridge}/tokenbridge-e2e-alm:${ALM_TAG:-local}
build:

View File

@@ -3,7 +3,7 @@ cd $(dirname $0)
if [ $CI ]; then exit $rc; fi
ps | grep node | grep -v grep | 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 -p validator2 down
docker-compose -p validator3 down

View File

@@ -8,8 +8,6 @@ while [ "$1" != "" ]; do
docker-compose pull oracle
elif [ "$1" == "monitor" ]; then
docker-compose pull monitor
elif [ "$1" == "ui" ]; then
docker-compose pull ui
elif [ "$1" == "alm" ]; then
docker-compose pull alm
fi

View File

@@ -44,16 +44,6 @@ cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js
cd - > /dev/null
echo -e "\n\n############ Deploying amb stake erc to erc ############\n"
cp "$ENVS_PATH/amb-stake-erc-to-erc.env" "$DEPLOY_PATH/.env"
node deployMultiBridgeToken.js
node deployBridgeTokenRewardable.js
cd "$DEPLOY_PATH"
node deploy.js
cd - > /dev/null
node setupStakeTokens.js
cd - > /dev/null
echo -e "\n\n############ Deploying one more test contract for amb ############\n"
cd "$DEPLOY_PATH"
node src/utils/deployTestBox.js

View File

@@ -1,111 +0,0 @@
const path = require('path')
const { ambStakeErcToErc, validator, secondValidator, thirdValidator } = require('../constants.json')
const contractsPath = '../../contracts'
require('dotenv').config({
path: path.join(__dirname, contractsPath, '/deploy/.env')
})
const { sendRawTxHome, sendRawTxForeign, privateKeyToAddress } = require(`${contractsPath}/deploy/src/deploymentUtils`)
const { web3Home, web3Foreign, deploymentPrivateKey } = require(`${contractsPath}/deploy/src/web3`)
const BlockReward = require(`${contractsPath}/build/contracts/BlockRewardMock.json`)
const ERC677BridgeTokenRewardable = require(`${contractsPath}/build/contracts/ERC677BridgeTokenRewardable.json`)
const ERC677MultiBridgeToken = require(`${contractsPath}/build/contracts/ERC677MultiBridgeToken.json`)
const { DEPLOYMENT_ACCOUNT_PRIVATE_KEY } = process.env
const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY)
async function setupStakeTokens() {
try {
let homeNonce = await web3Home.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
const blockReward = new web3Home.eth.Contract(BlockReward.abi, ambStakeErcToErc.blockReward)
console.log('\n[Home] Set token in block reward')
const setTokenData = await blockReward.methods.setToken(ambStakeErcToErc.homeToken).encodeABI()
await sendRawTxHome({
data: setTokenData,
nonce: homeNonce,
to: blockReward.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] Set validators rewards in block reward')
const setValidatorsRewardsData = await blockReward.methods
.setValidatorsRewards([validator.address, secondValidator.address, thirdValidator.address])
.encodeABI()
await sendRawTxHome({
data: setValidatorsRewardsData,
nonce: homeNonce,
to: blockReward.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
const homeToken = new web3Home.eth.Contract(ERC677BridgeTokenRewardable.abi, ambStakeErcToErc.homeToken)
console.log('\n[Home] Set block reward in token')
const setBlockRewardData = await homeToken.methods.setBlockRewardContract(ambStakeErcToErc.blockReward).encodeABI()
await sendRawTxHome({
data: setBlockRewardData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] Add bridge in token')
const addBridgeData = await homeToken.methods.addBridge(ambStakeErcToErc.home).encodeABI()
await sendRawTxHome({
data: addBridgeData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
console.log('\n[Home] transfer token ownership to mediator')
const transferOwnershipData = await homeToken.methods.transferOwnership(ambStakeErcToErc.home).encodeABI()
await sendRawTxHome({
data: transferOwnershipData,
nonce: homeNonce,
to: homeToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.HOME_RPC_URL
})
homeNonce++
let foreignNonce = await web3Foreign.eth.getTransactionCount(DEPLOYMENT_ACCOUNT_ADDRESS)
const foreignToken = new web3Foreign.eth.Contract(ERC677MultiBridgeToken.abi, ambStakeErcToErc.foreignToken)
console.log('\n[Foreign] Add bridge in token')
const addBridgeForeignData = await homeToken.methods.addBridge(ambStakeErcToErc.foreign).encodeABI()
await sendRawTxForeign({
data: addBridgeForeignData,
nonce: foreignNonce,
to: foreignToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.FOREIGN_RPC_URL
})
foreignNonce++
console.log('\n[Foreign] transfer token ownership to mediator')
const transferOwnershipForeignData = await homeToken.methods.transferOwnership(ambStakeErcToErc.foreign).encodeABI()
await sendRawTxForeign({
data: transferOwnershipForeignData,
nonce: foreignNonce,
to: foreignToken.options.address,
privateKey: deploymentPrivateKey,
url: process.env.FOREIGN_RPC_URL
})
foreignNonce++
} catch (e) {
console.log(e)
throw e
}
}
setupStakeTokens()

View File

@@ -42,6 +42,7 @@ startValidator () {
fi
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn sender:home
docker-compose $1 run $2 $3 -d oracle-erc20-native yarn sender:foreign
docker-compose $1 run $2 $3 -d oracle yarn manager:shutdown
}
startAMBValidator () {
@@ -52,6 +53,7 @@ startAMBValidator () {
docker-compose $1 run $2 $3 -d oracle-amb yarn watcher:affirmation-request
docker-compose $1 run $2 $3 -d oracle-amb yarn sender:home
docker-compose $1 run $2 $3 -d oracle-amb yarn sender:foreign
docker-compose $1 run $2 $3 -d oracle-amb yarn manager:shutdown
}
while [ "$1" != "" ]; do
@@ -73,18 +75,6 @@ while [ "$1" != "" ]; do
startValidator "$oracle3name" "$oracle3Values" "$oracle3comp" "redis3" "rabbit3"
fi
if [ "$1" == "ui" ]; then
# this should only rebuild last 3 steps from ui/Dockerfile
docker-compose build ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
docker-compose up -d ui ui-erc20 ui-erc20-native ui-amb-stake-erc20-erc20
docker-compose run -d -p 3000:3000 ui yarn start
docker-compose run -d -p 3001:3001 ui-erc20 yarn start
docker-compose run -d -p 3002:3002 ui-erc20-native yarn start
docker-compose run -d -p 3003:3003 ui-amb-stake-erc20-erc20 yarn start
fi
if [ "$1" == "alm" ]; then
docker-compose up -d alm
@@ -120,13 +110,7 @@ while [ "$1" != "" ]; do
fi
if [ "$1" == "alm-e2e" ]; then
docker-compose up -d redis rabbit
docker-compose run -d oracle-amb yarn watcher:signature-request
docker-compose run -d oracle-amb yarn watcher:collected-signatures
docker-compose run -d oracle-amb yarn watcher:affirmation-request
docker-compose run -d oracle-amb yarn sender:home
docker-compose run -d oracle-amb yarn sender:foreign
startAMBValidator "" "" "" "redis" "rabbit"
oracle2name="-p validator2"
oracle2Values="-e ORACLE_VALIDATOR_ADDRESS=0xdCC784657C78054aa61FbcFFd2605F32374816A4 -e ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY=5a5c3645d0f04e9eb4f27f94ed4c244a225587405b8838e7456f7781ce3a9513"

View File

@@ -1,18 +1,19 @@
require('dotenv').config()
const logger = require('./logger')('alerts')
const eventsInfo = require('./utils/events')
const { processedMsgNotDelivered, eventWithoutReference } = require('./utils/message')
const { BRIDGE_MODES } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
const { web3Home, web3Foreign } = require('./utils/web3')
async function main() {
async function main(eventsInfo) {
const {
homeBlockNumber,
foreignBlockNumber,
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
bridgeMode
} = await eventsInfo()
} = eventsInfo
let xSignatures
let xAffirmations
@@ -24,8 +25,6 @@ async function main() {
xAffirmations = foreignToHomeConfirmations.filter(eventWithoutReference(foreignToHomeRequests))
}
logger.debug('building misbehavior blocks')
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const baseRange = [false, false, false, false, false]
const xSignaturesMisbehavior = buildRangesObject(

View File

@@ -1,6 +1,7 @@
require('dotenv').config()
const logger = require('./logger')('checkWorker2')
const eventsStats = require('./eventsStats')
const getEventsInfo = require('./utils/events')
const alerts = require('./alerts')
const { writeFile, createDir } = require('./utils/file')
const { saveCache } = require('./utils/web3Cache')
@@ -10,8 +11,10 @@ const { MONITOR_BRIDGE_NAME } = process.env
async function checkWorker2() {
try {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling getEventsInfo()')
const eventsInfo = await getEventsInfo()
logger.debug('calling eventsStats()')
const evStats = await eventsStats()
const evStats = await eventsStats(eventsInfo)
if (!evStats) throw new Error('evStats is empty: ' + JSON.stringify(evStats))
evStats.ok =
(evStats.onlyInHomeDeposits || evStats.home.deliveredMsgNotProcessedInForeign).length === 0 &&
@@ -22,7 +25,7 @@ async function checkWorker2() {
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/eventsStats.json`, evStats)
logger.debug('calling alerts()')
const _alerts = await alerts()
const _alerts = await alerts(eventsInfo)
if (!_alerts) throw new Error('alerts is empty: ' + JSON.stringify(_alerts))
_alerts.ok = !_alerts.executeAffirmations.mostRecentTxHash && !_alerts.executeSignatures.mostRecentTxHash
_alerts.health = true

View File

@@ -2,6 +2,7 @@ require('dotenv').config()
const logger = require('./logger')('checkWorker3')
const stuckTransfers = require('./stuckTransfers')
const detectMediators = require('./detectMediators')
const detectFailures = require('./detectFailures')
const { writeFile, createDir } = require('./utils/file')
const { web3Home } = require('./utils/web3')
const { saveCache } = require('./utils/web3Cache')
@@ -24,11 +25,19 @@ async function checkWorker3() {
logger.debug('Done')
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling detectMediators()')
const mediators = await detectMediators(bridgeMode)
mediators.ok = true
mediators.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/mediators.json`, mediators)
logger.debug('calling detectFailures()')
const failures = await detectFailures(bridgeMode)
failures.ok = true
failures.health = true
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/failures.json`, failures)
saveCache()
logger.debug('Done')
}

86
monitor/detectFailures.js Normal file
View File

@@ -0,0 +1,86 @@
require('dotenv').config()
const logger = require('./logger')('detectFailures.js')
const eventsInfo = require('./utils/events')
const { normalizeAMBMessageEvent } = require('../commons')
const { getHomeBlockNumber, getForeignBlockNumber } = require('./utils/web3')
function normalize(events) {
const requests = {}
events.forEach(event => {
const request = normalizeAMBMessageEvent(event)
request.requestTx = event.transactionHash
requests[request.messageId] = request
})
return confirmation => {
const request = requests[confirmation.returnValues.messageId] || {}
return {
...request,
status: false,
executionTx: confirmation.transactionHash,
executionBlockNumber: confirmation.blockNumber
}
}
}
async function main(mode) {
const {
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests
} = await eventsInfo(mode)
const hasFailed = event => !event.returnValues.status
const cmp = (a, b) => b.executionBlockNumber - a.executionBlockNumber
const failedForeignToHomeMessages = foreignToHomeConfirmations
.filter(hasFailed)
.map(normalize(foreignToHomeRequests))
.sort(cmp)
const failedHomeToForeignMessages = homeToForeignConfirmations
.filter(hasFailed)
.map(normalize(homeToForeignRequests))
.sort(cmp)
const homeBlockNumber = await getHomeBlockNumber()
const foreignBlockNumber = await getForeignBlockNumber()
const blockRanges = [1000, 10000, 100000, 1000000]
const rangeNames = [
`last${blockRanges[0]}blocks`,
...blockRanges.slice(0, blockRanges.length - 1).map((n, i) => `last${n}to${blockRanges[i + 1]}blocks`),
`before${blockRanges[blockRanges.length - 1]}blocks`
]
const countFailures = (failedMessages, lastBlockNumber) => {
const result = {}
rangeNames.forEach(name => {
result[name] = 0
})
failedMessages.forEach(message => {
const blockAge = lastBlockNumber - message.executionBlockNumber
let rangeIndex = blockRanges.findIndex(n => n > blockAge)
if (rangeIndex === -1) {
rangeIndex = blockRanges.length
}
result[rangeNames[rangeIndex]] += 1
})
return result
}
logger.debug('Done')
return {
homeToForeign: {
total: failedHomeToForeignMessages.length,
stats: countFailures(failedHomeToForeignMessages, foreignBlockNumber),
lastFailures: failedHomeToForeignMessages.slice(0, 5)
},
foreignToHome: {
total: failedForeignToHomeMessages.length,
stats: countFailures(failedForeignToHomeMessages, homeBlockNumber),
lastFailures: failedForeignToHomeMessages.slice(0, 5)
},
lastChecked: Math.floor(Date.now() / 1000)
}
}
module.exports = main

View File

@@ -1,5 +1,5 @@
require('dotenv').config()
const logger = require('./logger')('stuckTransfers.js')
const logger = require('./logger')('detectMediators.js')
const { isHomeContract, isForeignContract } = require('./utils/web3Cache')
const eventsInfo = require('./utils/events')
const { getHomeTxSender, getForeignTxSender } = require('./utils/web3Cache')
@@ -13,9 +13,19 @@ function countInteractions(requests) {
stats[msg.sender] = {}
}
if (!stats[msg.sender][msg.executor]) {
stats[msg.sender][msg.executor] = 0
stats[msg.sender][msg.executor] = {
success: 0,
failed: 0,
pending: 0
}
}
if (msg.status === true) {
stats[msg.sender][msg.executor].success += 1
} else if (msg.status === false) {
stats[msg.sender][msg.executor].failed += 1
} else {
stats[msg.sender][msg.executor].pending += 1
}
stats[msg.sender][msg.executor] += 1
})
return stats
}
@@ -80,14 +90,8 @@ async function main(mode) {
homeToForeignConfirmations,
foreignToHomeConfirmations
} = await eventsInfo(mode)
const homeToForeign = homeToForeignRequests
.map(normalize)
.map(addExecutionStatus(homeToForeignConfirmations))
.filter(x => typeof x.status === 'boolean')
const foreignToHome = foreignToHomeRequests
.map(normalize)
.map(addExecutionStatus(foreignToHomeConfirmations))
.filter(x => typeof x.status === 'boolean')
const homeToForeign = homeToForeignRequests.map(normalize).map(addExecutionStatus(homeToForeignConfirmations))
const foreignToHome = foreignToHomeRequests.map(normalize).map(addExecutionStatus(foreignToHomeConfirmations))
for (const event of homeToForeign) {
// AMB contract emits a single UserRequestForSignature event for every home->foreign request.
@@ -98,7 +102,7 @@ async function main(mode) {
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isForeignContract(event.executor))
event.isExecutorAContract = event.status === false || (await isForeignContract(event.executor))
}
for (const event of foreignToHome) {
// AMB contract emits a single UserRequestForAffirmation event for every foreign->home request.
@@ -109,7 +113,7 @@ async function main(mode) {
// Executor is definitely a contract if a message execution failed, since message calls to EOA always succeed.
// Alternatively, the executor is checked to be a contract by looking at its bytecode size.
event.isExecutorAContract = !event.status || (await isHomeContract(event.executor))
event.isExecutorAContract = event.status === false || (await isHomeContract(event.executor))
}
const C2C = event => event.isSenderAContract && event.isExecutorAContract
const U2C = event => !event.isSenderAContract && event.isExecutorAContract

View File

@@ -1,5 +1,4 @@
require('dotenv').config()
const eventsInfo = require('./utils/events')
const {
processedMsgNotDelivered,
deliveredMsgNotProcessed,
@@ -15,14 +14,14 @@ const {
MONITOR_HOME_TO_FOREIGN_CHECK_SENDER
} = process.env
async function main() {
async function main(eventsInfo) {
const {
homeToForeignRequests,
homeToForeignConfirmations,
foreignToHomeConfirmations,
foreignToHomeRequests,
bridgeMode
} = await eventsInfo()
} = eventsInfo
if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {

View File

@@ -5,7 +5,12 @@ const logger = require('./logger')('getBalances')
const { BRIDGE_MODES } = require('../commons')
const { web3Home, web3Foreign, getHomeBlockNumber } = require('./utils/web3')
const { COMMON_HOME_BRIDGE_ADDRESS, COMMON_FOREIGN_BRIDGE_ADDRESS } = process.env
const {
MONITOR_HOME_START_BLOCK,
MONITOR_FOREIGN_START_BLOCK,
COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_BRIDGE_ADDRESS
} = process.env
const {
ERC20_ABI,
@@ -20,6 +25,8 @@ const {
async function main(bridgeMode, eventsInfo) {
const {
homeBlockNumber,
foreignBlockNumber,
homeToForeignConfirmations,
foreignToHomeConfirmations,
homeDelayedBlockNumber,
@@ -46,6 +53,13 @@ async function main(bridgeMode, eventsInfo) {
...foreignToHomeConfirmations.filter(e => e.blockNumber > homeDelayedBlockNumber).map(e => e.value)
)
const blockRanges = {
startBlockHome: MONITOR_HOME_START_BLOCK,
endBlockHome: homeBlockNumber,
startBlockForeign: MONITOR_FOREIGN_START_BLOCK,
endBlockForeign: foreignBlockNumber
}
if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
const foreignBridge = new web3Foreign.eth.Contract(FOREIGN_ERC_TO_ERC_ABI, COMMON_FOREIGN_BRIDGE_ADDRESS)
const erc20Address = await foreignBridge.methods.erc20token().call()
@@ -72,6 +86,7 @@ async function main(bridgeMode, eventsInfo) {
erc20Balance: Web3Utils.fromWei(foreignErc20Balance)
},
balanceDiff: Number(Web3Utils.fromWei(diff)),
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC || bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
@@ -94,6 +109,7 @@ async function main(bridgeMode, eventsInfo) {
totalSupply: Web3Utils.fromWei(totalSupply)
},
balanceDiff: Number(Web3Utils.fromWei(diff)),
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
@@ -163,12 +179,14 @@ async function main(bridgeMode, eventsInfo) {
},
foreign,
balanceDiff: Number(Web3Utils.fromWei(diff)),
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)
}
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
return {
home: {},
foreign: {},
...blockRanges,
lastChecked: Math.floor(Date.now() / 1000)
}
} else {

View File

@@ -11,9 +11,10 @@ app.use(cors())
app.get('/favicon.ico', (req, res) => res.sendStatus(204))
app.use('/:bridgeName', bridgeRouter)
bridgeRouter.get('/', async (req, res, next) => {
bridgeRouter.get('/:file(validators|eventsStats|alerts|mediators|stuckTransfers|failures)?', (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/getBalances.json`)
const { bridgeName, file } = req.params
const results = readFile(`./responses/${bridgeName}/${file || 'getBalances'}.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
@@ -21,49 +22,11 @@ bridgeRouter.get('/', async (req, res, next) => {
}
})
bridgeRouter.get('/validators', async (req, res, next) => {
bridgeRouter.get('/metrics', (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/validators.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
next(e)
}
})
bridgeRouter.get('/eventsStats', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/eventsStats.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
next(e)
}
})
bridgeRouter.get('/alerts', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/alerts.json`)
res.json(results)
} catch (e) {
next(e)
}
})
bridgeRouter.get('/mediators', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/mediators.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
next(e)
}
})
bridgeRouter.get('/stuckTransfers', async (req, res, next) => {
try {
const results = await readFile(`./responses/${req.params.bridgeName}/stuckTransfers.json`)
res.json(results)
const { bridgeName } = req.params
const metrics = readFile(`./responses/${bridgeName}/metrics.txt`, false)
res.type('text').send(metrics)
} catch (e) {
next(e)
}

20
monitor/metricsWorker.js Normal file
View File

@@ -0,0 +1,20 @@
require('dotenv').config()
const logger = require('./logger')('metricsWorker')
const { writeFile, createDir } = require('./utils/file')
const getPrometheusMetrics = require('./prometheusMetrics')
const { MONITOR_BRIDGE_NAME } = process.env
async function metricsWorker() {
try {
createDir(`/responses/${MONITOR_BRIDGE_NAME}`)
logger.debug('calling getPrometheusMetrics()')
const metrics = await getPrometheusMetrics(MONITOR_BRIDGE_NAME)
if (!metrics) throw new Error('metrics is empty: ' + JSON.stringify(metrics))
writeFile(`/responses/${MONITOR_BRIDGE_NAME}/metrics.txt`, metrics, { stringify: false })
logger.debug('Done')
} catch (e) {
logger.error(e)
}
}
metricsWorker()

View File

@@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"check-all": "timeout -s 9 5m node checkWorker.js && timeout -s 9 5m node checkWorker2.js && timeout -s 9 5m node checkWorker3.js",
"check-all": "timeout -s 9 5m node checkWorker.js && timeout -s 9 5m node checkWorker2.js && timeout -s 9 5m node checkWorker3.js && timeout -s 9 10s node metricsWorker.js",
"start": "node index.js",
"check-and-start": "yarn check-all && yarn start",
"lint": "eslint . --ignore-path ../.eslintignore",

View File

@@ -0,0 +1,137 @@
require('dotenv').config()
const logger = require('./logger')('metrics')
const { readFile } = require('./utils/file')
const {
MONITOR_HOME_START_BLOCK,
MONITOR_FOREIGN_START_BLOCK,
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE,
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE
} = process.env
function BridgeConf(type, validatorsBalanceEnable, alertTargetFunc, failureDirection) {
this.type = type
this.validatorsBalanceEnable = validatorsBalanceEnable
this.alertTargetFunc = alertTargetFunc
this.failureDirection = failureDirection
}
const BRIDGE_CONFS = [
new BridgeConf('home', MONITOR_HOME_VALIDATORS_BALANCE_ENABLE, 'executeAffirmations', 'homeToForeign'),
new BridgeConf('foreign', MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE, 'executeSignatures', 'foreignToHome')
]
function hasError(obj) {
return 'error' in obj
}
// Try to collect all metrics from JSON responses and then
// discard all unsuccessfully retrieved ones
async function getPrometheusMetrics(bridgeName) {
const responsePath = jsonName => `./responses/${bridgeName}/${jsonName}.json`
const metrics = {}
// Balance metrics
const balancesFile = readFile(responsePath('getBalances'))
if (!hasError(balancesFile)) {
const { home, foreign, ...commonBalances } = balancesFile
const balanceMetrics = {
// ERC_TO_ERC or ERC_TO_NATIVE mode
balances_home_value: home.totalSupply,
balances_home_txs_deposit: home.deposits,
balances_home_txs_withdrawal: home.withdrawals,
balances_foreign_value: foreign.erc20Balance,
balances_foreign_txs_deposit: foreign.deposits,
balances_foreign_txs_withdrawal: foreign.withdrawals,
// Not ARBITRARY_MESSAGE mode
balances_diff_value: commonBalances.balanceDiff,
balances_diff_deposit: commonBalances.depositsDiff,
balances_diff_withdrawal: commonBalances.withdrawalDiff,
// MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST or MONITOR_HOME_TO_FOREIGN_BLOCK_LIST is set
balances_unclaimed_txs: commonBalances.unclaimedDiff,
balances_unclaimed_value: commonBalances.unclaimedBalance,
// ARBITRARY_MESSAGE mode
txs_home_out: home.toForeign,
txs_home_in: home.fromForeign,
txs_foreign_out: foreign.toHome,
txs_foreign_in: foreign.fromHome,
txs_diff_home_out_oracles: commonBalances.fromHomeToForeignDiff,
txs_diff_home_out_users: commonBalances.fromHomeToForeignPBUDiff,
txs_diff_foreign_out: commonBalances.fromForeignToHomeDiff
}
const blockRanges = {
state_startblock_home: commonBalances.startBlockHome || MONITOR_HOME_START_BLOCK,
state_startblock_foreign: commonBalances.startBlockForeign || MONITOR_FOREIGN_START_BLOCK,
state_endblock_home: commonBalances.endBlockHome,
state_endblock_foreign: commonBalances.endBlockForeign
}
Object.assign(metrics, blockRanges, balanceMetrics)
}
// Validator metrics
const validatorsFile = readFile(responsePath('validators'))
if (!hasError(validatorsFile)) {
for (const bridge of BRIDGE_CONFS) {
const allValidators = validatorsFile[bridge.type].validators
const validatorAddressesWithBalanceCheck =
typeof bridge.validatorsBalanceEnable === 'string'
? bridge.validatorsBalanceEnable.split(' ')
: Object.keys(allValidators)
validatorAddressesWithBalanceCheck.forEach((addr, ind) => {
if (addr in allValidators) {
metrics[`validators_balances_${bridge.type}${ind}{address="${addr}"}`] = allValidators[addr].balance
} else {
logger.debug(`Nonexistent validator address ${addr}`)
}
})
}
}
// Alert metrics
const alertsFile = readFile(responsePath('alerts'))
if (!hasError(alertsFile)) {
for (const bridge of BRIDGE_CONFS) {
Object.entries(alertsFile[bridge.alertTargetFunc].misbehavior).forEach(([period, val]) => {
metrics[`misbehavior_${bridge.type}_${period}`] = val
})
}
}
// Failure metrics
const failureFile = readFile(responsePath('failures'))
if (!hasError(failureFile)) {
for (const bridge of BRIDGE_CONFS) {
const dir = bridge.failureDirection
const failures = failureFile[dir]
metrics[`failures_${dir}_total`] = failures.total
Object.entries(failures.stats).forEach(([period, count]) => {
metrics[`failures_${dir}_${period}`] = count
})
}
}
// Pack metrcis into a plain text
return Object.entries(metrics).reduceRight(
// Prometheus supports `Nan` and possibly signed `Infinity`
// in case cast to `Number` fails
(acc, [key, val]) => {
if (typeof val === 'undefined') return acc
else return `${key} ${Number(val)}\n${acc}`
},
''
)
}
module.exports = getPrometheusMetrics

View File

@@ -236,13 +236,15 @@ async function main(mode) {
foreignToHomeRequests,
isExternalErc20,
bridgeMode,
homeBlockNumber,
foreignBlockNumber,
homeDelayedBlockNumber,
foreignDelayedBlockNumber
}
if (MONITOR_CACHE_EVENTS === 'true') {
logger.debug('saving obtained events into cache file')
writeFile(cacheFilePath, result, false)
writeFile(cacheFilePath, result, { useCwd: false })
}
return result
}

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