Compare commits
2 Commits
master
...
monitor-ou
Author | SHA1 | Date | |
---|---|---|---|
|
65dc698022 | ||
|
c254e35d11 |
96
.github/workflows/main.yml
vendored
96
.github/workflows/main.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 10
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 10
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
@ -61,6 +61,31 @@ 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:
|
||||
@ -70,19 +95,21 @@ jobs:
|
||||
- name: Evaluate e2e docker images tags
|
||||
run: |
|
||||
git submodule status > submodule.status
|
||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
||||
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: |
|
||||
function check_if_image_exists() {
|
||||
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
||||
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
||||
}
|
||||
updated=()
|
||||
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
|
||||
if ! check_if_image_exists oracle ${ORACLE_TAG}; then updated+=("oracle-amb"); fi
|
||||
if ! check_if_image_exists monitor ${MONITOR_TAG}; then updated+=("monitor-amb"); 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[@]}"
|
||||
@ -104,7 +131,7 @@ jobs:
|
||||
- name: Rebuild and push molecule runner e2e image
|
||||
run: |
|
||||
function check_if_image_exists() {
|
||||
curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
||||
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
|
||||
}
|
||||
if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then
|
||||
echo "Image already exists"
|
||||
@ -122,10 +149,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [oracle-e2e, monitor-e2e, alm-e2e]
|
||||
task: [oracle-e2e, monitor-e2e, alm-e2e, 'ui-e2e:ci']
|
||||
include:
|
||||
- task: alm-e2e
|
||||
use-cache: true
|
||||
- task: 'ui-e2e:ci'
|
||||
use-cache: true
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@ -133,9 +162,10 @@ jobs:
|
||||
- name: Evaluate e2e docker images tags
|
||||
run: |
|
||||
git submodule status > submodule.status
|
||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
||||
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
|
||||
@ -149,21 +179,14 @@ jobs:
|
||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
||||
- name: yarn run ${{ matrix.task }}
|
||||
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: logs-${{ matrix.task }}
|
||||
path: e2e-commons/logs
|
||||
deployment:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-e2e-images
|
||||
- build-molecule-runner
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [oracle, monitor, multiple, repo]
|
||||
task: [oracle, ui, monitor, multiple, repo]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@ -174,15 +197,24 @@ jobs:
|
||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
||||
- run: deployment-e2e/molecule.sh ${{ matrix.task }}
|
||||
ultimate:
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags') || contains(github.event.head_commit.message, 'ultimate')
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- initialize
|
||||
- build-e2e-images
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [amb, erc-to-native]
|
||||
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'
|
||||
- task: erc-to-native
|
||||
ui-e2e-grep: 'ERC TO NATIVE'
|
||||
- task: native-to-erc
|
||||
ui-e2e-grep: 'NATIVE TO ERC'
|
||||
- task: amb-stake-erc-to-erc
|
||||
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@ -190,9 +222,10 @@ jobs:
|
||||
- name: Evaluate e2e docker images tags
|
||||
run: |
|
||||
git submodule status > submodule.status
|
||||
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
|
||||
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
|
||||
@ -205,23 +238,16 @@ jobs:
|
||||
- name: Login to docker registry
|
||||
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
|
||||
- name: Deploy contracts
|
||||
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
|
||||
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks
|
||||
- name: Pull e2e oracle image
|
||||
run: |
|
||||
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle-amb
|
||||
docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
|
||||
- name: Deploy oracle
|
||||
run: docker-compose -f ./e2e-commons/docker-compose.yml pull oracle
|
||||
- name: Deploy oracle and ui
|
||||
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
|
||||
run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}
|
||||
- name: Save logs
|
||||
if: always()
|
||||
run: e2e-commons/down.sh
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: logs-ultimate-${{ matrix.task }}
|
||||
path: e2e-commons/logs
|
||||
if: ${{ !matrix.ui-e2e-grep }}
|
||||
run: docker-compose -f ./e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run ${{ matrix.task }}
|
||||
|
2
.nvmrc
2
.nvmrc
@ -1 +1 @@
|
||||
12.22
|
||||
10.22
|
||||
|
@ -8,11 +8,11 @@ COMMON_HOME_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in t
|
||||
COMMON_FOREIGN_RPC_URL | The HTTPS URL(s) used to communicate to the RPC nodes in the Foreign network. 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)
|
||||
COMMON_HOME_BRIDGE_ADDRESS | The address of the bridge contract address in the Home network. It is used to listen to events from and send validators' transactions to the Home network. | hexidecimal beginning with "0x"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS | The address of the bridge contract address in the Foreign network. It is used to listen to events from and send validators' transactions to the Foreign network. | hexidecimal beginning with "0x"
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Home network. The gas price provided by the oracle is used to send the validator's transactions to the RPC node. Since it is assumed that the Home network has a predefined gas price (e.g. the gas price in the Core of POA.Network is `1 GWei`), the gas price oracle parameter can be omitted for such networks. | URL
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_HOME_GAS_PRICE_SUPPLIER_URL` is not used. | `instant` / `fast` / `standard` / `slow`
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. | integer
|
||||
COMMON_HOME_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | URL
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL | The URL used to get a JSON response from the gas price prediction oracle for the Foreign network. The provided gas price is used to send the validator's transactions to the RPC node. If the Foreign network is Ethereum Foundation mainnet, the oracle URL can be: https://gasprice.poa.network. Otherwise this parameter can be omitted. Set to `gas-price-oracle` if you want to use npm `gas-price-oracle` package for retrieving gas price from multiple sources. | URL
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE | Assuming the gas price oracle responds with the following JSON structure: `{"fast": 20.0, "block_time": 12.834, "health": true, "standard": 6.0, "block_number": 6470469, "instant": 71.0, "slow": 1.889}`, this parameter specifies the desirable transaction speed. The speed type can be omitted when `COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL`is not used. | `instant` / `fast` / `standard` / `slow`
|
||||
COMMON_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) used if both the oracle and fall back gas price specified in the Foreign Bridge contract are not available. | integer
|
||||
COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of the oracle to convert it to gwei. If the oracle API returns gas prices in gwei then this can be set to `1`. Also, it could be used to intentionally pay more gas than suggested by the oracle to guarantee the transaction verification. E.g. `1.25` or `1.5`. | integer
|
||||
@ -22,7 +22,7 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR | A value that will multiply the gas price of th
|
||||
|
||||
name | description | value
|
||||
--- | --- | ---
|
||||
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | ERC_TO_NATIVE / ARBITRARY_MESSAGE
|
||||
ORACLE_BRIDGE_MODE | The bridge mode. The bridge starts listening to a different set of events based on this parameter. | NATIVE_TO_ERC / ERC_TO_ERC / ERC_TO_NATIVE / ARBITRARY_MESSAGE
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC | **Only use in test environments - must be omitted in production environments.**. If this parameter is specified and set to `yes`, RPC URLs can be specified in form of HTTP links. A warning that the connection is insecure will be written to the logs. | `yes` / `no`
|
||||
ORACLE_HOME_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Home network for new blocks. The interval should match the average production time for a new block. | integer
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL | The interval in milliseconds used to request the RPC node in the Foreign network for new blocks. The interval should match the average production time for a new block. | integer
|
||||
@ -42,25 +42,29 @@ 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
|
||||
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_FOREIGN_ARCHIVE_RPC_URL | Optional HTTPS URL(s) for communication with the archive nodes on the foreign network. Only used in AMB bridge mode for async information request processing. 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`
|
||||
ORACLE_JSONRPC_ERROR_CODES | Override default JSON rpc error codes that can trigger RPC fallback to the next URL from the list (or a retry in case of a single RPC URL). Default is `-32603,-32002,-32005`. Should be a comma-separated list of negative integers. | `string`
|
||||
ORACLE_HOME_EVENTS_REPROCESSING | If set to `true`, home events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the home chain. Defaults to `1000` | `integer`
|
||||
ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer`
|
||||
ORACLE_HOME_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `integer`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING | If set to `true`, foreign events happened in the past will be refetched and processed once again, to ensure that nothing was missed on the first pass. | `bool`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BATCH_SIZE | Batch size for one `eth_getLogs` request when reprocessing old logs in the foreign chain. Defaults to `500` | `integer`
|
||||
ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer`
|
||||
ORACLE_FOREIGN_RPC_SYNC_STATE_CHECK_INTERVAL | Interval for checking JSON RPC sync state, by requesting the latest block number. Oracle will switch to the fallback JSON RPC in case sync process is stuck | `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
|
||||
|
||||
|
||||
## Monitor configuration
|
||||
@ -78,5 +82,3 @@ MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for ot
|
||||
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST | File with a list of addresses, separated by newlines. If set, determines the privileged set of accounts whose requests should be automatically processed by the CollectedSignatures watcher. | string
|
||||
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST | File with a list of addresses, separated by newlines. If set, determines the set of accounts whose requests should be marked as unclaimed. Has a lower priority than the `MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST`. | string
|
||||
MONITOR_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`
|
||||
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE | If set, defines the list of home validator addresses for which balance should be checked. | `string`
|
||||
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE | If set, defines the list of foreign validator addresses for which balance should be checked. | `string`
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:12 as contracts
|
||||
FROM node:10 as contracts
|
||||
|
||||
WORKDIR /mono
|
||||
|
||||
@ -11,7 +11,7 @@ COPY ./contracts/truffle-config.js ./
|
||||
COPY ./contracts/contracts ./contracts
|
||||
RUN npm run compile
|
||||
|
||||
FROM node:12
|
||||
FROM node:10
|
||||
|
||||
WORKDIR /mono
|
||||
COPY package.json .
|
||||
@ -19,7 +19,6 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
|
||||
COPY commons/package.json ./commons/
|
||||
COPY oracle-e2e/package.json ./oracle-e2e/
|
||||
COPY monitor-e2e/package.json ./monitor-e2e/
|
||||
COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js
|
||||
|
||||
COPY yarn.lock .
|
||||
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production
|
||||
|
10
README.md
10
README.md
@ -19,11 +19,13 @@ Sub-repositories maintained within this monorepo are listed below.
|
||||
|
||||
| Sub-repository | Description |
|
||||
| --- | --- |
|
||||
| [Oracle](oracle/README.md) | Responsible for listening to bridge related events and authorizing asset transfers. |
|
||||
| [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 |
|
||||
@ -54,6 +56,8 @@ Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbrid
|
||||
|
||||
The POA TokenBridge provides four operational modes:
|
||||
|
||||
- [x] `Native-to-ERC20` **Coins** on a Home network can be converted to ERC20-compatible **tokens** on a Foreign network. Coins are locked on the Home side and the corresponding amount of ERC20 tokens are minted on the Foreign side. When the operation is reversed, tokens are burnt on the Foreign side and unlocked in the Home network. **More Information: [POA-to-POA20 Bridge](https://medium.com/poa-network/introducing-poa-bridge-and-poa20-55d8b78058ac)**
|
||||
- [x] `ERC20-to-ERC20` ERC20-compatible tokens on the Foreign network are locked and minted as ERC20-compatible tokens (ERC677 tokens) on the Home network. When transferred from Home to Foreign, they are burnt on the Home side and unlocked in the Foreign network. This can be considered a form of atomic swap when a user swaps the token "X" in network "A" to the token "Y" in network "B". **More Information: [ERC20-to-ERC20](https://medium.com/poa-network/introducing-the-erc20-to-erc20-tokenbridge-ce266cc1a2d0)**
|
||||
- [x] `ERC20-to-Native`: Pre-existing **tokens** in the Foreign network are locked and **coins** are minted in the `Home` network. In this mode, the Home network consensus engine invokes [Parity's Block Reward contract](https://wiki.parity.io/Block-Reward-Contract.html) to mint coins per the bridge contract request. **More Information: [xDai Chain](https://medium.com/poa-network/poa-network-partners-with-makerdao-on-xdai-chain-the-first-ever-usd-stable-blockchain-65a078c41e6a)**
|
||||
- [x] `Arbitrary-Message`: Transfer arbitrary data between two networks as so the data could be interpreted as an arbitrary contract method invocation.
|
||||
|
||||
@ -64,7 +68,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), initialize submodules, install dependencies, compile the Smart Contracts:
|
||||
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts:
|
||||
```
|
||||
yarn initialize
|
||||
```
|
||||
@ -87,7 +91,7 @@ Running tests for all projects:
|
||||
yarn test
|
||||
```
|
||||
|
||||
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [Monitor](monitor-e2e/README.md).
|
||||
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [UI](ui-e2e/README.md).
|
||||
|
||||
For details on building, running and developing please refer to respective READMEs in sub-repositories.
|
||||
|
||||
|
@ -19,6 +19,6 @@
|
||||
"eslint-plugin-jest": "^23.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.22"
|
||||
"node": ">= 10.18"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
cd $(dirname $0)
|
||||
|
||||
../e2e-commons/up.sh deploy generate-amb-tx blocks alm alm-e2e
|
||||
../e2e-commons/up.sh deploy blocks alm alm-e2e
|
||||
|
||||
# run oracle amb e2e tests to generate transactions for alm
|
||||
docker-compose -f ../e2e-commons/docker-compose.yml run e2e yarn workspace oracle-e2e run alm
|
||||
|
@ -6,8 +6,8 @@ jest.setTimeout(60000)
|
||||
const statusText = 'Success'
|
||||
const statusSelector = 'label[data-id="status"]'
|
||||
|
||||
const homeToForeignTxURL = 'http://localhost:3004/77/0x295efbe6ae98937ef35d939376c9bd752b4dc6f6899a9d5ddd6a57cea3d76c89'
|
||||
const foreignToHomeTxURL = 'http://localhost:3004/42/0x7262f7dbe6c30599edded2137fbbe93c271b37f5c54dd27f713f0cf510e3b4dd'
|
||||
const homeToForeignTxURL = 'http://localhost:3004/77/0x58e7d63368335b9591d4dbb43889084f698fcee93ab7656fd7a39d8c66bc4b60'
|
||||
const foreignToHomeTxURL = 'http://localhost:3004/42/0x592bf28fc896419d2838f71cd0388775814b692688f1ecd5b1519081566b994a'
|
||||
|
||||
describe('ALM', () => {
|
||||
let browser
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:12 as contracts
|
||||
FROM node:10 as contracts
|
||||
|
||||
WORKDIR /mono
|
||||
|
||||
@ -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
|
||||
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production
|
||||
|
||||
COPY ./commons ./commons
|
||||
COPY ./alm ./alm
|
||||
|
@ -58,7 +58,6 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"node-fetch": "^2.6.1"
|
||||
"eslint-plugin-prettier": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ 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')
|
||||
|
||||
@ -12,9 +10,7 @@ const {
|
||||
COMMON_HOME_RPC_URL,
|
||||
COMMON_HOME_BRIDGE_ADDRESS,
|
||||
COMMON_FOREIGN_RPC_URL,
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS,
|
||||
ALM_FOREIGN_EXPLORER_API,
|
||||
ALM_HOME_EXPLORER_API
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS
|
||||
} = process.env
|
||||
|
||||
const generateSnapshot = async (side, url, bridgeAddress) => {
|
||||
@ -23,31 +19,6 @@ 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
|
||||
@ -58,14 +29,10 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
|
||||
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
|
||||
|
||||
// Save RequiredBlockConfirmationChanged events
|
||||
let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback(
|
||||
bridgeContract,
|
||||
'RequiredBlockConfirmationChanged',
|
||||
{
|
||||
fromBlock: 0,
|
||||
toBlock: currentBlockNumber
|
||||
}
|
||||
)
|
||||
let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('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
|
||||
@ -92,14 +59,10 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
|
||||
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
|
||||
|
||||
// Save RequiredSignaturesChanged events
|
||||
const RequiredSignaturesChangedEvents = await getPastEventsWithFallback(
|
||||
validatorContract,
|
||||
'RequiredSignaturesChanged',
|
||||
{
|
||||
fromBlock: 0,
|
||||
toBlock: currentBlockNumber
|
||||
}
|
||||
)
|
||||
const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', {
|
||||
fromBlock: 0,
|
||||
toBlock: currentBlockNumber
|
||||
})
|
||||
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
|
||||
blockNumber: e.blockNumber,
|
||||
returnValues: {
|
||||
@ -108,7 +71,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
|
||||
}))
|
||||
|
||||
// Save ValidatorAdded events
|
||||
const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', {
|
||||
const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', {
|
||||
fromBlock: 0,
|
||||
toBlock: currentBlockNumber
|
||||
})
|
||||
@ -122,7 +85,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
|
||||
}))
|
||||
|
||||
// Save ValidatorRemoved events
|
||||
const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', {
|
||||
const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', {
|
||||
fromBlock: 0,
|
||||
toBlock: currentBlockNumber
|
||||
})
|
||||
|
@ -123,24 +123,6 @@ const abi: AbiItem[] = [
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
},
|
||||
{
|
||||
name: '_signatures',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'safeExecuteSignaturesWithAutoGasLimit',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
import styled from 'styled-components'
|
||||
import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import { ValidatorsConfirmations } from './ValidatorsConfirmations'
|
||||
@ -39,27 +39,17 @@ export interface ConfirmationsContainerParams {
|
||||
message: MessageObject
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
fromHome: boolean
|
||||
homeStartBlock: Maybe<number>
|
||||
foreignStartBlock: Maybe<number>
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export const ConfirmationsContainer = ({
|
||||
message,
|
||||
receipt,
|
||||
fromHome,
|
||||
homeStartBlock,
|
||||
foreignStartBlock
|
||||
}: ConfirmationsContainerParams) => {
|
||||
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
|
||||
const {
|
||||
home: { name: homeName },
|
||||
home: { name: homeName, confirmations },
|
||||
foreign: { name: foreignName }
|
||||
} = useStateProvider()
|
||||
const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0)
|
||||
const [executionBlockNumber, setExecutionBlockNumber] = useState(0)
|
||||
const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest')
|
||||
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
|
||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
||||
const {
|
||||
confirmations,
|
||||
status,
|
||||
executionData,
|
||||
signatureCollected,
|
||||
@ -71,23 +61,12 @@ export const ConfirmationsContainer = ({
|
||||
message,
|
||||
receipt,
|
||||
fromHome,
|
||||
homeStartBlock,
|
||||
foreignStartBlock,
|
||||
requiredSignatures: src.requiredSignatures,
|
||||
validatorList: src.validatorList,
|
||||
targetValidatorList: dst.validatorList,
|
||||
timestamp,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
blockConfirmations
|
||||
})
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (executionBlockNumber || executionData.status !== VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS) return
|
||||
|
||||
setExecutionBlockNumber(executionData.blockNumber)
|
||||
},
|
||||
[executionData.status, executionBlockNumber, executionData.blockNumber]
|
||||
)
|
||||
|
||||
const statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
|
||||
|
||||
const parseDescription = () => {
|
||||
@ -126,22 +105,20 @@ export const ConfirmationsContainer = ({
|
||||
</MultiLine>
|
||||
</StatusDescription>
|
||||
<ValidatorsConfirmations
|
||||
confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
|
||||
requiredSignatures={dst.requiredSignatures}
|
||||
validatorList={dst.validatorList}
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
validatorList={validatorList}
|
||||
waitingBlocksResolved={waitingBlocksResolved}
|
||||
/>
|
||||
{signatureCollected && (
|
||||
<ExecutionConfirmation
|
||||
message={message}
|
||||
messageData={message.data}
|
||||
executionData={executionData}
|
||||
isHome={!fromHome}
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
setExecutionData={setExecutionData}
|
||||
executionEventsFetched={executionEventsFetched}
|
||||
setPendingExecution={setPendingExecution}
|
||||
dstRequiredSignatures={dst.requiredSignatures}
|
||||
dstValidatorList={dst.validatorList}
|
||||
/>
|
||||
)}
|
||||
</StyledConfirmationContainer>
|
||||
|
@ -1,102 +1,54 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { Thead, AgeTd, StatusTd } from './commons/Table'
|
||||
import { ManualExecutionButton } from './ManualExecutionButton'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
|
||||
import { WarningAlert } from './commons/WarningAlert'
|
||||
import { ErrorAlert } from './commons/ErrorAlert'
|
||||
|
||||
const StyledExecutionConfirmation = styled.div`
|
||||
margin-top: 30px;
|
||||
`
|
||||
|
||||
export interface ExecutionConfirmationParams {
|
||||
message: MessageObject
|
||||
messageData: string
|
||||
executionData: ExecutionData
|
||||
setExecutionData: Function
|
||||
confirmations: ConfirmationParam[]
|
||||
requiredSignatures: number
|
||||
isHome: boolean
|
||||
executionEventsFetched: boolean
|
||||
setPendingExecution: Function
|
||||
dstRequiredSignatures: number
|
||||
dstValidatorList: string[]
|
||||
}
|
||||
|
||||
export const ExecutionConfirmation = ({
|
||||
message,
|
||||
messageData,
|
||||
executionData,
|
||||
setExecutionData,
|
||||
confirmations,
|
||||
requiredSignatures,
|
||||
isHome,
|
||||
executionEventsFetched,
|
||||
setPendingExecution,
|
||||
dstRequiredSignatures,
|
||||
dstValidatorList
|
||||
setPendingExecution
|
||||
}: ExecutionConfirmationParams) => {
|
||||
const { foreign } = useStateProvider()
|
||||
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [warning, setWarning] = useState('')
|
||||
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)
|
||||
const formattedValidator =
|
||||
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!availableManualExecution || !foreign.bridgeContract) return
|
||||
|
||||
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
|
||||
p.then(({ major, minor }: any) => {
|
||||
major = parseInt(major, 10)
|
||||
minor = parseInt(minor, 10)
|
||||
if (major < 5 || (major === 5 && minor < 7)) return
|
||||
|
||||
setSafeExecutionAvailable(true)
|
||||
})
|
||||
},
|
||||
[availableManualExecution, foreign.bridgeContract]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!message.data || !executionData || !availableManualExecution) return
|
||||
|
||||
try {
|
||||
const fileName = 'warnRules'
|
||||
const rules: WarnRule[] = require(`../snapshots/${fileName}.json`)
|
||||
for (let rule of rules) {
|
||||
if (matchesRule(rule, message)) {
|
||||
setWarning(rule.message)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
[availableManualExecution, executionData, message, message.data, setWarning]
|
||||
)
|
||||
|
||||
const getExecutionStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
@ -114,14 +66,12 @@ export const ExecutionConfirmation = ({
|
||||
|
||||
return (
|
||||
<StyledExecutionConfirmation>
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
|
||||
<table>
|
||||
<Thead>
|
||||
<tr>
|
||||
<th>{requiredManualExecution ? 'Execution info' : 'Executed by'}</th>
|
||||
<th className="text-center">Status</th>
|
||||
{showAgeColumn && <th className="text-center">Age</th>}
|
||||
{!requiredManualExecution && <th className="text-center">Age</th>}
|
||||
{availableManualExecution && <th className="text-center">Actions</th>}
|
||||
</tr>
|
||||
</Thead>
|
||||
@ -137,7 +87,7 @@ export const ExecutionConfirmation = ({
|
||||
)}
|
||||
</td>
|
||||
<StatusTd className="text-center">{getExecutionStatusElement(executionData.status)}</StatusTd>
|
||||
{showAgeColumn && (
|
||||
{!requiredManualExecution && (
|
||||
<AgeTd className="text-center">
|
||||
{executionData.timestamp > 0 ? (
|
||||
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
||||
@ -153,14 +103,10 @@ export const ExecutionConfirmation = ({
|
||||
{availableManualExecution && (
|
||||
<td>
|
||||
<ManualExecutionButton
|
||||
safeExecutionAvailable={safeExecutionAvailable}
|
||||
messageData={message.data}
|
||||
messageData={messageData}
|
||||
setExecutionData={setExecutionData}
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
setPendingExecution={setPendingExecution}
|
||||
setError={setError}
|
||||
requiredSignatures={dstRequiredSignatures}
|
||||
validatorList={dstValidatorList}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
|
@ -8,6 +8,7 @@ import { TransactionReceipt } from 'web3-eth'
|
||||
import { InfoAlert } from './commons/InfoAlert'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
|
||||
import { ErrorAlert } from './commons/ErrorAlert'
|
||||
|
||||
const StyledMainPage = styled.div`
|
||||
text-align: center;
|
||||
@ -51,7 +52,7 @@ export interface FormSubmitParams {
|
||||
|
||||
export const MainPage = () => {
|
||||
const history = useHistory()
|
||||
const { home, foreign } = useStateProvider()
|
||||
const { home, foreign, error, setError } = useStateProvider()
|
||||
const [networkName, setNetworkName] = useState('')
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
const [showInfoAlert, setShowInfoAlert] = useState(false)
|
||||
@ -131,6 +132,7 @@ export const MainPage = () => {
|
||||
</AlertP>
|
||||
</InfoAlert>
|
||||
)}
|
||||
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
||||
<Route
|
||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
||||
|
@ -2,104 +2,37 @@ 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 {
|
||||
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 { 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'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
const ActionButton = styled.button`
|
||||
const StyledButton = styled.button`
|
||||
color: var(--button-color);
|
||||
border-color: var(--font-color);
|
||||
margin-top: 10px;
|
||||
min-width: 120px;
|
||||
padding: 1rem;
|
||||
&:focus {
|
||||
outline: var(--button-color);
|
||||
}
|
||||
`
|
||||
|
||||
interface ManualExecutionButtonParams {
|
||||
safeExecutionAvailable: boolean
|
||||
messageData: string
|
||||
setExecutionData: Function
|
||||
confirmations: ConfirmationParam[]
|
||||
setPendingExecution: Function
|
||||
setError: Function
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
setPendingExecution: Function
|
||||
}
|
||||
|
||||
export const ManualExecutionButton = ({
|
||||
safeExecutionAvailable,
|
||||
messageData,
|
||||
setExecutionData,
|
||||
confirmations,
|
||||
setPendingExecution,
|
||||
setError,
|
||||
requiredSignatures,
|
||||
validatorList
|
||||
setPendingExecution
|
||||
}: ManualExecutionButtonParams) => {
|
||||
const { foreign } = useStateProvider()
|
||||
const { home, foreign, setError } = useStateProvider()
|
||||
const { library, activate, account, active } = useWeb3React()
|
||||
const [manualExecution, setManualExecution] = useState(false)
|
||||
const [allowFailures, setAllowFailures] = useState(false)
|
||||
const [ready, setReady] = useState(false)
|
||||
const [title, setTitle] = useState('Loading')
|
||||
const [validSignatures, setValidSignatures] = useState<string[]>([])
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
!foreign.bridgeContract ||
|
||||
!foreign.web3 ||
|
||||
!confirmations ||
|
||||
!confirmations.length ||
|
||||
!requiredSignatures ||
|
||||
!validatorList ||
|
||||
!validatorList.length
|
||||
)
|
||||
return
|
||||
|
||||
const signatures = []
|
||||
for (let i = 0; i < confirmations.length && signatures.length < requiredSignatures; i++) {
|
||||
const sig = confirmations[i].signature
|
||||
if (!sig) {
|
||||
continue
|
||||
}
|
||||
const { v, r, s } = signatureToVRS(sig)
|
||||
const signer = foreign.web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
||||
if (validatorList.includes(signer)) {
|
||||
signatures.push(sig)
|
||||
}
|
||||
}
|
||||
|
||||
if (signatures.length >= requiredSignatures) {
|
||||
setValidSignatures(signatures.slice(0, requiredSignatures))
|
||||
setTitle('Execute')
|
||||
setReady(true)
|
||||
} else {
|
||||
setTitle('Unavailable')
|
||||
}
|
||||
},
|
||||
[
|
||||
foreign.bridgeContract,
|
||||
foreign.web3,
|
||||
validatorList,
|
||||
requiredSignatures,
|
||||
messageData,
|
||||
setValidSignatures,
|
||||
confirmations
|
||||
]
|
||||
)
|
||||
const disabled =
|
||||
home.confirmations.filter(({ signature }) => signature && signature.startsWith('0x')).length < requiredSignatures
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
@ -127,16 +60,13 @@ export const ManualExecutionButton = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return
|
||||
if (!library || !foreign.bridgeContract || !home.confirmations) return
|
||||
|
||||
const signatures = packSignatures(validSignatures.map(signatureToVRS))
|
||||
const messageId = messageData.slice(0, 66)
|
||||
const bridge = foreign.bridgeContract
|
||||
const executeMethod =
|
||||
safeExecutionAvailable && !allowFailures
|
||||
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
|
||||
: bridge.methods.executeSignatures
|
||||
const data = executeMethod(messageData, signatures).encodeABI()
|
||||
const collectedSignatures = home.confirmations
|
||||
.map(confirmation => confirmation.signature!)
|
||||
.filter(signature => signature && signature.startsWith('0x'))
|
||||
const signatures = packSignatures(collectedSignatures.map(signatureToVRS))
|
||||
const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
|
||||
setManualExecution(false)
|
||||
|
||||
library.eth
|
||||
@ -155,33 +85,7 @@ export const ManualExecutionButton = ({
|
||||
})
|
||||
setPendingExecution(true)
|
||||
})
|
||||
.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)
|
||||
}
|
||||
})
|
||||
.on('error', (e: Error) => setError(e.message))
|
||||
},
|
||||
[
|
||||
manualExecution,
|
||||
@ -194,38 +98,17 @@ export const ManualExecutionButton = ({
|
||||
foreign.bridgeContract,
|
||||
setError,
|
||||
messageData,
|
||||
home.confirmations,
|
||||
setExecutionData,
|
||||
setPendingExecution,
|
||||
safeExecutionAvailable,
|
||||
allowFailures,
|
||||
foreign.web3,
|
||||
validSignatures
|
||||
setPendingExecution
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="is-center">
|
||||
<ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
{title}
|
||||
</ActionButton>
|
||||
</div>
|
||||
{safeExecutionAvailable && (
|
||||
<div
|
||||
title="Allow executed message to fail and record its failure on-chain without reverting the whole transaction.
|
||||
Use fixed gas limit for execution."
|
||||
className="is-center"
|
||||
style={{ paddingTop: 10 }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="allow-failures"
|
||||
checked={allowFailures}
|
||||
onChange={e => setAllowFailures(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="allow-failures">Unsafe mode</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="is-center">
|
||||
<StyledButton disabled={disabled} className="button outline" onClick={() => setManualExecution(true)}>
|
||||
Execute
|
||||
</StyledButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ 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
|
||||
@ -24,15 +23,12 @@ 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
|
||||
|
||||
@ -68,6 +64,7 @@ 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
|
||||
|
||||
@ -104,13 +101,7 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptPar
|
||||
)}
|
||||
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}
|
||||
{displayConfirmations && (
|
||||
<ConfirmationsContainer
|
||||
message={messageToConfirm}
|
||||
receipt={receipt}
|
||||
fromHome={isHome}
|
||||
homeStartBlock={homeStartBlock}
|
||||
foreignStartBlock={foreignStartBlock}
|
||||
/>
|
||||
<ConfirmationsContainer message={messageToConfirm} receipt={receipt} fromHome={isHome} timestamp={timestamp} />
|
||||
)}
|
||||
<BackButton onBackToMain={onBackToMain} />
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
@ -31,9 +31,7 @@ export const ValidatorsConfirmations = ({
|
||||
const getValidatorStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
|
||||
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</SuccessLabel>
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
@ -60,28 +58,26 @@ export const ValidatorsConfirmations = ({
|
||||
</tr>
|
||||
</Thead>
|
||||
<tbody>
|
||||
{confirmations.map((confirmation, i) => {
|
||||
const displayedStatus = confirmation.status
|
||||
const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
|
||||
let elementIfNoTimestamp: any = <SimpleLoading />
|
||||
switch (displayedStatus) {
|
||||
case '':
|
||||
case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
|
||||
if (waitingBlocksResolved) {
|
||||
elementIfNoTimestamp = SEARCHING_TX
|
||||
}
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
||||
elementIfNoTimestamp = ''
|
||||
break
|
||||
case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
|
||||
elementIfNoTimestamp = RECENT_AGE
|
||||
break
|
||||
}
|
||||
{validatorList.map((validator, i) => {
|
||||
const filteredConfirmation = confirmations.filter(c => c.validator === validator)
|
||||
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
|
||||
const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
|
||||
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
|
||||
const elementIfNoTimestamp =
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
|
||||
(displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') &&
|
||||
waitingBlocksResolved ? (
|
||||
SEARCHING_TX
|
||||
) : (
|
||||
<SimpleLoading />
|
||||
)
|
||||
) : (
|
||||
''
|
||||
)
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td>
|
||||
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td>
|
||||
<StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd>
|
||||
<AgeTd className="text-center">
|
||||
{confirmation && confirmation.timestamp > 0 ? (
|
||||
@ -98,7 +94,7 @@ export const ValidatorsConfirmations = ({
|
||||
</tbody>
|
||||
</table>
|
||||
<RequiredConfirmations>
|
||||
At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
|
||||
{requiredSignatures} of {validatorList.length} confirmations required
|
||||
</RequiredConfirmations>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,7 +2,6 @@ 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);
|
||||
@ -16,33 +15,17 @@ const CloseIconContainer = styled.div`
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
white-space: pre-wrap;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
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-12 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>
|
||||
)
|
||||
}
|
||||
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>
|
||||
)
|
||||
|
@ -1,34 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { InfoIcon } from './InfoIcon'
|
||||
import { CloseIcon } from './CloseIcon'
|
||||
|
||||
const StyledErrorAlert = styled.div`
|
||||
border: 1px solid var(--warning-color);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
padding-top: 10px;
|
||||
`
|
||||
|
||||
const CloseIconContainer = styled.div`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
white-space: pre-wrap;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
export const WarningAlert = ({ onClick, error }: { onClick: () => void; error: string }) => {
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<StyledErrorAlert className="col-12 is-vertical-align row">
|
||||
<InfoIcon color="var(--warning-color)" />
|
||||
<TextContainer className="col-10">{error}</TextContainer>
|
||||
<CloseIconContainer className="col-1 is-vertical-align is-center" onClick={onClick}>
|
||||
<CloseIcon color="var(--warning-color)" />
|
||||
</CloseIconContainer>
|
||||
</StyledErrorAlert>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -18,8 +18,9 @@ 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 = 500
|
||||
export const MAX_TX_SEARCH_BLOCK_RANGE: number = 10000
|
||||
export const BLOCK_RANGE: number = 50
|
||||
export const ONE_DAY_TIMESTAMP: number = 86400
|
||||
export const THREE_DAYS_TIMESTAMP: number = 259200
|
||||
|
||||
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
|
||||
export const SUBMIT_SIGNATURE_HASH = '630cea8e'
|
||||
@ -54,28 +55,14 @@ export const CONFIRMATIONS_STATUS = {
|
||||
}
|
||||
|
||||
export const VALIDATOR_CONFIRMATION_STATUS = {
|
||||
SUCCESS: 'Confirmed',
|
||||
MANUAL: 'Manual',
|
||||
EXECUTION_SUCCESS: 'Executed',
|
||||
SUCCESS: 'Success',
|
||||
FAILED: 'Failed',
|
||||
FAILED_VALID: 'Failed valid',
|
||||
PENDING: 'Pending',
|
||||
WAITING: 'Waiting',
|
||||
NOT_REQUIRED: 'Not required',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const RECENT_AGE = 'Recent'
|
||||
|
||||
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.`
|
||||
|
@ -4,8 +4,6 @@ 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
|
||||
@ -21,11 +19,9 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio
|
||||
contract: Contract,
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
snapshotProvider: SnapshotProvider
|
||||
) => {
|
||||
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber, snapshotProvider)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
@ -33,12 +29,10 @@ export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmatio
|
||||
() => {
|
||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
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)
|
||||
if (!bridgeContract || !receipt) return
|
||||
callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations, snapshotProvider)
|
||||
},
|
||||
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome, home.web3, foreign.web3]
|
||||
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -1,68 +0,0 @@
|
||||
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
|
||||
}
|
@ -11,6 +11,9 @@ 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 {
|
||||
@ -25,17 +28,18 @@ export interface useMessageConfirmationsParams {
|
||||
message: MessageObject
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
fromHome: boolean
|
||||
homeStartBlock: Maybe<number>
|
||||
foreignStartBlock: Maybe<number>
|
||||
timestamp: number
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
targetValidatorList: string[]
|
||||
blockConfirmations: number
|
||||
}
|
||||
|
||||
export interface ConfirmationParam {
|
||||
export interface BasicConfirmationParam {
|
||||
validator: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface ConfirmationParam extends BasicConfirmationParam {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
signature?: string
|
||||
@ -47,22 +51,19 @@ export interface ExecutionData {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
executionResult: boolean
|
||||
blockNumber: number
|
||||
}
|
||||
|
||||
export const useMessageConfirmations = ({
|
||||
message,
|
||||
receipt,
|
||||
fromHome,
|
||||
homeStartBlock,
|
||||
foreignStartBlock,
|
||||
timestamp,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
targetValidatorList,
|
||||
blockConfirmations
|
||||
}: useMessageConfirmationsParams) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [confirmations, setConfirmations] = useState<ConfirmationParam[]>([])
|
||||
const { confirmations, setConfirmations } = home
|
||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
||||
@ -74,8 +75,7 @@ export const useMessageConfirmations = ({
|
||||
validator: '',
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
|
||||
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
|
||||
@ -84,59 +84,50 @@ export const useMessageConfirmations = ({
|
||||
const [pendingConfirmations, setPendingConfirmations] = useState(false)
|
||||
const [pendingExecution, setPendingExecution] = useState(false)
|
||||
|
||||
const existsConfirmation = (confirmationArray: ConfirmationParam[]) =>
|
||||
confirmationArray.some(
|
||||
const existsConfirmation = (confirmationArray: ConfirmationParam[]) => {
|
||||
const filteredList = confirmationArray.filter(
|
||||
c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING
|
||||
)
|
||||
|
||||
// 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]
|
||||
)
|
||||
return filteredList.length > 0
|
||||
}
|
||||
|
||||
// Check if the validators are waiting for block confirmations to verify the message
|
||||
useEffect(
|
||||
() => {
|
||||
if (!receipt || !blockConfirmations || waitingBlocksResolved) return
|
||||
if (!receipt || !blockConfirmations) return
|
||||
|
||||
let timeoutId: number
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}))
|
||||
|
||||
const checkSignaturesWaitingForBLocks = () => {
|
||||
const currentBlock = blockProvider.get()
|
||||
checkSignaturesWaitingForBLocks(
|
||||
targetBlock,
|
||||
setWaitingBlocks,
|
||||
setWaitingBlocksResolved,
|
||||
validatorList,
|
||||
setConfirmations,
|
||||
blockProvider,
|
||||
interval,
|
||||
subscriptions
|
||||
)
|
||||
|
||||
if (currentBlock && currentBlock >= targetBlock) {
|
||||
setWaitingBlocksResolved(true)
|
||||
setWaitingBlocks(false)
|
||||
} else if (currentBlock) {
|
||||
setWaitingBlocks(true)
|
||||
setConfirmations(validatorsWaiting)
|
||||
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, interval)
|
||||
} else {
|
||||
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, 500)
|
||||
}
|
||||
return () => {
|
||||
unsubscribe()
|
||||
blockProvider.stop()
|
||||
}
|
||||
|
||||
checkSignaturesWaitingForBLocks()
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
},
|
||||
[blockConfirmations, fromHome, receipt, validatorList, waitingBlocksResolved]
|
||||
[blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt, setConfirmations]
|
||||
)
|
||||
|
||||
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
|
||||
@ -144,40 +135,35 @@ export const useMessageConfirmations = ({
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
|
||||
if (!fromHome || !receipt || !home.web3 || !signatureCollected) return
|
||||
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
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)
|
||||
}
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
getCollectedSignaturesEvent(receipt.blockNumber, receipt.blockNumber + BLOCK_RANGE)
|
||||
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
|
||||
)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId)
|
||||
isCancelled = true
|
||||
unsubscribe()
|
||||
homeBlockNumberProvider.stop()
|
||||
}
|
||||
},
|
||||
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
|
||||
@ -187,99 +173,50 @@ export const useMessageConfirmations = ({
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !home.web3 || !collectedSignaturesEvent || !blockConfirmations) return
|
||||
if (waitingBlocksForExecutionResolved) return
|
||||
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return
|
||||
|
||||
let timeoutId: number
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
|
||||
|
||||
const checkWaitingBlocksForExecution = () => {
|
||||
const currentBlock = homeBlockNumberProvider.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)
|
||||
} 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)
|
||||
}
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
checkWaitingBlocksForExecution()
|
||||
homeBlockNumberProvider.start(home.web3)
|
||||
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
checkWaitingBlocksForExecution(
|
||||
homeBlockNumberProvider,
|
||||
HOME_RPC_POLLING_INTERVAL,
|
||||
targetBlock,
|
||||
collectedSignaturesEvent,
|
||||
setWaitingBlocksForExecution,
|
||||
setWaitingBlocksForExecutionResolved,
|
||||
setExecutionData,
|
||||
subscriptions
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
homeBlockNumberProvider.stop()
|
||||
}
|
||||
},
|
||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, waitingBlocksForExecutionResolved]
|
||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt]
|
||||
)
|
||||
|
||||
// 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 || !homeStartBlock || !requiredSignatures || !home.web3 || !home.bridgeContract) return
|
||||
if (!validatorList || !validatorList.length) return
|
||||
if (!waitingBlocksResolved || !timestamp || !requiredSignatures) return
|
||||
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
if (fromHome) {
|
||||
if (!targetValidatorList || !targetValidatorList.length) return
|
||||
const msgHash = home.web3.utils.sha3(message.data)!
|
||||
const allValidators = [...validatorList, ...targetValidatorList].filter((v, i, s) => s.indexOf(v) === i)
|
||||
const manualConfirmations = []
|
||||
for (let i = 0; i < allValidators.length; i++) {
|
||||
try {
|
||||
const overrideSignatures: {
|
||||
[key: string]: string
|
||||
} = require(`../snapshots/signatures_${allValidators[i]}.json`)
|
||||
if (overrideSignatures[msgHash]) {
|
||||
console.log(`Adding manual signature from ${allValidators[i]}`)
|
||||
manualConfirmations.push({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.MANUAL,
|
||||
validator: allValidators[i],
|
||||
timestamp: 0,
|
||||
txHash: '',
|
||||
signature: overrideSignatures[msgHash]
|
||||
})
|
||||
} else {
|
||||
console.log(`No manual signature from ${allValidators[i]} was found`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Signatures overrides are not present for ${allValidators[i]}`)
|
||||
}
|
||||
}
|
||||
setConfirmations(manualConfirmations)
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
getConfirmationsForTx(
|
||||
@ -291,9 +228,9 @@ export const useMessageConfirmations = ({
|
||||
setConfirmations,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
id => (timeoutId = id),
|
||||
() => isCancelled,
|
||||
homeStartBlock,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getValidatorFailedTransactionsForMessage,
|
||||
setFailedConfirmations,
|
||||
getValidatorPendingTransactionsForMessage,
|
||||
@ -302,8 +239,7 @@ export const useMessageConfirmations = ({
|
||||
)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId)
|
||||
isCancelled = true
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
@ -314,8 +250,8 @@ export const useMessageConfirmations = ({
|
||||
home.bridgeContract,
|
||||
requiredSignatures,
|
||||
waitingBlocksResolved,
|
||||
homeStartBlock,
|
||||
targetValidatorList
|
||||
timestamp,
|
||||
setConfirmations
|
||||
]
|
||||
)
|
||||
|
||||
@ -326,23 +262,29 @@ export const useMessageConfirmations = ({
|
||||
() => {
|
||||
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
|
||||
|
||||
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract
|
||||
const web3 = fromHome ? foreign.web3 : home.web3
|
||||
const startBlock = fromHome ? foreignStartBlock : homeStartBlock
|
||||
if (!startBlock || !bridgeContract || !web3) return
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
let timeoutId: number
|
||||
let isCancelled = false
|
||||
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
|
||||
|
||||
getFinalizationEvent(
|
||||
fromHome,
|
||||
bridgeContract,
|
||||
web3,
|
||||
contractEvent,
|
||||
providedWeb3,
|
||||
setExecutionData,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
id => (timeoutId = id),
|
||||
() => isCancelled,
|
||||
startBlock,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getExecutionFailedTransactionForMessage,
|
||||
setFailedExecution,
|
||||
@ -352,8 +294,7 @@ export const useMessageConfirmations = ({
|
||||
)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId)
|
||||
isCancelled = true
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
@ -365,26 +306,19 @@ export const useMessageConfirmations = ({
|
||||
home.web3,
|
||||
waitingBlocksResolved,
|
||||
waitingBlocksForExecutionResolved,
|
||||
collectedSignaturesEvent,
|
||||
foreignStartBlock,
|
||||
homeStartBlock
|
||||
timestamp,
|
||||
collectedSignaturesEvent
|
||||
]
|
||||
)
|
||||
|
||||
// Sets the message status based in the collected information
|
||||
useEffect(
|
||||
() => {
|
||||
if (
|
||||
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
|
||||
existsConfirmation(confirmations)
|
||||
) {
|
||||
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) {
|
||||
const newStatus = executionData.executionResult
|
||||
? CONFIRMATIONS_STATUS.SUCCESS
|
||||
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
|
||||
setStatus(newStatus)
|
||||
|
||||
foreignBlockNumberProvider.stop()
|
||||
homeBlockNumberProvider.stop()
|
||||
} else if (signatureCollected) {
|
||||
if (fromHome) {
|
||||
if (waitingBlocksForExecution) {
|
||||
@ -432,7 +366,6 @@ export const useMessageConfirmations = ({
|
||||
)
|
||||
|
||||
return {
|
||||
confirmations,
|
||||
status,
|
||||
signatureCollected,
|
||||
executionData,
|
||||
|
@ -11,23 +11,40 @@ export const useTransactionFinder = ({ txHash, web3 }: { txHash: string; web3: M
|
||||
() => {
|
||||
if (!txHash || !web3) return
|
||||
|
||||
let timeoutId: number
|
||||
const subscriptions: number[] = []
|
||||
|
||||
const getReceipt = async () => {
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const getReceipt = async (
|
||||
web3: Web3,
|
||||
txHash: string,
|
||||
setReceipt: Function,
|
||||
setStatus: Function,
|
||||
subscriptions: number[]
|
||||
) => {
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
setReceipt(txReceipt)
|
||||
|
||||
if (!txReceipt) {
|
||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
||||
timeoutId = setTimeout(getReceipt, HOME_RPC_POLLING_INTERVAL)
|
||||
const timeoutId = setTimeout(
|
||||
() => getReceipt(web3, txHash, setReceipt, setStatus, subscriptions),
|
||||
HOME_RPC_POLLING_INTERVAL
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
} else {
|
||||
setStatus(TRANSACTION_STATUS.FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
getReceipt()
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
getReceipt(web3, txHash, setReceipt, setStatus, subscriptions)
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[txHash, web3]
|
||||
)
|
||||
|
@ -31,14 +31,19 @@ export const useTransactionStatus = ({
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
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 subscriptions: Array<number> = []
|
||||
|
||||
let timeoutId: number
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -54,7 +59,8 @@ export const useTransactionStatus = ({
|
||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.NOT_FOUND))
|
||||
setMessages([{ id: txHash, data: '' }])
|
||||
timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
|
||||
const timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
|
||||
subscriptions.push(timeoutId)
|
||||
} else {
|
||||
const blockNumber = txReceipt.blockNumber
|
||||
const block = await getBlock(web3, blockNumber)
|
||||
@ -64,9 +70,9 @@ export const useTransactionStatus = ({
|
||||
if (txReceipt.status) {
|
||||
let bridgeMessages: Array<MessageObject>
|
||||
if (isHome) {
|
||||
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, web3, home.bridgeAddress)
|
||||
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, home.web3, home.bridgeAddress)
|
||||
} else {
|
||||
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, web3, foreign.bridgeAddress)
|
||||
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, foreign.web3, foreign.bridgeAddress)
|
||||
}
|
||||
|
||||
if (bridgeMessages.length === 0) {
|
||||
@ -92,9 +98,14 @@ export const useTransactionStatus = ({
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
getReceipt()
|
||||
// unsubscribe from previous txHash
|
||||
unsubscribe()
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
getReceipt()
|
||||
return () => {
|
||||
// unsubscribe when unmount component
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
txHash,
|
||||
|
@ -4,13 +4,18 @@ import Web3 from 'web3'
|
||||
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
|
||||
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 const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => {
|
||||
export interface useValidatorContractParams {
|
||||
fromHome: boolean
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
|
||||
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
|
||||
const [requiredSignatures, setRequiredSignatures] = useState(0)
|
||||
const [validatorList, setValidatorList] = useState<string[]>([])
|
||||
const [validatorList, setValidatorList] = useState([])
|
||||
|
||||
const { home, foreign } = useStateProvider()
|
||||
|
||||
@ -23,50 +28,45 @@ export const useValidatorContract = (isHome: boolean, blockNumber: number | 'lat
|
||||
|
||||
const callRequiredSignatures = async (
|
||||
contract: Maybe<Contract>,
|
||||
blockNumber: number | 'latest',
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
snapshotProvider: SnapshotProvider
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const callValidatorList = async (
|
||||
contract: Maybe<Contract>,
|
||||
blockNumber: number | 'latest',
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function,
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3,
|
||||
api: string
|
||||
snapshotProvider: SnapshotProvider
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
|
||||
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const web3 = isHome ? home.web3 : foreign.web3
|
||||
const api = isHome ? HOME_EXPLORER_API : FOREIGN_EXPLORER_API
|
||||
const bridgeContract = isHome ? home.bridgeContract : foreign.bridgeContract
|
||||
const snapshotProvider = isHome ? 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)
|
||||
},
|
||||
[web3, bridgeContract]
|
||||
[home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!web3 || !blockNumber) return
|
||||
callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
|
||||
callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
|
||||
if (!receipt) return
|
||||
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider
|
||||
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider)
|
||||
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider)
|
||||
},
|
||||
[validatorContract, blockNumber, web3, snapshotProvider, api]
|
||||
[validatorContract, receipt, fromHome]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Web3 from 'web3'
|
||||
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'
|
||||
import { FOREIGN_RPC_POLLING_INTERVAL, HOME_RPC_POLLING_INTERVAL } from '../config/constants'
|
||||
import { 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(FOREIGN_RPC_POLLING_INTERVAL)
|
||||
export const foreignBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createContext, ReactNode } from 'react'
|
||||
import React, { createContext, ReactNode, useState } from 'react'
|
||||
import { useNetwork } from '../hooks/useNetwork'
|
||||
import {
|
||||
HOME_RPC_URL,
|
||||
@ -12,6 +12,7 @@ import Web3 from 'web3'
|
||||
import { useBridgeContracts } from '../hooks/useBridgeContracts'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { foreignSnapshotProvider, homeSnapshotProvider } from '../services/SnapshotProvider'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
export interface BaseNetworkParams {
|
||||
chainId: number
|
||||
@ -21,10 +22,17 @@ export interface BaseNetworkParams {
|
||||
bridgeContract: Maybe<Contract>
|
||||
}
|
||||
|
||||
export interface HomeNetworkParams extends BaseNetworkParams {
|
||||
confirmations: Array<ConfirmationParam>
|
||||
setConfirmations: Function
|
||||
}
|
||||
|
||||
export interface StateContext {
|
||||
home: BaseNetworkParams
|
||||
home: HomeNetworkParams
|
||||
foreign: BaseNetworkParams
|
||||
loading: boolean
|
||||
error: string
|
||||
setError: Function
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
@ -33,7 +41,9 @@ const initialState = {
|
||||
name: '',
|
||||
web3: null,
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
bridgeContract: null,
|
||||
confirmations: [],
|
||||
setConfirmations: () => null
|
||||
},
|
||||
foreign: {
|
||||
chainId: 0,
|
||||
@ -42,7 +52,9 @@ const initialState = {
|
||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
},
|
||||
loading: true
|
||||
loading: true,
|
||||
error: '',
|
||||
setError: () => {}
|
||||
}
|
||||
|
||||
const StateContext = createContext<StateContext>(initialState)
|
||||
@ -54,12 +66,16 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
homeWeb3: homeNetwork.web3,
|
||||
foreignWeb3: foreignNetwork.web3
|
||||
})
|
||||
const [confirmations, setConfirmations] = useState([])
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const value = {
|
||||
home: {
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
name: HOME_NETWORK_NAME,
|
||||
bridgeContract: homeBridge,
|
||||
confirmations,
|
||||
setConfirmations,
|
||||
...homeNetwork
|
||||
},
|
||||
foreign: {
|
||||
@ -68,7 +84,9 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
bridgeContract: foreignBridge,
|
||||
...foreignNetwork
|
||||
},
|
||||
loading: homeNetwork.loading || foreignNetwork.loading
|
||||
loading: homeNetwork.loading || foreignNetwork.loading,
|
||||
error,
|
||||
setError
|
||||
}
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>
|
||||
|
@ -28,7 +28,5 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
|
||||
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
|
||||
--failed-color: ${props => props.theme.failed.textColor};
|
||||
--failed-bg-color: ${props => props.theme.failed.backgroundColor};
|
||||
--warning-color: ${props => props.theme.warning.textColor};
|
||||
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
|
||||
}
|
||||
`
|
||||
|
@ -17,10 +17,6 @@ const theme = {
|
||||
failed: {
|
||||
textColor: '#de4437',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
},
|
||||
warning: {
|
||||
textColor: '#ffa758',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
}
|
||||
}
|
||||
export default theme
|
||||
|
@ -16,7 +16,7 @@ describe('getRequiredBlockConfirmations', () => {
|
||||
|
||||
test('Should call requiredBlockConfirmations method if no events present', async () => {
|
||||
const contract = ({
|
||||
getPastEvents: async () => {
|
||||
getPastEvents: () => {
|
||||
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(async () => []),
|
||||
getPastEvents: jest.fn().mockImplementation(() => []),
|
||||
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(async () => [
|
||||
getPastEvents: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
blockNumber: 9,
|
||||
returnValues: {
|
||||
@ -102,7 +102,7 @@ describe('getRequiredBlockConfirmations', () => {
|
||||
})
|
||||
test('Should use the most updated event', async () => {
|
||||
const contract = ({
|
||||
getPastEvents: jest.fn().mockImplementation(async () => [
|
||||
getPastEvents: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
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(async () => [])
|
||||
getPastEvents: jest.fn().mockImplementation(() => [])
|
||||
} 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(async () => [
|
||||
getPastEvents: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
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(async () => [
|
||||
getPastEvents: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
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(async () => []),
|
||||
getPastEvents: jest.fn().mockImplementation(() => []),
|
||||
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(async () => []),
|
||||
getPastEvents: jest.fn().mockImplementation(() => []),
|
||||
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(async event => {
|
||||
getPastEvents: jest.fn().mockImplementation(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(async () => []),
|
||||
getPastEvents: jest.fn().mockImplementation(() => []),
|
||||
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(async event => {
|
||||
getPastEvents: jest.fn().mockImplementation(event => {
|
||||
if (event === 'ValidatorRemoved') {
|
||||
return [
|
||||
{
|
||||
|
@ -14,40 +14,23 @@ const messageData = '0x123456'
|
||||
const OTHER_HASH = 'aabbccdd'
|
||||
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
|
||||
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
|
||||
describe('getFailedTransactions', () => {
|
||||
test('should only return failed transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
]
|
||||
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
const result = await getFailedTransactions('', '', 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(3)
|
||||
})
|
||||
})
|
||||
describe('getSuccessTransactions', () => {
|
||||
test('should only return success transactions', async () => {
|
||||
const to = otherAddress
|
||||
const transactions = [
|
||||
{ isError: '0', to, from: validator1 },
|
||||
{ isError: '1', to, from: validator1 },
|
||||
{ isError: '0', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator2 },
|
||||
{ isError: '1', to, from: validator3 }
|
||||
]
|
||||
const transactions = [{ isError: '0' }, { isError: '1' }, { isError: '0' }, { isError: '1' }, { isError: '1' }]
|
||||
|
||||
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
|
||||
const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(1)
|
||||
const result = await getSuccessTransactions('', '', 0, 1, '', fetchAccountTransactions)
|
||||
expect(result.length).toEqual(2)
|
||||
})
|
||||
})
|
||||
describe('filterValidatorSignatureTransaction', () => {
|
||||
@ -91,8 +74,8 @@ describe('getExecutionFailedTransactionForMessage', () => {
|
||||
account: '',
|
||||
to: '',
|
||||
messageData,
|
||||
startBlock: 0,
|
||||
endBlock: 1
|
||||
startTimestamp: 0,
|
||||
endTimestamp: 1
|
||||
},
|
||||
fetchAccountTransactions
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { APIPendingTransaction, APITransaction } from '../explorer'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
|
||||
import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations'
|
||||
|
||||
jest.mock('../validatorConfirmationHelpers')
|
||||
|
||||
@ -18,26 +18,15 @@ const messageData = '0x111111111'
|
||||
const web3 = {
|
||||
utils: {
|
||||
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
|
||||
},
|
||||
eth: {
|
||||
accounts: new Web3().eth.accounts
|
||||
}
|
||||
} as Web3
|
||||
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
|
||||
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
|
||||
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
|
||||
const validatorList = [validator1, validator2, validator3]
|
||||
const signature =
|
||||
'0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
|
||||
const bridgeContract = {
|
||||
methods: {
|
||||
signature: () => ({
|
||||
call: () => signature
|
||||
})
|
||||
}
|
||||
} as Contract
|
||||
const bridgeContract = {} as Contract
|
||||
const requiredSignatures = 2
|
||||
const isCancelled = () => false
|
||||
const waitingBlocksResolved = true
|
||||
let subscriptions: Array<number> = []
|
||||
const timestamp = 1594045859
|
||||
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
|
||||
@ -64,19 +53,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -97,8 +86,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -124,20 +113,18 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -146,19 +133,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -179,8 +166,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -191,7 +178,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(1)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -210,19 +197,19 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -243,8 +230,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -256,7 +243,7 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -270,28 +257,18 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -305,22 +282,22 @@ describe('getConfirmationsForTx', () => {
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -341,8 +318,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -354,7 +331,7 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -368,40 +345,20 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED }
|
||||
])
|
||||
)
|
||||
})
|
||||
@ -414,22 +371,22 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
@ -453,8 +410,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -465,7 +422,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -479,35 +436,17 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
@ -521,13 +460,13 @@ describe('getConfirmationsForTx', () => {
|
||||
validator,
|
||||
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator !== validator1
|
||||
@ -536,7 +475,7 @@ describe('getConfirmationsForTx', () => {
|
||||
txHash: validatorData.validator !== validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator1 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -557,8 +496,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -570,7 +509,7 @@ describe('getConfirmationsForTx', () => {
|
||||
unsubscribe()
|
||||
|
||||
expect(subscriptions.length).toEqual(0)
|
||||
expect(setResult).toBeCalledTimes(3)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -584,24 +523,14 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
@ -610,13 +539,9 @@ describe('getConfirmationsForTx', () => {
|
||||
)
|
||||
})
|
||||
test('should remove pending state after transaction mined', async () => {
|
||||
const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
|
||||
const validatorList = [validator1, validator2, validator3, validator4]
|
||||
|
||||
// Validator1 success (ts=100)
|
||||
// Validator2 failed (ts=200)
|
||||
// Validator3 Pending (ts=300)
|
||||
// Validator4 Excess confirmation (Failed) (ts=400)
|
||||
// Validator1 success
|
||||
// Validator2 failed
|
||||
// Validator3 Pending
|
||||
|
||||
getValidatorConfirmation
|
||||
.mockImplementationOnce(() => async (validator: string) => ({
|
||||
@ -627,57 +552,41 @@ describe('getConfirmationsForTx', () => {
|
||||
.mockImplementation(() => async (validator: string) => ({
|
||||
validator,
|
||||
status:
|
||||
validator === validator1 || validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
}))
|
||||
getSuccessExecutionTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash: validatorData.validator === validator1 ? '0x100' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : 0
|
||||
txHash: validatorData.validator === validator1 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 123 : 0
|
||||
}))
|
||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash:
|
||||
validatorData.validator === validator1 ? '0x100' : validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator1 ? 100 : validatorData.validator === validator3 ? 300 : ''
|
||||
}))
|
||||
getValidatorFailedTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x200' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 200 : 0
|
||||
}))
|
||||
.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2 || validatorData.validator === validator4
|
||||
? validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash:
|
||||
validatorData.validator === validator2 ? '0x200' : validatorData.validator === validator4 ? '0x400' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 200 : validatorData.validator === validator4 ? 400 : ''
|
||||
txHash: validatorData.validator !== validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator !== validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator2
|
||||
? VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator2 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator2 ? 123 : 0
|
||||
}))
|
||||
getValidatorPendingTransaction
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status:
|
||||
validatorData.validator === validator3
|
||||
? VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: validatorData.validator === validator3 ? '0x300' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 300 : 0
|
||||
txHash: validatorData.validator === validator3 ? '0x123' : '',
|
||||
timestamp: validatorData.validator === validator3 ? 123 : 0
|
||||
}))
|
||||
.mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
|
||||
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({
|
||||
validator: validatorData.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
txHash: '',
|
||||
@ -698,8 +607,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -710,7 +619,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(setResult).toBeCalledTimes(2)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(1)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
|
||||
expect(setSignatureCollected).toBeCalledTimes(1)
|
||||
@ -718,46 +627,24 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(1)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
|
||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations).toBeCalledTimes(1)
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
|
||||
const res1 = setResult.mock.calls[0][0]()
|
||||
const res2 = setResult.mock.calls[1][0](res1)
|
||||
const res3 = setResult.mock.calls[2][0](res2)
|
||||
const res4 = setResult.mock.calls[3][0](res3)
|
||||
expect(res1).toEqual(
|
||||
expect(setResult.mock.calls[0][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
expect(res2).toEqual(
|
||||
expect(setResult.mock.calls[1][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res3).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res4).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
|
||||
@ -770,8 +657,8 @@ describe('getConfirmationsForTx', () => {
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
@ -782,7 +669,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
unsubscribe()
|
||||
|
||||
expect(setResult).toBeCalledTimes(7)
|
||||
expect(setResult).toBeCalledTimes(4)
|
||||
expect(getValidatorConfirmation).toBeCalledTimes(2)
|
||||
expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
|
||||
expect(setSignatureCollected).toBeCalledTimes(2)
|
||||
@ -791,7 +678,7 @@ describe('getConfirmationsForTx', () => {
|
||||
|
||||
expect(getValidatorFailedTransaction).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations).toBeCalledTimes(2)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false)
|
||||
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
|
||||
|
||||
expect(getValidatorPendingTransaction).toBeCalledTimes(1)
|
||||
@ -799,31 +686,18 @@ describe('getConfirmationsForTx', () => {
|
||||
expect(setPendingConfirmations.mock.calls[0][0]).toEqual(true)
|
||||
expect(setPendingConfirmations.mock.calls[1][0]).toEqual(false)
|
||||
|
||||
const res5 = setResult.mock.calls[4][0](res4)
|
||||
const res6 = setResult.mock.calls[5][0](res5)
|
||||
const res7 = setResult.mock.calls[6][0](res6)
|
||||
expect(res5).toEqual(
|
||||
expect(setResult.mock.calls[2][0]()).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }
|
||||
])
|
||||
)
|
||||
expect(res6).toEqual(
|
||||
expect(setResult.mock.calls[3][0]).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
|
||||
])
|
||||
)
|
||||
expect(res7).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
|
||||
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
|
||||
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 },
|
||||
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }
|
||||
])
|
||||
)
|
||||
})
|
||||
|
@ -4,6 +4,7 @@ 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'
|
||||
@ -19,11 +20,12 @@ const web3 = ({
|
||||
toChecksumAddress: (a: string) => a
|
||||
}
|
||||
} as unknown) as Web3
|
||||
const waitingBlocksResolved = true
|
||||
const message = {
|
||||
id: '0x123',
|
||||
data: '0x123456789'
|
||||
}
|
||||
const isCancelled = () => false
|
||||
const interval = 10000
|
||||
let subscriptions: Array<number> = []
|
||||
|
||||
const event = {
|
||||
@ -48,7 +50,7 @@ beforeEach(() => {
|
||||
describe('getFinalizationEvent', () => {
|
||||
test('should get finalization event and not try to get failed or pending transactions', async () => {
|
||||
const contract = ({
|
||||
getPastEvents: async () => {
|
||||
getPastEvents: () => {
|
||||
return [event]
|
||||
}
|
||||
} as unknown) as Contract
|
||||
@ -62,13 +64,14 @@ describe('getFinalizationEvent', () => {
|
||||
const setExecutionEventsFetched = jest.fn()
|
||||
|
||||
await getFinalizationEvent(
|
||||
true,
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
@ -84,11 +87,10 @@ describe('getFinalizationEvent', () => {
|
||||
expect(setResult).toBeCalledTimes(1)
|
||||
expect(setResult.mock.calls[0][0]).toEqual({
|
||||
validator: validator1,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
txHash,
|
||||
timestamp,
|
||||
executionResult: true,
|
||||
blockNumber: 5523145
|
||||
executionResult: true
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -99,7 +101,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: async () => {
|
||||
getPastEvents: () => {
|
||||
return []
|
||||
}
|
||||
} as unknown) as Contract
|
||||
@ -113,13 +115,14 @@ describe('getFinalizationEvent', () => {
|
||||
const setExecutionEventsFetched = jest.fn()
|
||||
|
||||
await getFinalizationEvent(
|
||||
true,
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
@ -142,7 +145,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: async () => {
|
||||
getPastEvents: () => {
|
||||
return []
|
||||
},
|
||||
options: {
|
||||
@ -163,13 +166,14 @@ describe('getFinalizationEvent', () => {
|
||||
const setExecutionEventsFetched = jest.fn()
|
||||
|
||||
await getFinalizationEvent(
|
||||
true,
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
@ -192,7 +196,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: async () => {
|
||||
getPastEvents: () => {
|
||||
return []
|
||||
},
|
||||
options: {
|
||||
@ -213,13 +217,14 @@ describe('getFinalizationEvent', () => {
|
||||
const setExecutionEventsFetched = jest.fn()
|
||||
|
||||
await getFinalizationEvent(
|
||||
true,
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
@ -238,8 +243,7 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(0)
|
||||
@ -250,7 +254,7 @@ describe('getFinalizationEvent', () => {
|
||||
})
|
||||
test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => {
|
||||
const contract = ({
|
||||
getPastEvents: async () => {
|
||||
getPastEvents: () => {
|
||||
return []
|
||||
},
|
||||
options: {
|
||||
@ -271,13 +275,14 @@ describe('getFinalizationEvent', () => {
|
||||
const setExecutionEventsFetched = jest.fn()
|
||||
|
||||
await getFinalizationEvent(
|
||||
true,
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
subscriptions.push.bind(subscriptions),
|
||||
isCancelled,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
@ -296,8 +301,7 @@ describe('getFinalizationEvent', () => {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
txHash,
|
||||
timestamp: expect.any(Number),
|
||||
executionResult: false,
|
||||
blockNumber: expect.any(Number)
|
||||
executionResult: false
|
||||
})
|
||||
|
||||
expect(getFailedExecution).toBeCalledTimes(1)
|
||||
|
@ -1,43 +1,18 @@
|
||||
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,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
snapshotProvider: SnapshotProvider
|
||||
) => {
|
||||
let blockConfirmations
|
||||
|
||||
try {
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
} catch {}
|
||||
|
||||
if (blockConfirmations) {
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
let contractEvents: EventData[] = []
|
||||
if (blockNumber > snapshotBlockNumber) {
|
||||
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredBlockConfirmationChanged', {
|
||||
contractEvents = await contract.getPastEvents('RequiredBlockConfirmationChanged', {
|
||||
fromBlock: snapshotBlockNumber + 1,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
@ -45,10 +20,16 @@ export const getRequiredBlockConfirmations = async (
|
||||
|
||||
const events = [...eventsFromSnapshot, ...contractEvents]
|
||||
|
||||
// Use the value from last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
||||
|
||||
let blockConfirmations
|
||||
if (events.length > 0) {
|
||||
// Use the value from last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
||||
} else {
|
||||
// This is a special case where RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
|
||||
// of Sokol - Kovan. In this case the current value is used.
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
}
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
@ -56,31 +37,15 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
|
||||
|
||||
export const getRequiredSignatures = async (
|
||||
contract: Contract,
|
||||
blockNumber: number | 'latest',
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
blockNumber: number,
|
||||
snapshotProvider: SnapshotProvider
|
||||
) => {
|
||||
let requiredSignatures
|
||||
|
||||
try {
|
||||
requiredSignatures = await contract.methods.requiredSignatures().call()
|
||||
} catch {}
|
||||
|
||||
if (requiredSignatures) {
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
if (blockNumber === 'latest') {
|
||||
return contract.methods.requiredSignatures().call()
|
||||
}
|
||||
|
||||
const eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
|
||||
let contractEvents: EventData[] = []
|
||||
if (blockNumber > snapshotBlockNumber) {
|
||||
contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredSignaturesChanged', {
|
||||
contractEvents = await contract.getPastEvents('RequiredSignaturesChanged', {
|
||||
fromBlock: snapshotBlockNumber + 1,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
@ -90,25 +55,11 @@ export const getRequiredSignatures = async (
|
||||
|
||||
// Use the value form last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
;({ requiredSignatures } = event.returnValues)
|
||||
const { requiredSignatures } = event.returnValues
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
export const getValidatorList = async (
|
||||
contract: Contract,
|
||||
blockNumber: number | 'latest',
|
||||
snapshotProvider: SnapshotProvider,
|
||||
web3: Web3 | null = null,
|
||||
api: string = ''
|
||||
) => {
|
||||
try {
|
||||
const currentList = await contract.methods.validatorList().call()
|
||||
|
||||
if (currentList) {
|
||||
return currentList
|
||||
}
|
||||
} catch {}
|
||||
|
||||
export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => {
|
||||
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
|
||||
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
|
||||
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
|
||||
@ -116,10 +67,10 @@ export const getValidatorList = async (
|
||||
const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber
|
||||
const [currentList, added, removed] = await Promise.all([
|
||||
contract.methods.validatorList().call(),
|
||||
getPastEventsWithFallback(api, web3, contract, 'ValidatorAdded', {
|
||||
contract.getPastEvents('ValidatorAdded', {
|
||||
fromBlock
|
||||
}),
|
||||
getPastEventsWithFallback(api, web3, contract, 'ValidatorRemoved', {
|
||||
contract.getPastEvents('ValidatorRemoved', {
|
||||
fromBlock
|
||||
})
|
||||
])
|
||||
|
58
alm/src/utils/executionWaitingForBlocks.ts
Normal file
58
alm/src/utils/executionWaitingForBlocks.ts
Normal file
@ -0,0 +1,58 @@
|
||||
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) {
|
||||
setExecutionData({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
setWaitingBlocksForExecutionResolved(true)
|
||||
setWaitingBlocksForExecution(false)
|
||||
blockProvider.stop()
|
||||
} else {
|
||||
let nextInterval = interval
|
||||
if (!currentBlock) {
|
||||
nextInterval = 500
|
||||
} else {
|
||||
setWaitingBlocksForExecution(true)
|
||||
setExecutionData({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
checkWaitingBlocksForExecution(
|
||||
blockProvider,
|
||||
interval,
|
||||
targetBlock,
|
||||
collectedSignaturesEvent,
|
||||
setWaitingBlocksForExecution,
|
||||
setWaitingBlocksForExecutionResolved,
|
||||
setExecutionData,
|
||||
subscriptions
|
||||
),
|
||||
nextInterval
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
@ -1,24 +1,17 @@
|
||||
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 {
|
||||
from: string
|
||||
timeStamp: string
|
||||
isError: string
|
||||
input: string
|
||||
to: string
|
||||
hash: string
|
||||
blockNumber: string
|
||||
}
|
||||
|
||||
export interface APIPendingTransaction {
|
||||
@ -34,54 +27,110 @@ export interface PendingTransactionsParams {
|
||||
|
||||
export interface AccountTransactionsParams {
|
||||
account: string
|
||||
startBlock: number
|
||||
endBlock: number
|
||||
to: string
|
||||
startTimestamp: number
|
||||
endTimestamp: 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 interface GetTransactionParams extends GetPendingTransactionParams {
|
||||
startBlock: number
|
||||
endBlock: number
|
||||
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 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', 'to')
|
||||
url.searchParams.append('startblock', startBlock.toString())
|
||||
url.searchParams.append('endblock', endBlock.toString())
|
||||
export const getBlockByTimestampUrl = (api: string, timestamp: number) =>
|
||||
`${api}&module=block&action=getblocknobytime×tamp=${timestamp}&closest=before`
|
||||
|
||||
const result = await fetch(url.toString()).then(res => res.json())
|
||||
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())
|
||||
])
|
||||
|
||||
if (result.message === 'No transactions found') {
|
||||
if (fromBlockResult.status !== '0') {
|
||||
fromBlock = parseInt(fromBlockResult.result)
|
||||
}
|
||||
|
||||
if (toBlockResult.status !== '0') {
|
||||
toBlock = parseInt(toBlockResult.result)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result || []
|
||||
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
|
||||
}
|
||||
|
||||
export const fetchPendingTransactions = async ({
|
||||
account,
|
||||
api
|
||||
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
|
||||
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)
|
||||
const url = `${api}?module=account&action=pendingtxlist&address=${account}`
|
||||
|
||||
try {
|
||||
const result = await fetch(url.toString()).then(res => res.json())
|
||||
const result = await fetch(url).then(res => res.json())
|
||||
if (result.status === '0') {
|
||||
return []
|
||||
}
|
||||
@ -92,137 +141,30 @@ 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++) {
|
||||
if (topics[j] !== null) {
|
||||
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 filterSender = (from: string) => (tx: APITransaction) => tx.from.toLowerCase() === from.toLowerCase()
|
||||
|
||||
export const getFailedTransactions = async (
|
||||
account: string,
|
||||
to: string,
|
||||
startBlock: number,
|
||||
endBlock: number,
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
||||
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
|
||||
|
||||
return transactions.filter(t => t.isError !== '0').filter(filterSender(account))
|
||||
return transactions.filter(t => t.isError !== '0')
|
||||
}
|
||||
|
||||
export const getSuccessTransactions = async (
|
||||
account: string,
|
||||
to: string,
|
||||
startBlock: number,
|
||||
endBlock: number,
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
api: string,
|
||||
getAccountTransactionsMethod = getAccountTransactions
|
||||
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
|
||||
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
|
||||
|
||||
return transactions.filter(t => t.isError === '0').filter(filterSender(account))
|
||||
return transactions.filter(t => t.isError === '0')
|
||||
}
|
||||
|
||||
export const filterValidatorSignatureTransaction = (
|
||||
@ -241,10 +183,17 @@ export const getValidatorFailedTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData,
|
||||
startBlock,
|
||||
endBlock
|
||||
}: GetTransactionParams): Promise<APITransaction[]> => {
|
||||
const failedTransactions = await getFailedTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
|
||||
startTimestamp,
|
||||
endTimestamp
|
||||
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
|
||||
const failedTransactions = await getFailedTransactions(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
HOME_EXPLORER_API,
|
||||
fetchAccountTransactionsFromBlockscout
|
||||
)
|
||||
|
||||
return filterValidatorSignatureTransaction(failedTransactions, messageData)
|
||||
}
|
||||
@ -253,19 +202,33 @@ export const getValidatorSuccessTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData,
|
||||
startBlock,
|
||||
endBlock
|
||||
}: GetTransactionParams): Promise<APITransaction[]> => {
|
||||
const transactions = await getSuccessTransactions(account, to, startBlock, endBlock, HOME_EXPLORER_API)
|
||||
startTimestamp,
|
||||
endTimestamp
|
||||
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
|
||||
const transactions = await getSuccessTransactions(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
HOME_EXPLORER_API,
|
||||
fetchAccountTransactionsFromBlockscout
|
||||
)
|
||||
|
||||
return filterValidatorSignatureTransaction(transactions, messageData)
|
||||
}
|
||||
|
||||
export const getExecutionFailedTransactionForMessage = async (
|
||||
{ account, to, messageData, startBlock, endBlock }: GetTransactionParams,
|
||||
{ account, to, messageData, startTimestamp, endTimestamp }: GetFailedTransactionParams,
|
||||
getFailedTransactionsMethod = getFailedTransactions
|
||||
): Promise<APITransaction[]> => {
|
||||
const failedTransactions = await getFailedTransactionsMethod(account, to, startBlock, endBlock, FOREIGN_EXPLORER_API)
|
||||
const failedTransactions = await getFailedTransactionsMethod(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
FOREIGN_EXPLORER_API,
|
||||
fetchAccountTransactions(FOREIGN_EXPLORER_API)
|
||||
)
|
||||
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))
|
||||
|
53
alm/src/utils/getCollectedSignaturesEvent.ts
Normal file
53
alm/src/utils/getCollectedSignaturesEvent.ts
Normal file
@ -0,0 +1,53 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
|
||||
import {
|
||||
GetFailedTransactionParams,
|
||||
APITransaction,
|
||||
APIPendingTransaction,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
import {
|
||||
getValidatorConfirmation,
|
||||
getValidatorFailedTransaction,
|
||||
@ -9,116 +15,71 @@ import {
|
||||
getSuccessExecutionTransaction
|
||||
} from './validatorConfirmationHelpers'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { signatureToVRS } from './signatures'
|
||||
|
||||
const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => {
|
||||
const confirmations = [...oldConfirmations]
|
||||
newConfirmations.forEach(validatorData => {
|
||||
const index = confirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
if (index === -1) {
|
||||
confirmations.push(validatorData)
|
||||
return
|
||||
}
|
||||
const currentStatus = confirmations[index].status
|
||||
const newStatus = validatorData.status
|
||||
if (
|
||||
validatorData.txHash ||
|
||||
!!validatorData.signature ||
|
||||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
|
||||
) {
|
||||
confirmations[index] = {
|
||||
status: validatorData.status,
|
||||
validator: validatorData.validator,
|
||||
timestamp: confirmations[index].timestamp || validatorData.timestamp,
|
||||
txHash: confirmations[index].txHash || validatorData.txHash,
|
||||
signature: confirmations[index].signature || validatorData.signature
|
||||
}
|
||||
}
|
||||
})
|
||||
return confirmations
|
||||
}
|
||||
|
||||
export const getConfirmationsForTx = async (
|
||||
messageData: string,
|
||||
web3: Web3,
|
||||
web3: Maybe<Web3>,
|
||||
validatorList: string[],
|
||||
bridgeContract: Contract,
|
||||
bridgeContract: Maybe<Contract>,
|
||||
fromHome: boolean,
|
||||
setResult: Function,
|
||||
requiredSignatures: number,
|
||||
setSignatureCollected: Function,
|
||||
setTimeoutId: (timeoutId: number) => void,
|
||||
isCancelled: () => boolean,
|
||||
startBlock: number,
|
||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
|
||||
waitingBlocksResolved: boolean,
|
||||
subscriptions: number[],
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
|
||||
setFailedConfirmations: Function,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
||||
setPendingConfirmations: Function,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => {
|
||||
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
|
||||
// If all the information was not collected, then it should retry
|
||||
let shouldRetry = false
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
let validatorConfirmations = await Promise.all(
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, fromHome))
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
|
||||
)
|
||||
|
||||
const updateConfirmations = (confirmations: ConfirmationParam[]) => {
|
||||
if (confirmations.length === 0) {
|
||||
return
|
||||
}
|
||||
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
|
||||
setResult((currentConfirmations: ConfirmationParam[]) => {
|
||||
if (currentConfirmations && currentConfirmations.length) {
|
||||
return mergeConfirmations(currentConfirmations, confirmations)
|
||||
}
|
||||
return confirmations
|
||||
})
|
||||
}
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
const hasEnoughSignatures = successConfirmations.length >= requiredSignatures
|
||||
|
||||
updateConfirmations(validatorConfirmations)
|
||||
setSignatureCollected(hasEnoughSignatures)
|
||||
|
||||
if (hasEnoughSignatures) {
|
||||
setPendingConfirmations(false)
|
||||
if (fromHome) {
|
||||
// fetch collected signatures for possible manual processing
|
||||
const signatures = await Promise.all(
|
||||
Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
|
||||
)
|
||||
const confirmations = signatures.flatMap(sig => {
|
||||
const { v, r, s } = signatureToVRS(sig)
|
||||
const address = web3.eth.accounts.recover(messageData, `0x${v}`, `0x${r}`, `0x${s}`)
|
||||
return successConfirmations.filter(c => c.validator === address).map(c => ({ ...c, signature: sig }))
|
||||
setResult((prevConfirmations: ConfirmationParam[]) => {
|
||||
if (prevConfirmations && prevConfirmations.length) {
|
||||
successConfirmations.forEach(validatorData => {
|
||||
const index = prevConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
updateConfirmations(confirmations)
|
||||
return prevConfirmations
|
||||
} else {
|
||||
return validatorConfirmations
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// get transactions from success signatures
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
successConfirmations.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, startBlock, getSuccessTransactions)
|
||||
)
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
updateConfirmations(successConfirmationWithTxFound)
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
|
||||
// If signatures not collected, look for pending transactions
|
||||
if (!hasEnoughSignatures) {
|
||||
let pendingConfirmationsResult = false
|
||||
if (successConfirmations.length !== requiredSignatures) {
|
||||
// Check if confirmation is pending
|
||||
const validatorPendingConfirmationsChecks = await Promise.all(
|
||||
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
|
||||
)
|
||||
|
||||
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
updateConfirmations(validatorPendingConfirmations)
|
||||
setPendingConfirmations(validatorPendingConfirmations.length > 0)
|
||||
|
||||
validatorPendingConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
|
||||
if (validatorPendingConfirmations.length > 0) {
|
||||
pendingConfirmationsResult = true
|
||||
}
|
||||
}
|
||||
|
||||
const undefinedConfirmations = validatorConfirmations.filter(
|
||||
@ -126,76 +87,101 @@ export const getConfirmationsForTx = async (
|
||||
)
|
||||
|
||||
// Check if confirmation failed
|
||||
let failedConfirmationsResult = false
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
|
||||
)
|
||||
)
|
||||
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
)
|
||||
if (hasEnoughSignatures && !fromHome) {
|
||||
const lastTS = Math.max(...successConfirmationWithTxFound.map(c => c.timestamp || 0))
|
||||
validatorFailedConfirmations = validatorFailedConfirmations.map(
|
||||
c =>
|
||||
c.timestamp < lastTS
|
||||
? c
|
||||
: {
|
||||
...c,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
}
|
||||
)
|
||||
validatorFailedConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures
|
||||
if (messageConfirmationsFailed) {
|
||||
failedConfirmationsResult = true
|
||||
}
|
||||
setFailedConfirmations(
|
||||
!hasEnoughSignatures && validatorFailedConfirmations.some(c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED)
|
||||
)
|
||||
updateConfirmations(validatorFailedConfirmations)
|
||||
|
||||
const missingConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
if (hasEnoughSignatures) {
|
||||
if (successConfirmations.length !== requiredSignatures && missingConfirmations.length > 0) {
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
let signatureCollectedResult = false
|
||||
if (successConfirmations.length === requiredSignatures) {
|
||||
// If signatures collected, it should set other signatures not found as not required
|
||||
const notRequiredConfirmations = missingConfirmations.map(c => ({
|
||||
validator: c.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
|
||||
}))
|
||||
updateConfirmations(notRequiredConfirmations)
|
||||
|
||||
notRequiredConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
signatureCollectedResult = true
|
||||
}
|
||||
|
||||
// retry if not all signatures are collected and some confirmations are still missing
|
||||
// or some success transactions were not fetched successfully
|
||||
if (
|
||||
(!hasEnoughSignatures && missingConfirmations.length > 0) ||
|
||||
successConfirmationWithTxFound.length < successConfirmationWithData.length
|
||||
) {
|
||||
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
|
||||
// get transactions from success signatures
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
validatorConfirmations
|
||||
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
.map(
|
||||
getSuccessExecutionTransaction(web3, bridgeContract, fromHome, messageData, timestamp, getSuccessTransactions)
|
||||
)
|
||||
setTimeoutId(timeoutId)
|
||||
}
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
|
||||
const updatedValidatorConfirmations = [...validatorConfirmations]
|
||||
|
||||
if (successConfirmationWithTxFound.length > 0) {
|
||||
successConfirmationWithTxFound.forEach(validatorData => {
|
||||
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
updatedValidatorConfirmations[index] = validatorData
|
||||
})
|
||||
}
|
||||
|
||||
// Set results
|
||||
setResult(updatedValidatorConfirmations)
|
||||
setFailedConfirmations(failedConfirmationsResult)
|
||||
setPendingConfirmations(pendingConfirmationsResult)
|
||||
setSignatureCollected(signatureCollectedResult)
|
||||
|
||||
// Retry if not all transaction were found for validator confirmations
|
||||
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) {
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
if (shouldRetry) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,41 @@
|
||||
import { Contract, EventData } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
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 { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import {
|
||||
APIPendingTransaction,
|
||||
APITransaction,
|
||||
GetTransactionParams,
|
||||
GetPendingTransactionParams,
|
||||
getLogs
|
||||
GetFailedTransactionParams,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { getBlock, MessageObject } from './web3'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
|
||||
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,
|
||||
export const getFinalizationEvent = async (
|
||||
contract: Maybe<Contract>,
|
||||
eventName: string,
|
||||
web3: Web3,
|
||||
messageId: string,
|
||||
api: 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
|
||||
) => {
|
||||
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 getPastEventsWithFallback(api, web3, contract, eventName, {
|
||||
const events: EventData[] = await contract.getPastEvents(eventName, {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
filter: {
|
||||
messageId
|
||||
messageId: message.id
|
||||
}
|
||||
})
|
||||
if (events.length > 0) {
|
||||
@ -58,41 +48,13 @@ export const getSuccessExecutionData = async (
|
||||
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
|
||||
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator: validatorAddress,
|
||||
txHash: event.transactionHash,
|
||||
timestamp: blockTimestamp,
|
||||
executionResult: event.returnValues.status,
|
||||
blockNumber: event.blockNumber
|
||||
}
|
||||
}
|
||||
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)
|
||||
executionResult: event.returnValues.status
|
||||
})
|
||||
} else {
|
||||
setExecutionEventsFetched(true)
|
||||
// If event is defined, it means it is a message from Home to Foreign
|
||||
@ -116,22 +78,20 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: pendingTx.hash,
|
||||
timestamp: nowTimestamp,
|
||||
executionResult: false,
|
||||
blockNumber: 0
|
||||
executionResult: false
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} 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,
|
||||
startBlock,
|
||||
endBlock: blockProvider.get() || 0
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
|
||||
})
|
||||
|
||||
if (failedTransactions.length > 0) {
|
||||
@ -146,8 +106,7 @@ export const getFinalizationEvent = async (
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false,
|
||||
blockNumber: parseInt(failedTx.blockNumber)
|
||||
executionResult: false
|
||||
})
|
||||
setFailedExecution(true)
|
||||
}
|
||||
@ -155,28 +114,27 @@ export const getFinalizationEvent = async (
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
getFinalizationEvent(
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
setFailedExecution,
|
||||
getPendingExecution,
|
||||
setPendingExecution,
|
||||
setExecutionEventsFetched
|
||||
),
|
||||
interval
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
|
50
alm/src/utils/signatureWaitingForBlocks.ts
Normal file
50
alm/src/utils/signatureWaitingForBlocks.ts
Normal file
@ -0,0 +1,50 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,45 +1,47 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
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'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from './contract'
|
||||
import {
|
||||
CACHE_KEY_FAILED,
|
||||
CACHE_KEY_SUCCESS,
|
||||
ONE_DAY_TIMESTAMP,
|
||||
VALIDATOR_CONFIRMATION_STATUS
|
||||
} from '../config/constants'
|
||||
import {
|
||||
APIPendingTransaction,
|
||||
APITransaction,
|
||||
GetFailedTransactionParams,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
|
||||
export const getValidatorConfirmation = (
|
||||
web3: Web3,
|
||||
hashMsg: string,
|
||||
bridgeContract: Contract,
|
||||
fromHome: boolean
|
||||
) => async (validator: string): Promise<ConfirmationParam> => {
|
||||
confirmationContractMethod: Function
|
||||
) => async (validator: string): Promise<BasicConfirmationParam> => {
|
||||
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
|
||||
|
||||
const fromCache = validatorsCache.getData(hashSenderMsg)
|
||||
if (fromCache) {
|
||||
return fromCache
|
||||
const signatureFromCache = validatorsCache.get(hashSenderMsg)
|
||||
if (signatureFromCache) {
|
||||
return {
|
||||
validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg)
|
||||
const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
// If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (confirmed) {
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
}
|
||||
validatorsCache.setData(hashSenderMsg, confirmation)
|
||||
return confirmation
|
||||
validatorsCache.set(hashSenderMsg, confirmed)
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator,
|
||||
timestamp: 0,
|
||||
txHash: ''
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,9 +50,9 @@ export const getSuccessExecutionTransaction = (
|
||||
bridgeContract: Contract,
|
||||
fromHome: boolean,
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
timestamp: number,
|
||||
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const { validator } = validatorData
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
||||
@ -63,24 +65,30 @@ export const getSuccessExecutionTransaction = (
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData,
|
||||
startBlock,
|
||||
endBlock: homeBlockNumberProvider.get() || 0
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
|
||||
})
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
let signature = ''
|
||||
const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
|
||||
if (transactions.length > 0) {
|
||||
const tx = transactions[0]
|
||||
txHashTimestamp = parseInt(tx.timeStamp)
|
||||
txHash = tx.hash
|
||||
if (fromHome) {
|
||||
const decoded = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${tx.input.substr(10)}`)
|
||||
signature = decoded[0]
|
||||
}
|
||||
|
||||
// cache the result
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
signature,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
@ -89,17 +97,17 @@ export const getSuccessExecutionTransaction = (
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
signature,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
export const getValidatorFailedTransaction = (
|
||||
web3: Web3,
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
startBlock: number,
|
||||
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
@ -111,36 +119,33 @@ export const getValidatorFailedTransaction = (
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData,
|
||||
startBlock,
|
||||
endBlock: homeBlockNumberProvider.get() || 0
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
|
||||
})
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
const confirmation: ConfirmationParam = {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validatorData.validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp: parseInt(failedTx.timeStamp)
|
||||
}
|
||||
txHashTimestamp = parseInt(failedTx.timeStamp)
|
||||
txHash = failedTx.hash
|
||||
|
||||
if (failedTx.input && failedTx.input.length > 10) {
|
||||
try {
|
||||
const res = web3.eth.abi.decodeParameters(['bytes', 'bytes'], `0x${failedTx.input.slice(10)}`)
|
||||
confirmation.signature = res[0]
|
||||
confirmation.status = VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
|
||||
console.log(`Adding manual signature from failed message from ${validatorData.validator}`)
|
||||
} catch {}
|
||||
}
|
||||
validatorsCache.setData(validatorCacheKey, confirmation)
|
||||
return confirmation
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: validatorData.validator,
|
||||
txHash: '',
|
||||
timestamp: 0
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +153,7 @@ export const getValidatorPendingTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
||||
) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const failedTransactions = await getPendingTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
|
@ -10,37 +10,6 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
|
||||
export interface MessageObject {
|
||||
id: string
|
||||
data: string
|
||||
sender?: string
|
||||
executor?: string
|
||||
obToken?: string
|
||||
obReceiver?: string
|
||||
}
|
||||
|
||||
export interface WarnRule {
|
||||
message: string
|
||||
sender?: string
|
||||
executor?: string
|
||||
obToken?: string
|
||||
obReceiver?: string
|
||||
}
|
||||
|
||||
export const matchesRule = (rule: WarnRule, msg: MessageObject) => {
|
||||
if (!msg.executor || !msg.sender) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.executor && rule.executor.toLowerCase() !== msg.executor.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.sender && rule.sender.toLowerCase() !== msg.sender.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.obToken && (!msg.obToken || rule.obToken.toLowerCase() !== msg.obToken.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
if (!!rule.obReceiver && (!msg.obReceiver || rule.obReceiver.toLowerCase() !== msg.obReceiver.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
|
||||
@ -57,33 +26,15 @@ export const filterEventsByAbi = (
|
||||
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
|
||||
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
|
||||
|
||||
if (!eventAbi || !eventAbi.inputs || !eventAbi.inputs.length) {
|
||||
return []
|
||||
}
|
||||
const inputs = eventAbi.inputs
|
||||
return events.map(e => {
|
||||
const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
|
||||
let sender, executor, obToken, obReceiver
|
||||
if (encodedData.length >= 160) {
|
||||
sender = `0x${encodedData.slice(66, 106)}`
|
||||
executor = `0x${encodedData.slice(106, 146)}`
|
||||
const dataOffset =
|
||||
160 + (parseInt(encodedData.slice(154, 156), 16) + parseInt(encodedData.slice(156, 158), 16)) * 2 + 8
|
||||
if (encodedData.length >= dataOffset + 64) {
|
||||
obToken = `0x${encodedData.slice(dataOffset + 24, dataOffset + 64)}`
|
||||
}
|
||||
if (encodedData.length >= dataOffset + 128) {
|
||||
obReceiver = `0x${encodedData.slice(dataOffset + 88, dataOffset + 128)}`
|
||||
}
|
||||
let decodedLogs: { [p: string]: string } = {
|
||||
messageId: '',
|
||||
encodedData: ''
|
||||
}
|
||||
return {
|
||||
id: messageId || '',
|
||||
data: encodedData || '',
|
||||
sender,
|
||||
executor,
|
||||
obToken,
|
||||
obReceiver
|
||||
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) {
|
||||
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]])
|
||||
}
|
||||
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
|
||||
})
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,7 +1,13 @@
|
||||
const HOME_NATIVE_TO_ERC_ABI = require('../contracts/build/contracts/HomeBridgeNativeToErc').abi
|
||||
const FOREIGN_NATIVE_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeNativeToErc').abi
|
||||
const HOME_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeBridgeErcToErc').abi
|
||||
const FOREIGN_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignBridgeErc677ToErc677').abi
|
||||
const HOME_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/HomeBridgeErcToNative').abi
|
||||
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/XDaiForeignBridge.json').abi
|
||||
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative').abi
|
||||
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
|
||||
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockRewardMock').abi
|
||||
const ERC677_ABI = require('../contracts/build/contracts/ERC677').abi
|
||||
const ERC677_BRIDGE_TOKEN_ABI = require('../contracts/build/contracts/ERC677BridgeToken').abi
|
||||
const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockReward').abi
|
||||
const BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi
|
||||
const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/RewardableValidators').abi
|
||||
const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi
|
||||
@ -9,9 +15,43 @@ const FOREIGN_AMB_ABI = require('../contracts/build/contracts/ForeignAMB').abi
|
||||
const BOX_ABI = require('../contracts/build/contracts/Box').abi
|
||||
const HOME_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeAMBErc677ToErc677').abi
|
||||
const FOREIGN_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignAMBErc677ToErc677').abi
|
||||
const HOME_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeStakeTokenMediator').abi
|
||||
const FOREIGN_STAKE_ERC_TO_ERC_ABI = require('../contracts/build/contracts/ForeignStakeTokenMediator').abi
|
||||
|
||||
const { HOME_V1_ABI, FOREIGN_V1_ABI } = require('./v1Abis')
|
||||
const { BRIDGE_MODES } = require('./constants')
|
||||
|
||||
const ERC20_BYTES32_ABI = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'name',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'symbol',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
const OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI = [
|
||||
{
|
||||
anonymous: false,
|
||||
@ -45,15 +85,27 @@ const OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI = [
|
||||
function getBridgeABIs(bridgeMode) {
|
||||
let HOME_ABI = null
|
||||
let FOREIGN_ABI = null
|
||||
if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
|
||||
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) {
|
||||
HOME_ABI = HOME_NATIVE_TO_ERC_ABI
|
||||
FOREIGN_ABI = FOREIGN_NATIVE_TO_ERC_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
|
||||
HOME_ABI = HOME_ERC_TO_ERC_ABI
|
||||
FOREIGN_ABI = FOREIGN_ERC_TO_ERC_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
|
||||
HOME_ABI = HOME_ERC_TO_NATIVE_ABI
|
||||
FOREIGN_ABI = FOREIGN_ERC_TO_NATIVE_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC_V1) {
|
||||
HOME_ABI = HOME_V1_ABI
|
||||
FOREIGN_ABI = FOREIGN_V1_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
|
||||
HOME_ABI = HOME_AMB_ABI
|
||||
FOREIGN_ABI = FOREIGN_AMB_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC) {
|
||||
HOME_ABI = HOME_AMB_ERC_TO_ERC_ABI
|
||||
FOREIGN_ABI = FOREIGN_AMB_ERC_TO_ERC_ABI
|
||||
} else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) {
|
||||
HOME_ABI = HOME_STAKE_ERC_TO_ERC_ABI
|
||||
FOREIGN_ABI = FOREIGN_STAKE_ERC_TO_ERC_ABI
|
||||
} else {
|
||||
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
|
||||
}
|
||||
@ -63,15 +115,26 @@ function getBridgeABIs(bridgeMode) {
|
||||
|
||||
module.exports = {
|
||||
getBridgeABIs,
|
||||
HOME_NATIVE_TO_ERC_ABI,
|
||||
FOREIGN_NATIVE_TO_ERC_ABI,
|
||||
HOME_ERC_TO_ERC_ABI,
|
||||
FOREIGN_ERC_TO_ERC_ABI,
|
||||
HOME_ERC_TO_NATIVE_ABI,
|
||||
FOREIGN_ERC_TO_NATIVE_ABI,
|
||||
ERC20_ABI,
|
||||
ERC677_ABI,
|
||||
ERC677_BRIDGE_TOKEN_ABI,
|
||||
BLOCK_REWARD_ABI,
|
||||
BRIDGE_VALIDATORS_ABI,
|
||||
REWARDABLE_VALIDATORS_ABI,
|
||||
HOME_V1_ABI,
|
||||
FOREIGN_V1_ABI,
|
||||
ERC20_BYTES32_ABI,
|
||||
HOME_AMB_ABI,
|
||||
FOREIGN_AMB_ABI,
|
||||
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI,
|
||||
OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI,
|
||||
BOX_ABI
|
||||
BOX_ABI,
|
||||
HOME_STAKE_ERC_TO_ERC_ABI,
|
||||
FOREIGN_STAKE_ERC_TO_ERC_ABI
|
||||
}
|
||||
|
@ -1,12 +1,30 @@
|
||||
const BRIDGE_MODES = {
|
||||
NATIVE_TO_ERC: 'NATIVE_TO_ERC',
|
||||
ERC_TO_ERC: 'ERC_TO_ERC',
|
||||
ERC_TO_NATIVE: 'ERC_TO_NATIVE',
|
||||
NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1',
|
||||
ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE',
|
||||
AMB_ERC_TO_ERC: 'AMB_ERC_TO_ERC'
|
||||
AMB_ERC_TO_ERC: 'AMB_ERC_TO_ERC',
|
||||
STAKE_AMB_ERC_TO_ERC: 'STAKE_AMB_ERC_TO_ERC'
|
||||
}
|
||||
|
||||
const ERC_TYPES = {
|
||||
ERC20: 'ERC20',
|
||||
ERC677: 'ERC677'
|
||||
}
|
||||
|
||||
const FEE_MANAGER_MODE = {
|
||||
ONE_DIRECTION: 'ONE_DIRECTION',
|
||||
BOTH_DIRECTIONS: 'BOTH_DIRECTIONS',
|
||||
ONE_DIRECTION_STAKE: 'ONE_DIRECTION_STAKE',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
module.exports = {
|
||||
BRIDGE_MODES,
|
||||
ERC_TYPES,
|
||||
FEE_MANAGER_MODE,
|
||||
ZERO_ADDRESS
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
const { soliditySha3 } = require('web3-utils')
|
||||
|
||||
function strip0x(input) {
|
||||
return input.replace(/^0x/, '')
|
||||
}
|
||||
|
||||
function addTxHashToData({ encodedData, transactionHash }) {
|
||||
return encodedData.slice(0, 2) + strip0x(transactionHash) + encodedData.slice(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the datatype byte from the AMB message.
|
||||
* First (the most significant bit) denotes if the message should be forwarded to the manual lane.
|
||||
@ -31,45 +33,8 @@ function parseAMBMessage(message) {
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeAMBMessageEvent = e => {
|
||||
let msgData = e.returnValues.encodedData
|
||||
if (!e.returnValues.messageId) {
|
||||
// append tx hash to an old message, where message id was not used
|
||||
// for old messages, e.messageId is a corresponding transactionHash
|
||||
msgData = e.transactionHash + msgData.slice(2)
|
||||
}
|
||||
return parseAMBMessage(msgData)
|
||||
}
|
||||
|
||||
const ambInformationSignatures = [
|
||||
'eth_call(address,bytes)',
|
||||
'eth_call(address,bytes,uint256)',
|
||||
'eth_call(address,address,uint256,bytes)',
|
||||
'eth_blockNumber()',
|
||||
'eth_getBlockByNumber()',
|
||||
'eth_getBlockByNumber(uint256)',
|
||||
'eth_getBlockByHash(bytes32)',
|
||||
'eth_getBalance(address)',
|
||||
'eth_getBalance(address,uint256)',
|
||||
'eth_getTransactionCount(address)',
|
||||
'eth_getTransactionCount(address,uint256)',
|
||||
'eth_getTransactionByHash(bytes32)',
|
||||
'eth_getTransactionReceipt(bytes32)',
|
||||
'eth_getStorageAt(address,bytes32)',
|
||||
'eth_getStorageAt(address,bytes32,uint256)'
|
||||
]
|
||||
const ambInformationSelectors = Object.fromEntries(ambInformationSignatures.map(sig => [soliditySha3(sig), sig]))
|
||||
const normalizeAMBInfoRequest = e => ({
|
||||
messageId: e.returnValues.messageId,
|
||||
sender: e.returnValues.sender,
|
||||
requestSelector: ambInformationSelectors[e.returnValues.requestSelector] || 'unknown',
|
||||
data: e.returnValues.data
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
strip0x,
|
||||
addTxHashToData,
|
||||
parseAMBMessage,
|
||||
normalizeAMBMessageEvent,
|
||||
ambInformationSignatures,
|
||||
normalizeAMBInfoRequest
|
||||
strip0x
|
||||
}
|
||||
|
@ -8,10 +8,8 @@
|
||||
"test": "NODE_ENV=test mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mycrypto/gas-estimation": "^1.1.0",
|
||||
"gas-price-oracle": "^0.1.5",
|
||||
"web3-utils": "^1.3.0",
|
||||
"node-fetch": "^2.1.2"
|
||||
"web3-utils": "1.0.0-beta.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bn-chai": "^1.0.1",
|
||||
|
@ -1,8 +1,12 @@
|
||||
const { expect } = require('chai')
|
||||
const { BRIDGE_MODES } = require('../constants')
|
||||
const { BRIDGE_MODES, ERC_TYPES } = require('../constants')
|
||||
|
||||
describe('constants', () => {
|
||||
it('should contain correct number of bridge types', () => {
|
||||
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(3)
|
||||
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(7)
|
||||
})
|
||||
|
||||
it('should contain correct number of erc types', () => {
|
||||
expect(Object.keys(ERC_TYPES).length).to.be.equal(2)
|
||||
})
|
||||
})
|
||||
|
159
commons/test/getTokenType.js
Normal file
159
commons/test/getTokenType.js
Normal file
@ -0,0 +1,159 @@
|
||||
const { expect } = require('chai')
|
||||
const { getTokenType, ERC_TYPES } = require('..')
|
||||
|
||||
describe('getTokenType', () => {
|
||||
it('should return ERC677 if bridgeContract is equal to bridgeAddress', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.resolve(bridgeAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC677)
|
||||
})
|
||||
|
||||
it('should return ERC20 if bridgeContract is not equal to bridgeAddress', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.resolve('0xBFCb120F7B1de491262CA4D9D8Eba70438b6896E')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC20)
|
||||
})
|
||||
|
||||
it('should return ERC20 if bridgeContract is not present', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC20)
|
||||
})
|
||||
|
||||
it('should return ERC20 if bridgeContract and isBridge are not present', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.reject()
|
||||
}
|
||||
},
|
||||
isBridge: () => {
|
||||
return {
|
||||
call: () => Promise.reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC20)
|
||||
})
|
||||
|
||||
it('should return ERC677 if isBridge returns true', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.reject()
|
||||
}
|
||||
},
|
||||
isBridge: () => {
|
||||
return {
|
||||
call: () => Promise.resolve(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC677)
|
||||
})
|
||||
|
||||
it('should return ERC677 if isBridge returns true and bridgeContract not present', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
isBridge: () => {
|
||||
return {
|
||||
call: () => Promise.resolve(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC677)
|
||||
})
|
||||
|
||||
it('should return ERC20 if isBridge returns false', async () => {
|
||||
// Given
|
||||
const bridgeAddress = '0xCecBE80Ed3548dE11D7d2D922a36576eA40C4c26'
|
||||
const contract = {
|
||||
methods: {
|
||||
bridgeContract: () => {
|
||||
return {
|
||||
call: () => Promise.reject()
|
||||
}
|
||||
},
|
||||
isBridge: () => {
|
||||
return {
|
||||
call: () => Promise.resolve(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When
|
||||
const type = await getTokenType(contract, bridgeAddress)
|
||||
|
||||
// Then
|
||||
expect(type).to.equal(ERC_TYPES.ERC20)
|
||||
})
|
||||
})
|
@ -1,6 +1,6 @@
|
||||
const { BN } = require('web3-utils')
|
||||
const { expect } = require('chai').use(require('bn-chai')(BN))
|
||||
const { parseAMBMessage, strip0x } = require('../message')
|
||||
const { parseAMBMessage, strip0x, addTxHashToData } = require('../message')
|
||||
|
||||
describe('strip0x', () => {
|
||||
it('should remove 0x from input', () => {
|
||||
@ -24,6 +24,28 @@ describe('strip0x', () => {
|
||||
expect(result).to.be.equal(input)
|
||||
})
|
||||
})
|
||||
describe('addTxHashToData', () => {
|
||||
it('should add txHash to encoded data at position 2', () => {
|
||||
// Given
|
||||
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
|
||||
const msgExecutor = '0xf4bef13f9f4f2b203faf0c3cbbaabe1afe056955'
|
||||
const msgGasLimit = '000000000000000000000000000000000000000000000000000000005b877705'
|
||||
const msgDataType = '00'
|
||||
const msgData = '0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03'
|
||||
const encodedData = `0x${strip0x(msgSender)}${strip0x(msgExecutor)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
|
||||
|
||||
const transactionHash = '0xbdceda9d8c94838aca10c687da1411a07b1390e88239c0638cb9cc264219cc10'
|
||||
const message = `0x${strip0x(transactionHash)}${strip0x(msgSender)}${strip0x(
|
||||
msgExecutor
|
||||
)}${msgGasLimit}${msgDataType}${strip0x(msgData)}`
|
||||
|
||||
// When
|
||||
const result = addTxHashToData({ encodedData, transactionHash })
|
||||
|
||||
// Then
|
||||
expect(result).to.be.equal(message)
|
||||
})
|
||||
})
|
||||
describe('parseAMBMessage', () => {
|
||||
it('should parse data type 00', () => {
|
||||
const msgSender = '0x003667154bb32e42bb9e1e6532f19d187fa0082e'
|
||||
|
161
commons/utils.js
161
commons/utils.js
@ -1,18 +1,22 @@
|
||||
const { toWei, toBN, BN } = require('web3-utils')
|
||||
const { toWei, toBN } = require('web3-utils')
|
||||
const { GasPriceOracle } = require('gas-price-oracle')
|
||||
const { estimateFees } = require('@mycrypto/gas-estimation')
|
||||
const fetch = require('node-fetch')
|
||||
const { BRIDGE_MODES } = require('./constants')
|
||||
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants')
|
||||
const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
|
||||
|
||||
const gasPriceOracle = new GasPriceOracle()
|
||||
|
||||
function decodeBridgeMode(bridgeModeHash) {
|
||||
switch (bridgeModeHash) {
|
||||
case '0x92a8d7fe':
|
||||
return BRIDGE_MODES.NATIVE_TO_ERC
|
||||
case '0xba4690f5':
|
||||
return BRIDGE_MODES.ERC_TO_ERC
|
||||
case '0x18762d46':
|
||||
return BRIDGE_MODES.ERC_TO_NATIVE
|
||||
case '0x2544fbb9':
|
||||
return BRIDGE_MODES.ARBITRARY_MESSAGE
|
||||
case '0x16ea01e9':
|
||||
return BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
|
||||
case '0x76595b56':
|
||||
return BRIDGE_MODES.AMB_ERC_TO_ERC
|
||||
default:
|
||||
@ -20,9 +24,80 @@ function decodeBridgeMode(bridgeModeHash) {
|
||||
}
|
||||
}
|
||||
|
||||
const decodeFeeManagerMode = managerModeHash => {
|
||||
switch (managerModeHash) {
|
||||
case '0xf2aed8f7':
|
||||
return FEE_MANAGER_MODE.ONE_DIRECTION
|
||||
case '0xd7de965f':
|
||||
return FEE_MANAGER_MODE.BOTH_DIRECTIONS
|
||||
default:
|
||||
throw new Error(`Unrecognized fee manager mode hash: '${managerModeHash}'`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getBridgeMode(contract) {
|
||||
const bridgeModeHash = await contract.methods.getBridgeMode().call()
|
||||
return decodeBridgeMode(bridgeModeHash)
|
||||
try {
|
||||
const bridgeModeHash = await contract.methods.getBridgeMode().call()
|
||||
return decodeBridgeMode(bridgeModeHash)
|
||||
} catch (e) {
|
||||
return BRIDGE_MODES.NATIVE_TO_ERC_V1
|
||||
}
|
||||
}
|
||||
|
||||
const getTokenType = async (bridgeTokenContract, bridgeAddress) => {
|
||||
try {
|
||||
const resultBridgeAddress = await bridgeTokenContract.methods.bridgeContract().call()
|
||||
if (resultBridgeAddress === bridgeAddress) {
|
||||
return ERC_TYPES.ERC677
|
||||
} else {
|
||||
return ERC_TYPES.ERC20
|
||||
}
|
||||
} catch (e) {
|
||||
try {
|
||||
const isBridge = await bridgeTokenContract.methods.isBridge(bridgeAddress).call()
|
||||
if (isBridge) {
|
||||
return ERC_TYPES.ERC677
|
||||
} else {
|
||||
return ERC_TYPES.ERC20
|
||||
}
|
||||
} catch (e) {
|
||||
return ERC_TYPES.ERC20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isErcToErcMode = bridgeMode => {
|
||||
return (
|
||||
bridgeMode === BRIDGE_MODES.ERC_TO_ERC ||
|
||||
bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC ||
|
||||
bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
|
||||
)
|
||||
}
|
||||
|
||||
const isMediatorMode = bridgeMode => {
|
||||
return bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC || bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
|
||||
}
|
||||
|
||||
const getUnit = bridgeMode => {
|
||||
let unitHome = null
|
||||
let unitForeign = null
|
||||
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) {
|
||||
unitHome = 'Native coins'
|
||||
unitForeign = 'Tokens'
|
||||
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) {
|
||||
unitHome = 'Tokens'
|
||||
unitForeign = 'Tokens'
|
||||
} else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
|
||||
unitHome = 'Native coins'
|
||||
unitForeign = 'Tokens'
|
||||
} else if (bridgeMode === BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC) {
|
||||
unitHome = 'Tokens'
|
||||
unitForeign = 'Tokens'
|
||||
} else {
|
||||
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
|
||||
}
|
||||
|
||||
return { unitHome, unitForeign }
|
||||
}
|
||||
|
||||
const parseValidatorEvent = event => {
|
||||
@ -75,8 +150,11 @@ const tryCall = async (method, fallbackValue) => {
|
||||
|
||||
const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0)
|
||||
|
||||
const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, options }) => {
|
||||
let events = []
|
||||
const getPastEvents = async (
|
||||
contract,
|
||||
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
|
||||
) => {
|
||||
let events
|
||||
try {
|
||||
events = await contract.getPastEvents(event, {
|
||||
...options,
|
||||
@ -84,19 +162,19 @@ const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, optio
|
||||
toBlock
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.message.includes('query returned more than') || e.message.toLowerCase().includes('timeout')) {
|
||||
if (e.message.includes('query returned more than') && toBlock !== 'latest') {
|
||||
const middle = toBN(fromBlock)
|
||||
.add(toBN(toBlock))
|
||||
.divRound(toBN(2))
|
||||
const middlePlusOne = middle.add(toBN(1))
|
||||
|
||||
const firstHalfEvents = await getPastEventsOrSplit(contract, {
|
||||
const firstHalfEvents = await getPastEvents(contract, {
|
||||
options,
|
||||
event,
|
||||
fromBlock,
|
||||
toBlock: middle
|
||||
})
|
||||
const secondHalfEvents = await getPastEventsOrSplit(contract, {
|
||||
const secondHalfEvents = await getPastEvents(contract, {
|
||||
options,
|
||||
event,
|
||||
fromBlock: middlePlusOne,
|
||||
@ -110,31 +188,6 @@ const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, optio
|
||||
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')
|
||||
|
||||
@ -177,27 +230,20 @@ const gasPriceWithinLimits = (gasPrice, limits) => {
|
||||
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
|
||||
let gasPrice = oracleGasPrice * factor
|
||||
gasPrice = gasPriceWithinLimits(gasPrice, limits)
|
||||
return toWei(gasPrice.toFixed(2).toString(), 'gwei')
|
||||
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei'))
|
||||
}
|
||||
|
||||
const gasPriceFromSupplier = async (web3, url, options = {}) => {
|
||||
// fetchFn has to be supplied (instead of just url to oracle),
|
||||
// because this utility function is shared between Browser and Node,
|
||||
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
|
||||
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
|
||||
try {
|
||||
let json
|
||||
if (url === 'eip1559-gas-estimation') {
|
||||
const { maxFeePerGas, maxPriorityFeePerGas } = await estimateFees(web3)
|
||||
const res = { maxFeePerGas: maxFeePerGas.toString(10), maxPriorityFeePerGas: maxPriorityFeePerGas.toString(10) }
|
||||
options.logger &&
|
||||
options.logger.debug &&
|
||||
options.logger.debug(res, 'Gas price updated using eip1559-gas-estimation')
|
||||
return res
|
||||
}
|
||||
if (url === 'gas-price-oracle') {
|
||||
json = await gasPriceOracle.fetchGasPricesOffChain()
|
||||
} else if (url) {
|
||||
const response = await fetch(url, { timeout: 2000 })
|
||||
if (fetchFn) {
|
||||
const response = await fetchFn()
|
||||
json = await response.json()
|
||||
} else {
|
||||
return null
|
||||
json = await gasPriceOracle.fetchGasPricesOffChain()
|
||||
}
|
||||
const oracleGasPrice = json[options.speedType]
|
||||
|
||||
@ -214,7 +260,7 @@ const gasPriceFromSupplier = async (web3, url, options = {}) => {
|
||||
options.logger.debug &&
|
||||
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
|
||||
|
||||
return { gasPrice: normalizedGasPrice }
|
||||
return normalizedGasPrice
|
||||
} catch (e) {
|
||||
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
|
||||
}
|
||||
@ -223,11 +269,11 @@ const gasPriceFromSupplier = async (web3, url, options = {}) => {
|
||||
|
||||
const gasPriceFromContract = async (bridgeContract, options = {}) => {
|
||||
try {
|
||||
const gasPrice = (await bridgeContract.methods.gasPrice().call()).toString()
|
||||
const gasPrice = await bridgeContract.methods.gasPrice().call()
|
||||
options.logger &&
|
||||
options.logger.debug &&
|
||||
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
|
||||
return { gasPrice }
|
||||
return gasPrice
|
||||
} catch (e) {
|
||||
options.logger &&
|
||||
options.logger.error &&
|
||||
@ -238,7 +284,10 @@ const gasPriceFromContract = async (bridgeContract, options = {}) => {
|
||||
|
||||
module.exports = {
|
||||
decodeBridgeMode,
|
||||
decodeFeeManagerMode,
|
||||
getBridgeMode,
|
||||
getTokenType,
|
||||
getUnit,
|
||||
parseValidatorEvent,
|
||||
processValidatorsEvents,
|
||||
getValidatorList,
|
||||
@ -247,5 +296,7 @@ module.exports = {
|
||||
normalizeGasPrice,
|
||||
gasPriceFromSupplier,
|
||||
gasPriceFromContract,
|
||||
gasPriceWithinLimits
|
||||
gasPriceWithinLimits,
|
||||
isErcToErcMode,
|
||||
isMediatorMode
|
||||
}
|
||||
|
313
commons/v1Abis.js
Normal file
313
commons/v1Abis.js
Normal file
@ -0,0 +1,313 @@
|
||||
const homeV1Abi = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorContract',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'recipient',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'value',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'Deposit',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'recipient',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'value',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'transactionHash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'Withdraw',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'deployedAtBlock',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'foreignDailyLimit',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'foreignMaxPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getCurrentDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'totalExecutedPerDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
const foreignViAbi = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorContract',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'recipient',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'value',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'transactionHash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'Deposit',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'recipient',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'value',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'homeGasPrice',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'Withdraw',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'deployedAtBlock',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'erc677token',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'homeDailyLimit',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'homeMaxPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getCurrentDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'totalExecutedPerDay',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
HOME_V1_ABI: homeV1Abi,
|
||||
FOREIGN_V1_ABI: foreignViAbi
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 908a48107919d4ab127f9af07d44d47eac91547e
|
||||
Subproject commit 835742dfd8f1c869d4e7b61582155d250d6cf094
|
@ -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, Oracle and Contracts deployed by your local playbook.
|
||||
In this case `master` branch will be used as a codebase for Monitor, UI, Oracle and Contracts deployed by your local playbook.
|
||||
|
||||
## Run the tests
|
||||
|
||||
@ -29,6 +29,7 @@ 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
|
||||
|
||||
|
@ -16,6 +16,7 @@ platforms:
|
||||
children:
|
||||
- oracle
|
||||
- monitor
|
||||
- ui
|
||||
image: ubuntu:16.04
|
||||
privileged: true
|
||||
network_mode: host
|
||||
@ -34,7 +35,7 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
multiple-host:
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
MONITOR_PORT: 3003
|
||||
syslog_server_port: "udp://127.0.0.1:514"
|
||||
verifier:
|
||||
|
@ -8,6 +8,7 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
|
||||
|
||||
@pytest.mark.parametrize("service", [
|
||||
("poabridge"),
|
||||
("tokenbridge-ui"),
|
||||
("tokenbridge-monitor")
|
||||
])
|
||||
def test_services(host, service):
|
||||
@ -23,7 +24,7 @@ def test_services(host, service):
|
||||
("oracle_bridge_affirmation_1"),
|
||||
("oracle_bridge_senderhome_1"),
|
||||
("oracle_bridge_senderforeign_1"),
|
||||
("oracle_bridge_shutdown_1"),
|
||||
("ui_ui_1"),
|
||||
("monitor_monitor_1")
|
||||
])
|
||||
def test_docker_containers(host, name):
|
||||
|
@ -33,7 +33,7 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-host:
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
syslog_server_port: "udp://127.0.0.1:514"
|
||||
verifier:
|
||||
name: testinfra
|
||||
|
@ -14,7 +14,6 @@ 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)
|
||||
|
@ -15,6 +15,7 @@ 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):
|
||||
@ -27,7 +28,8 @@ 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/monitor/package.json"),
|
||||
("/home/poadocker/bridge/ui/package.json")
|
||||
])
|
||||
def test_existing_package_json(host, path):
|
||||
assert host.file(path).exists
|
||||
@ -39,7 +41,8 @@ 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/monitor/Dockerfile"),
|
||||
("/home/poadocker/bridge/ui/Dockerfile")
|
||||
])
|
||||
def test_existing_docker_files(host, path):
|
||||
assert host.file(path).exists
|
||||
|
14
deployment-e2e/molecule/ui/Dockerfile.j2
Normal file
14
deployment-e2e/molecule/ui/Dockerfile.j2
Normal file
@ -0,0 +1,14 @@
|
||||
# Molecule managed
|
||||
|
||||
{% if item.registry is defined %}
|
||||
FROM {{ item.registry.url }}/{{ item.image }}
|
||||
{% else %}
|
||||
FROM {{ item.image }}
|
||||
{% endif %}
|
||||
|
||||
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
|
||||
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
|
||||
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
|
||||
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
|
||||
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
|
||||
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
|
55
deployment-e2e/molecule/ui/molecule.yml
Normal file
55
deployment-e2e/molecule/ui/molecule.yml
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
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
|
48
deployment-e2e/molecule/ui/tests/test_ui.py
Normal file
48
deployment-e2e/molecule/ui/tests/test_ui.py
Normal file
@ -0,0 +1,48 @@
|
||||
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"'
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
# Molecule managed
|
||||
|
||||
{% if item.registry is defined %}
|
||||
FROM {{ item.registry.url }}/{{ item.image }}
|
||||
{% else %}
|
||||
FROM {{ item.image }}
|
||||
{% endif %}
|
||||
|
||||
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
|
||||
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
|
||||
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
|
||||
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
|
||||
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
|
||||
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
|
@ -0,0 +1,54 @@
|
||||
---
|
||||
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:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
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
|
@ -21,6 +21,8 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-amb-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
verifier:
|
||||
|
@ -1,3 +1,5 @@
|
||||
---
|
||||
- import_playbook: ./oracle-docker-compose.yml
|
||||
- import_playbook: ../../../deployment/site.yml
|
||||
# The docker-compose files have to be modified, in order to join the docker containers over network with the parity containers
|
||||
- import_playbook: ./oracle-docker-compose.yml
|
||||
- import_playbook: ./ui-docker-compose.yml
|
||||
|
@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/oracle/{{ file }}.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all Oracle containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': docker_compose_parsed.services[item].networks | union(['ultimate'])}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Expose Redis port to allow connecting from redis-cli
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {'redis': {'ports': ['6379:6379']}}}, recursive=True) }}"
|
||||
|
||||
- name: Write updated docker file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/oracle/{{ file }}.yml"
|
@ -1,25 +1,33 @@
|
||||
---
|
||||
- name: Prepare Oracle for ultimate tests
|
||||
- name: Overwrite Oracle the docker-compose
|
||||
hosts: oracle
|
||||
become: true
|
||||
tasks:
|
||||
- name: Connect parity to oracle networks
|
||||
shell: "docker network create {{ item }} && docker network connect {{ item }} parity1 && docker network connect {{ item }} parity2"
|
||||
with_items:
|
||||
- oracle_net_db_bridge_request
|
||||
- oracle_net_db_bridge_collected
|
||||
- oracle_net_db_bridge_affirmation
|
||||
- oracle_net_db_bridge_information
|
||||
- oracle_net_db_bridge_transfer
|
||||
- oracle_net_db_bridge_senderhome
|
||||
- oracle_net_db_bridge_senderforeign
|
||||
- oracle_net_db_bridge_shutdown
|
||||
- oracle_net_rabbit_bridge_request
|
||||
- oracle_net_rabbit_bridge_collected
|
||||
- oracle_net_rabbit_bridge_affirmation
|
||||
- oracle_net_rabbit_bridge_information
|
||||
- oracle_net_rabbit_bridge_transfer
|
||||
- oracle_net_rabbit_bridge_senderhome
|
||||
- oracle_net_rabbit_bridge_senderforeign
|
||||
- name: stop the service
|
||||
shell: service poabridge stop
|
||||
|
||||
- name: ReTag current oracle image
|
||||
shell: docker tag $(docker images --format '{{ '{{' }}.Repository{{ '}}' }}:{{ '{{' }}.Tag{{ '}}' }}' | grep -m 1 tokenbridge-e2e-oracle) oracle:ultimate-testing
|
||||
delegate_to: 127.0.0.1
|
||||
become: false
|
||||
|
||||
- name: Replace oracle image
|
||||
replace:
|
||||
path: "/home/poadocker/bridge/oracle/{{ item }}.yml"
|
||||
regexp: 'poanetwork/tokenbridge-oracle:latest'
|
||||
replace: "oracle:ultimate-testing"
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
|
||||
- include_tasks: oracle-add-docker-external-network.yml
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-erc-native
|
||||
loop_control:
|
||||
loop_var: file
|
||||
|
||||
- name: start the service
|
||||
shell: service poabridge start
|
||||
|
@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Overwrite UI the docker-compose
|
||||
hosts: ui
|
||||
become: true
|
||||
tasks:
|
||||
- name: stop the service
|
||||
shell: service tokenbridge-ui stop
|
||||
|
||||
- name: Slurp docker compose file
|
||||
slurp:
|
||||
src: "/home/poadocker/bridge/ui/docker-compose.yml"
|
||||
register: docker_compose_slurp
|
||||
- name: Parse docker compose file
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_slurp['content'] | b64decode | from_yaml }}"
|
||||
|
||||
- name: Add the external network used to connect to Parity nodes
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed |combine({'networks': {'ultimate': {'external': 'true'}}}, recursive=True) }}"
|
||||
|
||||
- name: Add all UI containers to the network
|
||||
set_fact:
|
||||
docker_compose_parsed: "{{ docker_compose_parsed | combine({'services': {item: {'networks': ['ultimate']}}}, recursive=True) }}"
|
||||
with_items: "{{ docker_compose_parsed.services }}"
|
||||
|
||||
- name: Write new docker-compose file
|
||||
copy:
|
||||
content: "{{ docker_compose_parsed | to_yaml }}"
|
||||
dest: "/home/poadocker/bridge/ui/docker-compose.yml"
|
||||
|
||||
- name: start the service
|
||||
shell: service tokenbridge-ui start
|
14
deployment-e2e/molecule/ultimate-erc-to-erc/Dockerfile.j2
Normal file
14
deployment-e2e/molecule/ultimate-erc-to-erc/Dockerfile.j2
Normal file
@ -0,0 +1,14 @@
|
||||
# Molecule managed
|
||||
|
||||
{% if item.registry is defined %}
|
||||
FROM {{ item.registry.url }}/{{ item.image }}
|
||||
{% else %}
|
||||
FROM {{ item.image }}
|
||||
{% endif %}
|
||||
|
||||
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
|
||||
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
|
||||
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
|
||||
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
|
||||
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
|
||||
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
|
54
deployment-e2e/molecule/ultimate-erc-to-erc/molecule.yml
Normal file
54
deployment-e2e/molecule/ultimate-erc-to-erc/molecule.yml
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: oracle-erc-to-erc-host
|
||||
groups:
|
||||
- ultimate
|
||||
- erc-to-erc
|
||||
children:
|
||||
- oracle
|
||||
image: ubuntu:16.04
|
||||
privileged: true
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- name: ui-erc-to-erc-host
|
||||
groups:
|
||||
- ultimate
|
||||
- erc-to-erc
|
||||
children:
|
||||
- ui
|
||||
image: ubuntu:16.04
|
||||
privileged: true
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
prepare: ../prepare.yml
|
||||
converge: ../ultimate-commons/converge.yml
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-erc-to-erc-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
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:
|
||||
name: flake8
|
||||
scenario:
|
||||
name: ultimate-erc-to-erc
|
||||
test_sequence:
|
||||
- cleanup
|
||||
- destroy
|
||||
- syntax
|
||||
- create
|
||||
- prepare
|
||||
- converge
|
@ -13,6 +13,17 @@ 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:
|
||||
@ -21,10 +32,15 @@ provisioner:
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-erc-to-native-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
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:
|
||||
|
14
deployment-e2e/molecule/ultimate-native-to-erc/Dockerfile.j2
Normal file
14
deployment-e2e/molecule/ultimate-native-to-erc/Dockerfile.j2
Normal file
@ -0,0 +1,14 @@
|
||||
# Molecule managed
|
||||
|
||||
{% if item.registry is defined %}
|
||||
FROM {{ item.registry.url }}/{{ item.image }}
|
||||
{% else %}
|
||||
FROM {{ item.image }}
|
||||
{% endif %}
|
||||
|
||||
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
|
||||
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python*-dnf bash && dnf clean all; \
|
||||
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
|
||||
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
|
||||
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
|
||||
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
|
54
deployment-e2e/molecule/ultimate-native-to-erc/molecule.yml
Normal file
54
deployment-e2e/molecule/ultimate-native-to-erc/molecule.yml
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: oracle-native-to-erc-host
|
||||
groups:
|
||||
- ultimate
|
||||
- native-to-erc
|
||||
children:
|
||||
- oracle
|
||||
image: ubuntu:16.04
|
||||
privileged: true
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- name: ui-native-to-erc-host
|
||||
groups:
|
||||
- ultimate
|
||||
- native-to-erc
|
||||
children:
|
||||
- ui
|
||||
image: ubuntu:16.04
|
||||
privileged: true
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
prepare: ../prepare.yml
|
||||
converge: ../ultimate-commons/converge.yml
|
||||
inventory:
|
||||
host_vars:
|
||||
oracle-native-to-erc-host:
|
||||
COMMON_HOME_RPC_URL: "http://parity1:8545"
|
||||
COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
|
||||
ORACLE_VALIDATOR_ADDRESS: "0xaaB52d66283F7A1D5978bcFcB55721ACB467384b"
|
||||
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
|
||||
ui-native-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-native-to-erc
|
||||
test_sequence:
|
||||
- cleanup
|
||||
- destroy
|
||||
- syntax
|
||||
- create
|
||||
- prepare
|
||||
- converge
|
@ -34,6 +34,14 @@ 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>:
|
||||
@ -42,7 +50,18 @@ 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>`, and both Oracle and Monitor on `<host_ip_B>`.
|
||||
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>
|
||||
```
|
||||
|
||||
| Value | Description |
|
||||
|
@ -44,6 +44,7 @@ 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.
|
||||
|
4
deployment/group_vars/amb-stake-erc-to-erc.yml
Normal file
4
deployment/group_vars/amb-stake-erc-to-erc.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0xc26Aa60Ff574f157616D3aEE70e08aAC129E1dFC"
|
||||
UI_PORT: 3003
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x8397be90BCF57b0B71219f555Fe121b22e5a994C"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0"
|
||||
MONITOR_PORT: 3013
|
||||
|
@ -1,14 +1,19 @@
|
||||
---
|
||||
## 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
|
||||
|
||||
@ -25,6 +30,20 @@ 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
|
||||
|
6
deployment/group_vars/erc-to-erc.yml
Normal file
6
deployment/group_vars/erc-to-erc.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
ORACLE_BRIDGE_MODE: "ERC_TO_ERC"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
|
||||
UI_PORT: 3001
|
||||
MONITOR_PORT: 3011
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda"
|
||||
UI_PORT: 3002
|
||||
MONITOR_PORT: 3012
|
||||
|
@ -1,17 +1,22 @@
|
||||
---
|
||||
## General settings
|
||||
ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE"
|
||||
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"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x59ba90A588ce732AB33FD32Aab1b58c21400A0f6"
|
||||
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://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0xdA4a49a00F4fF4A5988b9AceE95f99e3b2c208b6"
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
|
||||
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
|
||||
|
||||
## Home Gasprice
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
|
||||
@ -27,12 +32,26 @@ 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
|
||||
MONITOR_CACHE_EVENTS: "false"
|
||||
MONITOR_HOME_START_BLOCK: 20821049
|
||||
MONITOR_FOREIGN_START_BLOCK: 24773297
|
||||
MONITOR_HOME_START_BLOCK: 0
|
||||
MONITOR_FOREIGN_START_BLOCK: 0
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
|
||||
MONITOR_TX_NUMBER_THRESHOLD: 100
|
||||
|
6
deployment/group_vars/native-to-erc.yml
Normal file
6
deployment/group_vars/native-to-erc.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC"
|
||||
COMMON_HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
|
||||
UI_PORT: 3000
|
||||
MONITOR_PORT: 3010
|
@ -1,14 +1,19 @@
|
||||
---
|
||||
## 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"
|
||||
COMMON_HOME_RPC_URL: "https://sokol.poa.network"
|
||||
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"
|
||||
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network"
|
||||
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
|
||||
UI_FOREIGN_WITHOUT_EVENTS: false
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
|
||||
|
||||
## Home Gasprice
|
||||
@ -25,6 +30,20 @@ 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"
|
||||
@ -33,7 +52,3 @@ MONITOR_FOREIGN_START_BLOCK: 0
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
|
||||
MONITOR_TX_NUMBER_THRESHOLD: 100
|
||||
|
||||
# disable building and pulling of docker images from the Docker Hub
|
||||
skip_pull: true
|
||||
skip_build: true
|
||||
|
56
deployment/group_vars/wetc.yml
Normal file
56
deployment/group_vars/wetc.yml
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
## 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
|
||||
|
||||
## Home Gasprice
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice-etc.poa.network/"
|
||||
COMMON_HOME_GAS_PRICE_SPEED_TYPE: "standard"
|
||||
COMMON_HOME_GAS_PRICE_FALLBACK: 15000000000 # in wei
|
||||
ORACLE_HOME_GAS_PRICE_UPDATE_INTERVAL: 600000
|
||||
COMMON_HOME_GAS_PRICE_FACTOR: 1
|
||||
|
||||
## Foreign Gasprice
|
||||
COMMON_FOREIGN_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
|
||||
COMMON_FOREIGN_GAS_PRICE_SPEED_TYPE: "standard"
|
||||
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
|
||||
MONITOR_CACHE_EVENTS: "true"
|
||||
MONITOR_HOME_START_BLOCK: 7703292
|
||||
MONITOR_FOREIGN_START_BLOCK: 7412459
|
||||
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
|
||||
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
|
||||
MONITOR_TX_NUMBER_THRESHOLD: 100
|
@ -7,6 +7,11 @@ 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:
|
||||
|
@ -33,7 +33,7 @@
|
||||
mode: "0755"
|
||||
|
||||
- name: Upgrade pip version
|
||||
shell: pip3 install --upgrade pip==19.3.1
|
||||
shell: pip3 install --upgrade pip
|
||||
|
||||
- name: Install python docker library
|
||||
shell: pip3 install docker docker-compose setuptools
|
||||
|
@ -1,6 +1,6 @@
|
||||
/var/log/docker/*/docker.log {
|
||||
rotate 5
|
||||
size 100M
|
||||
size 1G
|
||||
compress
|
||||
missingok
|
||||
delaycompress
|
||||
@ -8,7 +8,7 @@
|
||||
}
|
||||
/var/log/docker/*.log {
|
||||
rotate 5
|
||||
size 100M
|
||||
size 1G
|
||||
compress
|
||||
missingok
|
||||
delaycompress
|
||||
|
@ -3,4 +3,3 @@
|
||||
shell: docker-compose pull
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/monitor"
|
||||
when: skip_pull is undefined
|
||||
|
@ -3,4 +3,3 @@
|
||||
shell: docker-compose pull
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/oracle"
|
||||
when: skip_pull is undefined
|
||||
|
@ -3,11 +3,11 @@
|
||||
with_items:
|
||||
- docker-compose
|
||||
- docker-compose-transfer
|
||||
- docker-compose-amb
|
||||
- docker-compose-erc-native
|
||||
loop_control:
|
||||
loop_var: file
|
||||
|
||||
- name: Set the oracle's containers local logs configuration file
|
||||
- name: Set the local container logs configuration file
|
||||
template:
|
||||
src: 31-oracle-docker.conf.j2
|
||||
dest: /etc/rsyslog.d/31-oracle-docker.conf
|
||||
@ -15,22 +15,6 @@
|
||||
group: root
|
||||
mode: 0644
|
||||
|
||||
- name: Set the redis container local logs configuration file
|
||||
template:
|
||||
src: 32-redis-docker.conf.j2
|
||||
dest: /etc/rsyslog.d/32-redis-docker.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0644
|
||||
|
||||
- name: Set the rabbit MQ container local logs configuration file
|
||||
template:
|
||||
src: 33-rabbit-docker.conf.j2
|
||||
dest: /etc/rsyslog.d/33-rabbit-docker.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0644
|
||||
|
||||
- name: Set the log configuration file to send container logs to remote server
|
||||
template:
|
||||
src: 36-oracle-remote-logging.conf.j2
|
||||
|
@ -27,13 +27,24 @@
|
||||
set_fact:
|
||||
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
|
||||
|
||||
- name: Extend docker compose file for erc to native
|
||||
set_fact: composefileoverride="-f docker-compose-transfer.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"
|
||||
- name: Get foreign erc type
|
||||
become_user: "{{ compose_service_user }}"
|
||||
shell: docker-compose run --rm --entrypoint "node scripts/initialChecks.js" bridge_affirmation
|
||||
args:
|
||||
chdir: "{{ bridge_path }}/oracle"
|
||||
register: ERCTYPE
|
||||
|
||||
- name: Extend docker compose file for amb
|
||||
set_fact: composefileoverride="-f docker-compose-amb.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ARBITRARY_MESSAGE"
|
||||
- name: Set FOREIGN_ERC_TYPE variable
|
||||
set_fact:
|
||||
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
|
||||
|
||||
- name: Extend docker compose file
|
||||
set_fact: composefileoverride="-f docker-compose-transfer.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ERC_TO_ERC" and FOREIGN_ERC_TYPE != "ERC677"
|
||||
|
||||
- name: Extend docker compose file for erc to native
|
||||
set_fact: composefileoverride="-f docker-compose-erc-native.yml"
|
||||
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"
|
||||
|
||||
- name: Install .key config
|
||||
template:
|
||||
|
@ -20,4 +20,4 @@
|
||||
with_items:
|
||||
- docker-compose.yml
|
||||
- docker-compose-transfer.yml
|
||||
- docker-compose-amb.yml
|
||||
- docker-compose-erc-native.yml
|
||||
|
@ -11,16 +11,9 @@ ORACLE_HOME_RPC_POLLING_INTERVAL={{ ORACLE_HOME_RPC_POLLING_INTERVAL }}
|
||||
|
||||
## Foreign contract
|
||||
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }}
|
||||
{% if ORACLE_FOREIGN_ARCHIVE_RPC_URL | default('') != '' %}
|
||||
ORACLE_FOREIGN_ARCHIVE_RPC_URL={{ ORACLE_FOREIGN_ARCHIVE_RPC_URL }}
|
||||
{% endif %}
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
|
||||
ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }}
|
||||
|
||||
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
|
||||
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
|
||||
{% endif %}
|
||||
|
||||
## Gasprice
|
||||
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
|
||||
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
|
||||
@ -54,28 +47,8 @@ COMMON_FOREIGN_GAS_PRICE_FACTOR={{ COMMON_FOREIGN_GAS_PRICE_FACTOR }}
|
||||
ORACLE_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
|
||||
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
|
||||
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
|
||||
{% if ORACLE_FOREIGN_TX_RESEND_INTERVAL | default('') != '' %}
|
||||
ORACLE_FOREIGN_TX_RESEND_INTERVAL={{ ORACLE_FOREIGN_TX_RESEND_INTERVAL }}
|
||||
{% endif %}
|
||||
{% if ORACLE_HOME_TX_RESEND_INTERVAL | default('') != '' %}
|
||||
ORACLE_HOME_TX_RESEND_INTERVAL={{ ORACLE_HOME_TX_RESEND_INTERVAL }}
|
||||
{% endif %}
|
||||
|
||||
## Emergency shutdown configuration
|
||||
{% if ORACLE_SHUTDOWN_SERVICE_URL | default('') != '' %}
|
||||
ORACLE_SHUTDOWN_SERVICE_URL={{ ORACLE_SHUTDOWN_SERVICE_URL }}
|
||||
{% endif %}
|
||||
{% if ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL | default('') != '' %}
|
||||
ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL={{ ORACLE_SHUTDOWN_SERVICE_POLLING_INTERVAL }}
|
||||
{% endif %}
|
||||
{% if ORACLE_SIDE_RPC_URL | default('') != '' %}
|
||||
ORACLE_SIDE_RPC_URL={{ ORACLE_SIDE_RPC_URL }}
|
||||
{% endif %}
|
||||
{% if ORACLE_SHUTDOWN_CONTRACT_ADDRESS | default('') != '' %}
|
||||
ORACLE_SHUTDOWN_CONTRACT_ADDRESS={{ ORACLE_SHUTDOWN_CONTRACT_ADDRESS }}
|
||||
{% endif %}
|
||||
{% if ORACLE_SHUTDOWN_CONTRACT_METHOD | default('') != '' %}
|
||||
ORACLE_SHUTDOWN_CONTRACT_METHOD={{ ORACLE_SHUTDOWN_CONTRACT_METHOD }}
|
||||
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
|
||||
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
|
||||
{% endif %}
|
||||
|
||||
{% if ORACLE_HOME_START_BLOCK | default('') != '' %}
|
||||
|
@ -1,11 +0,0 @@
|
||||
$FileCreateMode 0644
|
||||
template(name="DockerLogFileName_Redis" type="list") {
|
||||
constant(value="/var/log/docker/")
|
||||
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*redis.*)\\/[a-zA-Z0-9]+\\[")
|
||||
constant(value="/docker.log")
|
||||
}
|
||||
|
||||
if $programname contains 'oracle' and $programname contains 'redis' then \
|
||||
?DockerLogFileName_Redis
|
||||
|
||||
$FileCreateMode 0600
|
@ -1,11 +0,0 @@
|
||||
$FileCreateMode 0644
|
||||
template(name="DockerLogFileName_Rabbit" type="list") {
|
||||
constant(value="/var/log/docker/")
|
||||
property(name="syslogtag" securepath="replace" regex.type="ERE" regex.submatch="1" regex.expression="oracle_(.*rabbit.*)\\/[a-zA-Z0-9]+\\[")
|
||||
constant(value="/docker.log")
|
||||
}
|
||||
|
||||
if $programname contains 'oracle' and $programname contains 'rabbit' then \
|
||||
?DockerLogFileName_Rabbit
|
||||
|
||||
$FileCreateMode 0600
|
3
deployment/roles/ui/meta/main.yml
Normal file
3
deployment/roles/ui/meta/main.yml
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
dependencies:
|
||||
- role: common
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user