Compare commits

..

72 Commits

Author SHA1 Message Date
ce515a8635
Improve contract fetching
Some checks failed
tokenbridge / initialize (push) Has been cancelled
tokenbridge / build-e2e-images (push) Has been cancelled
tokenbridge / build-molecule-runner (push) Has been cancelled
tokenbridge / validate (build) (push) Has been cancelled
tokenbridge / validate (lint) (push) Has been cancelled
tokenbridge / validate (test) (push) Has been cancelled
tokenbridge / e2e (alm-e2e, true) (push) Has been cancelled
tokenbridge / e2e (monitor-e2e) (push) Has been cancelled
tokenbridge / e2e (oracle-e2e) (push) Has been cancelled
tokenbridge / deployment (monitor) (push) Has been cancelled
tokenbridge / deployment (multiple) (push) Has been cancelled
tokenbridge / deployment (oracle) (push) Has been cancelled
tokenbridge / deployment (repo) (push) Has been cancelled
tokenbridge / ultimate (amb) (push) Has been cancelled
tokenbridge / ultimate (erc-to-native) (push) Has been cancelled
2024-05-08 23:45:31 +00:00
Alexander Kolotov
961b12b9f3
Naming convention for oracle (#661) 2022-10-28 14:07:02 +03:00
Alexander Kolotov
ff9f3fb7d6
Merge the develop branch to the master branch, preparation to v3.7.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Periodic check for RPC sync state (#656)
2022-05-25 14:09:36 +03:00
Kirill Fedoseev
297cb67895
Periodic check for RPC sync state (#656) 2022-05-25 13:54:47 +03:00
Alexander Kolotov
bcf16144c1
Merge the develop branch to the master branch, preparation to v3.6.0
This merge contains the following set of changes:
  * [Oracle, Fix] Add missing Ganache nonce error message (#651)
  * [ALM, Improvement] ALM: show manually added signatures (#653)
2022-04-29 19:13:41 +03:00
Kirill Fedoseev
16f3e9add6
ALM: show manually added signatures (#653) 2022-04-29 16:38:08 +03:00
Ho Xuan Cuong
4af882b0ff
Add missing Ganache nonce error message (#651) 2022-04-08 13:26:55 +04:00
Alexander Kolotov
dbaf7feca7
Merge the develop branch to the master branch, preparation to v3.5.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Helpers for overriding AMB signatures (#640)
  * [Oracle, Improvement] Support manual signatures in erc to native mode (#646)
  * [ALM, Improvement] ALM: Do not show unneeded failed confirmations (#641)
  * [ALM, Improvement] ALM: warning for auto-relayed messages (#644)
  * [ALM, Fix] ALM: reorder warnings
2022-03-18 17:07:42 +03:00
Kirill Fedoseev
735aa75f81 ALM: reorder warnings 2022-03-17 17:41:33 +04:00
Kirill Fedoseev
910c3759c1
ALM: warning for auto-relayed messages (#644) 2022-03-17 14:11:14 +03:00
Kirill Fedoseev
9c2d2f404c
ALM: Do not show unneeded failed confirmations (#641) 2022-03-13 13:54:33 +03:00
Kirill Fedoseev
2b51d4c209
Support manual signatures in erc to native mode (#646) 2022-03-10 14:18:19 +03:00
Kirill Fedoseev
981231fb47
Helpers for overriding AMB signatures (#640) 2022-02-18 17:35:12 +03:00
Alexander Kolotov
5bc562e810
Merge the develop branch to the master branch, preparation to v3.4.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Refetch old logs ranges to see if there are missed events (#627)
  * [Oracle, Improvement] Add support for EIP1559 gas price oracle (#631)
  * [Oracle, Improvement] CollectedSignatures AMB watcher for MEV bundling (#634)
  * [Oracle, Fix] Fix eip1559 transaction sending problems (#632)
2022-02-11 10:24:38 +03:00
Kirill Fedoseev
72f0d30b52
CollectedSignatures AMB watcher for MEV bundling (#634) 2022-02-07 23:32:41 +03:00
Kirill Fedoseev
8ec11d0476
Fix eip1559 transaction sending problems (#632) 2022-01-17 16:51:29 +03:00
Kirill Fedoseev
8d732adba1
Add support for EIP1559 gas price oracle (#631) 2022-01-03 17:58:36 +03:00
Kirill Fedoseev
296e5c5a22
Refetch old logs ranges to see if there are missed events (#627) 2022-01-03 15:32:38 +03:00
Alexander Kolotov
b17fff2b56
Merge the develop branch to the master branch, preparation to v3.3.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Add oracle helper script for fetching interest amounts via async calls (#615)
  * [Monitor, Fix] Include cDAI balance in balanceDiff (#613)
  * [Monitor, Fix] Added logging prior the investedAmount call (#614)
  * [ALM, Fix] Fetch AMB signatures a bit earlier (#620)
2021-11-05 20:22:46 +03:00
Kirill Fedoseev
4eba91ef7e
Fetch AMB signatures a bit earlier (#620) 2021-11-03 19:21:35 +03:00
Kirill Fedoseev
1e3aa53ab3
Add oracle helper script for fetching interest amounts via async calls (#615) 2021-10-21 13:33:12 +03:00
Alexander Kolotov
a05ff51555
Added logging prior the investedAmount call (#614) 2021-10-12 18:06:50 +03:00
Kirill Fedoseev
52e1c88b58
Include cDAI balance in balanceDiff (#613) 2021-10-11 12:14:55 +03:00
Alexander Kolotov
d36dcadd34
Merge the develop branch to the master branch, preparation to v3.2.0
This merge contains the following set of changes:
  * [Oracle, Fix] Use more accurate gas estimates for very high message gas limits (#611)
  * [Common, Other] Final version of AMB and OB contracts security audit report by ChainSecurity (#608)
2021-10-07 21:45:51 +03:00
Kirill Fedoseev
d543dbb339
Use more accurate gas estimates for very high message gas limits (#611) 2021-10-07 20:44:14 +03:00
Kirill Fedoseev
bd21cc163e
Merge pull request #608 from poanetwork/reports/chainsecurity-amb-6.0.0-ob-1.1.0-contracts
AMB and OB contracts security audit report by ChainSecurity
2021-09-28 21:12:37 +03:00
Alexander Kolotov
4a0fc936a1
Final version of AMB and OB contracts security audit report by ChainSecurity 2021-09-28 20:39:04 +03:00
Alexander Kolotov
78564afabd
Merge the develop branch to the master branch, preparation to v3.1.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Try to detect unsynced node state (#592)
  * [Oracle, Improvement] Allow to override JSON RPC error codes (#603)
  * [Oracle, Fix] Fix handling of Compound related Transfer events (#595)
  * [Oracle, Fix] Add new nonce-related error messages (#599)
  * [Deployment, Fix] .env template includes latest changes related to the oracle configuration (#601)
  * [Deployment, Fix] Improvements for the local logs configuration (#602)
  * [Common, Other] Update the contract's submodule to the release 6.0.0 (#600)
2021-09-20 23:56:37 +03:00
Kirill Fedoseev
70a2c30b4c Fix imported ABI 2021-09-20 13:31:37 +03:00
Kirill Fedoseev
06a9586148 Update yarn.lock 2021-09-20 12:50:07 +03:00
Kirill Fedoseev
7379fe4190
Allow to override JSON RPC error codes (#603) 2021-09-20 12:07:49 +03:00
Alexander Kolotov
e59766c5df
Update the contract's submodule to the release 6.0.0 (#600) 2021-09-18 13:44:48 +03:00
Alexander Kolotov
fdb18a1a17
Improvements for the local logs configuration (#602) 2021-09-18 13:42:29 +03:00
Alexander Kolotov
4412046f66
.env template includes latest changes related to the oracle configuration (#601) 2021-09-18 13:41:56 +03:00
Kirill Fedoseev
0ff224ccd3
Add new nonce-related error messages (#599) 2021-09-15 23:42:32 +03:00
Kirill Fedoseev
5cedacafe5
Fix handling of Compound related Transfer events (#595) 2021-08-24 11:31:15 +03:00
Kirill Fedoseev
2e6179f974
Try to detect unsynced node state (#592) 2021-08-03 23:36:50 +03:00
Alexander Kolotov
4f5e3c47be
Merge the develop branch to the master branch, preparation to v3.0.0
This merge contains the following set of changes:
  * [Oracle, Improvement] Async calls error codes (#587)
  * [Oracle, Improvement] Refactor/async call serializers (#588)
2021-07-16 17:58:21 +03:00
Kirill Fedoseev
4c06329153
Refactor/async call serializers (#588) 2021-07-13 12:25:05 +03:00
Kirill Fedoseev
d53452675e
Async calls error codes (#587) 2021-07-13 11:57:08 +03:00
Alexander Kolotov
c92f80c484
Merge the develop branch to the master branch, preparation to v3.0.0-rc1
This merge contains the following set of changes:
  * [Monitor, Improvement] Add statistics about used AMB information requests (#577)
  * [ALM, Improvement] Add safe-execute button in ALM (#580), closes #573, closes #551
  * [Oracle, Improvement] Improve confirm-relay feature (#582), closes #569
  * [Monitor, Fix] Prune print of long error messages about missing file (#579), closes #578
  * [Oracle, Fix] Use safe approach for eth_getLogs requests (#581)
  * [Oracle, Fix] Fix logging in gas price service (#583), closes #552
  * [Oracle, Fix] Fix oracle error patterns and oracle e2e tests (#585)
  * [Common, Other] Fix dependencies versions and update example.yml (#566)
  * [Common, Other] Upload services logs in e2e and ultimate tests (#568)
  * [Oracle, Other] added example of emergency shutdown controller contract (#572)
  * [Oracle, Other] Refactor oracle configuration (#584)
2021-07-10 10:10:22 +03:00
Kirill Fedoseev
5e95b5d8c5
Fix oracle error patterns and oracle e2e tests (#585) 2021-07-08 16:42:56 +03:00
Kirill Fedoseev
6e1f57493a
Refactor oracle configuration (#584) 2021-07-08 09:38:55 +03:00
Kirill Fedoseev
8ed6550635
Improve confirm-relay feature (#582) 2021-07-07 16:21:01 +03:00
Kirill Fedoseev
c8eb0f1ed8
Fix logging in gas price service (#583) 2021-07-05 13:50:18 +03:00
Kirill Fedoseev
3e5e50c06e
Use safe approach for eth_getLogs requests (#581) 2021-06-30 12:28:03 +03:00
Kirill Fedoseev
92e1b597c4
Add safe-execute button in ALM (#580) 2021-06-18 09:43:18 +03:00
Kirill Fedoseev
8b1a97e673
Prune print of long error messages about missing file (#579) 2021-06-12 00:24:20 +03:00
Kirill Fedoseev
3cf184c391
Add statistics about used AMB information requests (#577) 2021-05-26 08:58:20 -06:00
Alexander Kolotov
8f72516374
added example of emergency shutdown controller contract (#572) 2021-05-16 15:42:36 -06:00
Kirill Fedoseev
98155e3075
Upload services logs in e2e and ultimate tests (#568) 2021-05-15 10:29:20 -06:00
Kirill Fedoseev
0d724147bd
Fix dependencies versions and update example.yml (#566) 2021-05-10 14:36:57 -06:00
Alexander Kolotov
3b959776f3
Merge the develop branch to the master branch, preparation to v3.0.0-rc0
This merge contains the following set of changes:
  * [Oracle, Improvement] Oracle watcher for AMB async calls (#509), 
  * [Common, Other] Update the contract's submodule to the release 6.0.0-rc0 (#562)
2021-05-09 07:59:36 -06:00
Kirill Fedoseev
ffbca8b941
Oracle watcher for AMB async calls (#509) 2021-05-09 07:34:19 -06:00
Alexander Kolotov
38f1bae8f5
Update the contract's submodule to the release 6.0.0-rc0 (#562) 2021-05-08 09:50:46 -06:00
Alexander Kolotov
9da1d7ab0a
Merge the develop branch to the master branch, preparation to v2.7.0-rc2
This merge contains the following set of changes:
  * [Monitor, Improvement] Include stats about failed and pending messages in mediators monitor endpoint (#544), closes #522
  * [Oracle, Fix] Fix remote shutdown sender behaviour (#550)
  * [Oracle, Fix] Force update of gas price when getting underprice tx error (#554), closes #545
  * [Oracle, Fix] Add oracle configuration option for max block range length (#557)
  * [Monitor, Fix] Limit block range of logs request (#543), closes #537
  * [Monitor, Fix] Add missing cache file save (#549)
  * [UI, Other] Remove UI from monorepo (#553)
  * [Common, Other] Update the contract's submodule to the release 5.7.0-rc0 (#548)
  * [Common, Other] Final version of the FT OB contract security audit report by ChainSecurity (#559)
2021-05-04 07:42:06 -06:00
Alexander Kolotov
78bcd7568b
Final version of the FT OB contract security audit report by ChainSecurity (#559) 2021-05-04 07:25:02 -06:00
Kirill Fedoseev
429312500a
Add oracle configuration option for max block range length (#557) 2021-04-26 10:47:38 -06:00
Kirill Fedoseev
59e0bf7565
Force update of gas price when getting underprice tx error (#554) 2021-04-18 12:30:40 -06:00
Kirill Fedoseev
ab51370d5a
Remove UI from monorepo (#553) 2021-04-16 03:31:12 -06:00
Kirill Fedoseev
9dfb0510c4
Fix remote shutdown sender behaviour (#550) 2021-04-14 14:35:43 -06:00
Kirill Fedoseev
f65e8f9244
Add missing cache file save (#549) 2021-04-14 08:55:13 -06:00
Alexander Kolotov
5c8b595382
Update the contract's submodule to the release 5.7.0-rc0 (#548) 2021-04-13 14:50:52 -06:00
Kirill Fedoseev
71bf9d5583
Limit block range of logs request (#543) 2021-04-13 08:05:01 -06:00
Kirill Fedoseev
329ded4beb
Include stats about failed and pending messages in mediators monitor endpoint (#544) 2021-04-13 07:59:49 -06:00
Alexander Kolotov
dc70247e2c
Merge the develop branch to the master branch, preparation to v2.7.0-rc1 2021-04-13 04:09:38 -06:00
Kirill Fedoseev
f95beee5dc
Add oracle service for remote shutdown (#531) 2021-04-12 14:38:42 -06:00
Leonid Tyurin
ae83c76be9
Fix monitor metrics (#533) 2021-04-02 03:59:49 -06:00
Kirill Fedoseev
dc3026e584
Fix undefined is not an object exception (#535) 2021-03-26 12:16:23 -06:00
Kirill Fedoseev
b6ba0744b9
Fix ALM react subscriptions leading to duplicated RPC requests (#534) 2021-03-18 00:04:33 -06:00
Kirill Fedoseev
4dba9a50e8
Extract tx resend intervals in env variables (#532) 2021-03-17 21:48:40 -06:00
Kirill Fedoseev
818bc4675d
Fallback eth_getLogs to explorer API for BSC (#530) 2021-03-14 07:39:23 -06:00
497 changed files with 13807 additions and 20982 deletions

View File

@ -15,7 +15,7 @@ jobs:
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 10 node-version: 12
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
@ -48,7 +48,7 @@ jobs:
steps: steps:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 10 node-version: 12
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
@ -61,31 +61,6 @@ jobs:
key: ${{ needs.initialize.outputs.cache_key }} key: ${{ needs.initialize.outputs.cache_key }}
- name: yarn run ${{ matrix.task }} - name: yarn run ${{ matrix.task }}
run: ${{ steps.cache-repo.outputs.cache-hit }} && 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: build-e2e-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -95,21 +70,19 @@ jobs:
- name: Evaluate e2e docker images tags - name: Evaluate e2e docker images tags
run: | run: |
git submodule status > submodule.status git submodule status > submodule.status
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $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 "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 "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- name: Rebuild and push updated images - name: Rebuild and push updated images
run: | run: |
function check_if_image_exists() { function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
} }
updated=() updated=()
if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi if ! check_if_image_exists e2e ${E2E_TAG}; then updated+=("e2e"); fi
if ! check_if_image_exists oracle ${ORACLE_TAG}; then updated+=("oracle"); fi if ! check_if_image_exists oracle ${ORACLE_TAG}; then updated+=("oracle-amb"); fi
if ! check_if_image_exists monitor ${MONITOR_TAG}; then updated+=("monitor"); fi if ! check_if_image_exists monitor ${MONITOR_TAG}; then updated+=("monitor-amb"); 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 ! check_if_image_exists alm ${ALM_TAG}; then updated+=("alm"); fi
if [ ${#updated[@]} -gt 0 ]; then if [ ${#updated[@]} -gt 0 ]; then
echo "Updated services: ${updated[@]}" echo "Updated services: ${updated[@]}"
@ -131,7 +104,7 @@ jobs:
- name: Rebuild and push molecule runner e2e image - name: Rebuild and push molecule runner e2e image
run: | run: |
function check_if_image_exists() { function check_if_image_exists() {
curl -fsSlL -H 'Authorization: bearer ${{ github.token }}' "https://${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null curl -fsSlL "https://${{ github.actor }}:${{ github.token }}@${DOCKER_REGISTRY}/v2/${DOCKER_REPO}/tokenbridge-e2e-$1/manifests/$2" > /dev/null
} }
if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then if check_if_image_exists molecule_runner ${MOLECULE_RUNNER_TAG}; then
echo "Image already exists" echo "Image already exists"
@ -149,12 +122,10 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
task: [oracle-e2e, monitor-e2e, alm-e2e, 'ui-e2e:ci'] task: [oracle-e2e, monitor-e2e, alm-e2e]
include: include:
- task: alm-e2e - task: alm-e2e
use-cache: true use-cache: true
- task: 'ui-e2e:ci'
use-cache: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
@ -162,10 +133,9 @@ jobs:
- name: Evaluate e2e docker images tags - name: Evaluate e2e docker images tags
run: | run: |
git submodule status > submodule.status git submodule status > submodule.status
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $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 "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 "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
- if: ${{ matrix.use-cache }} - if: ${{ matrix.use-cache }}
uses: actions/cache@v2 uses: actions/cache@v2
@ -179,6 +149,12 @@ jobs:
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }} run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: yarn run ${{ matrix.task }} - name: yarn run ${{ matrix.task }}
run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }} run: ${{ !matrix.use-cache || steps.cache-repo.outputs.cache-hit }} && yarn run ${{ matrix.task }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@v2
with:
name: logs-${{ matrix.task }}
path: e2e-commons/logs
deployment: deployment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
@ -187,7 +163,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
task: [oracle, ui, monitor, multiple, repo] task: [oracle, monitor, multiple, repo]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
@ -206,20 +182,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
task: [amb, erc-to-erc, erc-to-native, native-to-erc, amb-stake-erc-to-erc] task: [amb, erc-to-native]
include:
- task: erc-to-erc
ui-e2e-grep: 'ERC TO ERC'
ui-config: 'e2e-commons/components-envs/ui-erc20.env'
- task: erc-to-native
ui-e2e-grep: 'ERC TO NATIVE'
ui-config: 'e2e-commons/components-envs/ui-erc20-native.env'
- task: native-to-erc
ui-e2e-grep: 'NATIVE TO ERC'
ui-config: 'e2e-commons/components-envs/ui.env'
- task: amb-stake-erc-to-erc
ui-e2e-grep: 'AMB-STAKE-ERC-TO-ERC'
ui-config: 'e2e-commons/components-envs/ui-amb-stake-erc20-erc20.env'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
@ -227,10 +190,9 @@ jobs:
- name: Evaluate e2e docker images tags - name: Evaluate e2e docker images tags
run: | run: |
git submodule status > submodule.status git submodule status > submodule.status
echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e') }}" >> $GITHUB_ENV echo "E2E_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'Dockerfile.e2e', 'commons', 'oracle-e2e', 'monitor-e2e', 'e2e-commons') }}" >> $GITHUB_ENV
echo "ORACLE_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'oracle') }}" >> $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 "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 "ALM_TAG=${{ hashFiles('yarn.lock', 'package.json', 'submodule.status', 'commons', 'alm') }}" >> $GITHUB_ENV
echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV echo "MOLECULE_RUNNER_TAG=${{ hashFiles('./deployment-e2e/Dockerfile') }}" >> $GITHUB_ENV
- uses: actions/cache@v2 - uses: actions/cache@v2
@ -243,29 +205,23 @@ jobs:
- name: Login to docker registry - name: Login to docker registry
run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }} run: docker login ${DOCKER_REGISTRY} -u ${{ github.actor }} -p ${{ github.token }}
- name: Deploy contracts - name: Deploy contracts
run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy blocks run: ${{ steps.cache-repo.outputs.cache-hit }} && e2e-commons/up.sh deploy generate-amb-tx blocks
- name: Pull e2e oracle image - name: Pull e2e oracle image
run: | run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull oracle 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 docker tag ${DOCKER_IMAGE_BASE}/tokenbridge-e2e-oracle:${ORACLE_TAG} poanetwork/tokenbridge-oracle:latest
- if: ${{ matrix.ui-e2e-grep }} - name: Deploy oracle
name: Pull e2e ui image
run: |
docker-compose -f ./e2e-commons/docker-compose.yml pull ui
docker build \
--build-arg DOCKER_IMAGE_BASE=${DOCKER_IMAGE_BASE} \
--build-arg UI_TAG=${UI_TAG} \
--build-arg DOT_ENV_PATH=${{ matrix.ui-config }} \
-f ./e2e-commons/Dockerfile.ui \
-t ui_ui:latest \
.
- name: Deploy oracle and ui
run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }} run: deployment-e2e/molecule.sh ultimate-${{ matrix.task }}
- name: Reset docker socket permissions - name: Reset docker socket permissions
run: sudo chown -R $USER:docker /var/run/docker.sock 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 - name: Run oracle e2e tests
if: ${{ !matrix.ui-e2e-grep }} run: docker-compose -f ./e2e-commons/docker-compose.yml run -e ULTIMATE=true e2e yarn workspace oracle-e2e run ${{ matrix.task }}
run: docker-compose -f ./e2e-commons/docker-compose.yml run 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

2
.nvmrc
View File

@ -1 +1 @@
10.22 12.22

View File

@ -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_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_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_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. | 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. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | 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_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_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_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. | 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. Set to `eip1559-gas-estimation` if you want to use EIP1559 RPC-based gas estimation. | 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_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_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 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 name | description | value
--- | --- | --- --- | --- | ---
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_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_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_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_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 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,29 +42,25 @@ 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_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_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_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
## UI configuration 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
name | description | value 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)
UI_TITLE | The title for the bridge UI page. `%c` will be replaced by the name of the network. | string 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`
UI_OG_TITLE | The meta title for the deployed bridge page. | string 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`
UI_DESCRIPTION | The meta description for the deployed bridge page. | string 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`
UI_NATIVE_TOKEN_DISPLAY_NAME | name of the home native coin | string 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`
UI_HOME_NETWORK_DISPLAY_NAME | name to be displayed for home network | string 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`
UI_FOREIGN_NETWORK_DISPLAY_NAME | name to be displayed for foreign network | 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`
UI_HOME_WITHOUT_EVENTS | `true` if home network doesn't support events | true/false 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`
UI_FOREIGN_WITHOUT_EVENTS | `true` if foreign network doesn't support events | true/false ORACLE_HOME_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the home chain. Defaults to `500` | `integer`
UI_HOME_EXPLORER_TX_TEMPLATE | template link to transaction on home explorer. `%s` will be replaced by transaction hash | URL template 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`
UI_FOREIGN_EXPLORER_TX_TEMPLATE | template link to transaction on foreign explorer. `%s` will be replaced by transaction hash | URL template 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`
UI_HOME_EXPLORER_ADDRESS_TEMPLATE | template link to address on home explorer. `%s` will be replaced by address | URL template 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`
UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explorer. `%s` will be replaced by address | URL template ORACLE_FOREIGN_EVENTS_REPROCESSING_BLOCK_DELAY | Block confirmations number, after which old logs are being reprocessed in the foreign chain. Defaults to `250` | `integer`
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 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_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 ## Monitor configuration

View File

@ -1,4 +1,4 @@
FROM node:10 as contracts FROM node:12 as contracts
WORKDIR /mono WORKDIR /mono
@ -11,7 +11,7 @@ COPY ./contracts/truffle-config.js ./
COPY ./contracts/contracts ./contracts COPY ./contracts/contracts ./contracts
RUN npm run compile RUN npm run compile
FROM node:10 FROM node:12
WORKDIR /mono WORKDIR /mono
COPY package.json . COPY package.json .
@ -19,6 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
COPY commons/package.json ./commons/ COPY commons/package.json ./commons/
COPY oracle-e2e/package.json ./oracle-e2e/ COPY oracle-e2e/package.json ./oracle-e2e/
COPY monitor-e2e/package.json ./monitor-e2e/ COPY monitor-e2e/package.json ./monitor-e2e/
COPY oracle/src/utils/constants.js ./oracle/src/utils/constants.js
COPY yarn.lock . COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production

View File

@ -19,13 +19,11 @@ Sub-repositories maintained within this monorepo are listed below.
| Sub-repository | Description | | Sub-repository | Description |
| --- | --- | | --- | --- |
| [Oracle](oracle/README.md) | Oracle responsible for listening to bridge related events and authorizing asset transfers. | | [Oracle](oracle/README.md) | 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. | | [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. | | [Deployment](deployment/README.md) | Ansible playbooks for deploying cross-chain bridges. |
| [Oracle-E2E](oracle-e2e/README.md) | End to end tests for the Oracle | | [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 | | [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 | | [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 | | [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 | | [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
@ -56,8 +54,6 @@ Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbrid
The POA TokenBridge provides four operational modes: 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] `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. - [x] `Arbitrary-Message`: Transfer arbitrary data between two networks as so the data could be interpreted as an arbitrary contract method invocation.
@ -68,7 +64,7 @@ Clone the repository:
git clone https://github.com/poanetwork/tokenbridge git clone https://github.com/poanetwork/tokenbridge
``` ```
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts: If there is no need to build docker images for the TokenBridge components (oracle, monitor), initialize submodules, install dependencies, compile the Smart Contracts:
``` ```
yarn initialize yarn initialize
``` ```
@ -91,7 +87,7 @@ Running tests for all projects:
yarn test yarn test
``` ```
Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [UI](ui-e2e/README.md). Additionally there are end-to-end tests for [Oracle](oracle-e2e/README.md) and [Monitor](monitor-e2e/README.md).
For details on building, running and developing please refer to respective READMEs in sub-repositories. For details on building, running and developing please refer to respective READMEs in sub-repositories.

View File

@ -19,6 +19,6 @@
"eslint-plugin-jest": "^23.18.0" "eslint-plugin-jest": "^23.18.0"
}, },
"engines": { "engines": {
"node": ">= 10.18" "node": ">= 12.22"
} }
} }

View File

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

View File

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

View File

@ -1,4 +1,4 @@
FROM node:10 as contracts FROM node:12 as contracts
WORKDIR /mono WORKDIR /mono
@ -19,7 +19,7 @@ COPY --from=contracts /mono/contracts/build ./contracts/build
COPY commons/package.json ./commons/ COPY commons/package.json ./commons/
COPY alm/package.json ./alm/ COPY alm/package.json ./alm/
COPY yarn.lock . COPY yarn.lock .
RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile --production RUN NOYARNPOSTINSTALL=1 yarn install --frozen-lockfile
COPY ./commons ./commons COPY ./commons ./commons
COPY ./alm ./alm COPY ./alm ./alm

View File

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

View File

@ -3,6 +3,8 @@ const { BRIDGE_VALIDATORS_ABI, HOME_AMB_ABI } = require('commons')
const path = require('path') const path = require('path')
require('dotenv').config() require('dotenv').config()
const Web3 = require('web3') const Web3 = require('web3')
const fetch = require('node-fetch')
const { URL } = require('url')
const fs = require('fs') const fs = require('fs')
@ -10,7 +12,9 @@ const {
COMMON_HOME_RPC_URL, COMMON_HOME_RPC_URL,
COMMON_HOME_BRIDGE_ADDRESS, COMMON_HOME_BRIDGE_ADDRESS,
COMMON_FOREIGN_RPC_URL, COMMON_FOREIGN_RPC_URL,
COMMON_FOREIGN_BRIDGE_ADDRESS COMMON_FOREIGN_BRIDGE_ADDRESS,
ALM_FOREIGN_EXPLORER_API,
ALM_HOME_EXPLORER_API
} = process.env } = process.env
const generateSnapshot = async (side, url, bridgeAddress) => { const generateSnapshot = async (side, url, bridgeAddress) => {
@ -19,6 +23,31 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const snapshot = {} const snapshot = {}
const web3 = new Web3(new Web3.providers.HttpProvider(url)) 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() const currentBlockNumber = await web3.eth.getBlockNumber()
snapshot.snapshotBlockNumber = currentBlockNumber snapshot.snapshotBlockNumber = currentBlockNumber
@ -29,10 +58,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress) const bridgeContract = new web3.eth.Contract(HOME_AMB_ABI, bridgeAddress)
// Save RequiredBlockConfirmationChanged events // Save RequiredBlockConfirmationChanged events
let requiredBlockConfirmationChangedEvents = await bridgeContract.getPastEvents('RequiredBlockConfirmationChanged', { let requiredBlockConfirmationChangedEvents = await getPastEventsWithFallback(
fromBlock: 0, bridgeContract,
toBlock: currentBlockNumber 'RequiredBlockConfirmationChanged',
}) {
fromBlock: 0,
toBlock: currentBlockNumber
}
)
// In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB // In case RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
// manually generate an event for this. Example Sokol - Kovan bridge // manually generate an event for this. Example Sokol - Kovan bridge
@ -59,10 +92,14 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress) const validatorContract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, validatorAddress)
// Save RequiredSignaturesChanged events // Save RequiredSignaturesChanged events
const RequiredSignaturesChangedEvents = await validatorContract.getPastEvents('RequiredSignaturesChanged', { const RequiredSignaturesChangedEvents = await getPastEventsWithFallback(
fromBlock: 0, validatorContract,
toBlock: currentBlockNumber 'RequiredSignaturesChanged',
}) {
fromBlock: 0,
toBlock: currentBlockNumber
}
)
snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({ snapshot.RequiredSignaturesChanged = RequiredSignaturesChangedEvents.map(e => ({
blockNumber: e.blockNumber, blockNumber: e.blockNumber,
returnValues: { returnValues: {
@ -71,7 +108,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
})) }))
// Save ValidatorAdded events // Save ValidatorAdded events
const validatorAddedEvents = await validatorContract.getPastEvents('ValidatorAdded', { const validatorAddedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorAdded', {
fromBlock: 0, fromBlock: 0,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}) })
@ -85,7 +122,7 @@ const generateSnapshot = async (side, url, bridgeAddress) => {
})) }))
// Save ValidatorRemoved events // Save ValidatorRemoved events
const validatorRemovedEvents = await validatorContract.getPastEvents('ValidatorRemoved', { const validatorRemovedEvents = await getPastEventsWithFallback(validatorContract, 'ValidatorRemoved', {
fromBlock: 0, fromBlock: 0,
toBlock: currentBlockNumber toBlock: currentBlockNumber
}) })

View File

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

View File

@ -1,9 +1,9 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { useMessageConfirmations } from '../hooks/useMessageConfirmations' import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
import { MessageObject } from '../utils/web3' import { MessageObject } from '../utils/web3'
import styled from 'styled-components' import styled from 'styled-components'
import { CONFIRMATIONS_STATUS } from '../config/constants' import { CONFIRMATIONS_STATUS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions' import { CONFIRMATIONS_STATUS_LABEL, CONFIRMATIONS_STATUS_LABEL_HOME } from '../config/descriptions'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
import { ValidatorsConfirmations } from './ValidatorsConfirmations' import { ValidatorsConfirmations } from './ValidatorsConfirmations'
@ -54,7 +54,9 @@ export const ConfirmationsContainer = ({
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
} = useStateProvider() } = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt }) const src = useValidatorContract(fromHome, receipt ? receipt.blockNumber : 0)
const [executionBlockNumber, setExecutionBlockNumber] = useState(0)
const dst = useValidatorContract(!fromHome, executionBlockNumber || 'latest')
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt }) const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
const { const {
confirmations, confirmations,
@ -71,11 +73,21 @@ export const ConfirmationsContainer = ({
fromHome, fromHome,
homeStartBlock, homeStartBlock,
foreignStartBlock, foreignStartBlock,
requiredSignatures, requiredSignatures: src.requiredSignatures,
validatorList, validatorList: src.validatorList,
targetValidatorList: dst.validatorList,
blockConfirmations 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 statusLabel = fromHome ? CONFIRMATIONS_STATUS_LABEL_HOME : CONFIRMATIONS_STATUS_LABEL
const parseDescription = () => { const parseDescription = () => {
@ -114,20 +126,22 @@ export const ConfirmationsContainer = ({
</MultiLine> </MultiLine>
</StatusDescription> </StatusDescription>
<ValidatorsConfirmations <ValidatorsConfirmations
confirmations={confirmations} confirmations={fromHome ? confirmations.filter(c => dst.validatorList.includes(c.validator)) : confirmations}
requiredSignatures={requiredSignatures} requiredSignatures={dst.requiredSignatures}
validatorList={validatorList} validatorList={dst.validatorList}
waitingBlocksResolved={waitingBlocksResolved} waitingBlocksResolved={waitingBlocksResolved}
/> />
{signatureCollected && ( {signatureCollected && (
<ExecutionConfirmation <ExecutionConfirmation
messageData={message.data} message={message}
executionData={executionData} executionData={executionData}
isHome={!fromHome} isHome={!fromHome}
signatureCollected={signatureCollected} confirmations={confirmations}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
executionEventsFetched={executionEventsFetched} executionEventsFetched={executionEventsFetched}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
dstRequiredSignatures={dst.requiredSignatures}
dstValidatorList={dst.validatorList}
/> />
)} )}
</StyledConfirmationContainer> </StyledConfirmationContainer>

View File

@ -1,38 +1,50 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size' import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants' import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components' import styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ConfirmationParam, ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels' import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table' import { Thead, AgeTd, StatusTd } from './commons/Table'
import { ManualExecutionButton } from './ManualExecutionButton' import { ManualExecutionButton } from './ManualExecutionButton'
import { useStateProvider } from '../state/StateProvider'
import { matchesRule, MessageObject, WarnRule } from '../utils/web3'
import { WarningAlert } from './commons/WarningAlert'
import { ErrorAlert } from './commons/ErrorAlert'
const StyledExecutionConfirmation = styled.div` const StyledExecutionConfirmation = styled.div`
margin-top: 30px; margin-top: 30px;
` `
export interface ExecutionConfirmationParams { export interface ExecutionConfirmationParams {
messageData: string message: MessageObject
executionData: ExecutionData executionData: ExecutionData
setExecutionData: Function setExecutionData: Function
signatureCollected: boolean | string[] confirmations: ConfirmationParam[]
isHome: boolean isHome: boolean
executionEventsFetched: boolean executionEventsFetched: boolean
setPendingExecution: Function setPendingExecution: Function
dstRequiredSignatures: number
dstValidatorList: string[]
} }
export const ExecutionConfirmation = ({ export const ExecutionConfirmation = ({
messageData, message,
executionData, executionData,
setExecutionData, setExecutionData,
signatureCollected, confirmations,
isHome, isHome,
executionEventsFetched, executionEventsFetched,
setPendingExecution setPendingExecution,
dstRequiredSignatures,
dstValidatorList
}: ExecutionConfirmationParams) => { }: ExecutionConfirmationParams) => {
const { foreign } = useStateProvider()
const [safeExecutionAvailable, setSafeExecutionAvailable] = useState(false)
const [error, setError] = useState('')
const [warning, setWarning] = useState('')
const availableManualExecution = const availableManualExecution =
!isHome && !isHome &&
(executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING || (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
@ -48,9 +60,43 @@ export const ExecutionConfirmation = ({
const formattedValidator = const formattedValidator =
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
useEffect(
() => {
if (!availableManualExecution || !foreign.bridgeContract) return
const p = foreign.bridgeContract.methods.getBridgeInterfacesVersion().call()
p.then(({ major, minor }: any) => {
major = parseInt(major, 10)
minor = parseInt(minor, 10)
if (major < 5 || (major === 5 && minor < 7)) return
setSafeExecutionAvailable(true)
})
},
[availableManualExecution, foreign.bridgeContract]
)
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 = '') => { const getExecutionStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS:
return <SuccessLabel>{validatorStatus}</SuccessLabel> return <SuccessLabel>{validatorStatus}</SuccessLabel>
case VALIDATOR_CONFIRMATION_STATUS.FAILED: case VALIDATOR_CONFIRMATION_STATUS.FAILED:
return <RedLabel>{validatorStatus}</RedLabel> return <RedLabel>{validatorStatus}</RedLabel>
@ -68,6 +114,8 @@ export const ExecutionConfirmation = ({
return ( return (
<StyledExecutionConfirmation> <StyledExecutionConfirmation>
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
{warning && <WarningAlert onClick={() => setWarning('')} error={warning} />}
<table> <table>
<Thead> <Thead>
<tr> <tr>
@ -105,10 +153,14 @@ export const ExecutionConfirmation = ({
{availableManualExecution && ( {availableManualExecution && (
<td> <td>
<ManualExecutionButton <ManualExecutionButton
messageData={messageData} safeExecutionAvailable={safeExecutionAvailable}
messageData={message.data}
setExecutionData={setExecutionData} setExecutionData={setExecutionData}
signatureCollected={signatureCollected as string[]} confirmations={confirmations}
setPendingExecution={setPendingExecution} setPendingExecution={setPendingExecution}
setError={setError}
requiredSignatures={dstRequiredSignatures}
validatorList={dstValidatorList}
/> />
</td> </td>
)} )}

View File

@ -8,7 +8,6 @@ import { TransactionReceipt } from 'web3-eth'
import { InfoAlert } from './commons/InfoAlert' import { InfoAlert } from './commons/InfoAlert'
import { ExplorerTxLink } from './commons/ExplorerTxLink' import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants' import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
import { ErrorAlert } from './commons/ErrorAlert'
const StyledMainPage = styled.div` const StyledMainPage = styled.div`
text-align: center; text-align: center;
@ -52,7 +51,7 @@ export interface FormSubmitParams {
export const MainPage = () => { export const MainPage = () => {
const history = useHistory() const history = useHistory()
const { home, foreign, error, setError } = useStateProvider() const { home, foreign } = useStateProvider()
const [networkName, setNetworkName] = useState('') const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null) const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false) const [showInfoAlert, setShowInfoAlert] = useState(false)
@ -132,7 +131,6 @@ export const MainPage = () => {
</AlertP> </AlertP>
</InfoAlert> </InfoAlert>
)} )}
{error && <ErrorAlert onClick={() => setError('')} error={error} />}
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} /> <Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
<Route <Route
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']} path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}

View File

@ -6,6 +6,7 @@ import {
DOUBLE_EXECUTION_ATTEMPT_ERROR, DOUBLE_EXECUTION_ATTEMPT_ERROR,
EXECUTION_FAILED_ERROR, EXECUTION_FAILED_ERROR,
EXECUTION_OUT_OF_GAS_ERROR, EXECUTION_OUT_OF_GAS_ERROR,
FOREIGN_EXPLORER_API,
INCORRECT_CHAIN_ERROR, INCORRECT_CHAIN_ERROR,
VALIDATOR_CONFIRMATION_STATUS VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants' } from '../config/constants'
@ -13,32 +14,92 @@ import { useStateProvider } from '../state/StateProvider'
import { signatureToVRS, packSignatures } from '../utils/signatures' import { signatureToVRS, packSignatures } from '../utils/signatures'
import { getSuccessExecutionData } from '../utils/getFinalizationEvent' import { getSuccessExecutionData } from '../utils/getFinalizationEvent'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
const StyledButton = styled.button` const ActionButton = styled.button`
color: var(--button-color); color: var(--button-color);
border-color: var(--font-color); border-color: var(--font-color);
margin-top: 10px; margin-top: 10px;
min-width: 120px;
padding: 1rem;
&:focus { &:focus {
outline: var(--button-color); outline: var(--button-color);
} }
` `
interface ManualExecutionButtonParams { interface ManualExecutionButtonParams {
safeExecutionAvailable: boolean
messageData: string messageData: string
setExecutionData: Function setExecutionData: Function
signatureCollected: string[] confirmations: ConfirmationParam[]
setPendingExecution: Function setPendingExecution: Function
setError: Function
requiredSignatures: number
validatorList: string[]
} }
export const ManualExecutionButton = ({ export const ManualExecutionButton = ({
safeExecutionAvailable,
messageData, messageData,
setExecutionData, setExecutionData,
signatureCollected, confirmations,
setPendingExecution setPendingExecution,
setError,
requiredSignatures,
validatorList
}: ManualExecutionButtonParams) => { }: ManualExecutionButtonParams) => {
const { foreign, setError } = useStateProvider() const { foreign } = useStateProvider()
const { library, activate, account, active } = useWeb3React() const { library, activate, account, active } = useWeb3React()
const [manualExecution, setManualExecution] = useState(false) const [manualExecution, setManualExecution] = useState(false)
const [allowFailures, setAllowFailures] = useState(false)
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
]
)
useEffect( useEffect(
() => { () => {
@ -66,12 +127,16 @@ export const ManualExecutionButton = ({
return return
} }
if (!library || !foreign.bridgeContract || !signatureCollected || !signatureCollected.length) return if (!library || !foreign.bridgeContract || !foreign.web3 || !validSignatures || !validSignatures.length) return
const signatures = packSignatures(signatureCollected.map(signatureToVRS)) const signatures = packSignatures(validSignatures.map(signatureToVRS))
const messageId = messageData.slice(0, 66) const messageId = messageData.slice(0, 66)
const bridge = foreign.bridgeContract const bridge = foreign.bridgeContract
const data = bridge.methods.executeSignatures(messageData, signatures).encodeABI() const executeMethod =
safeExecutionAvailable && !allowFailures
? bridge.methods.safeExecuteSignaturesWithAutoGasLimit
: bridge.methods.executeSignatures
const data = executeMethod(messageData, signatures).encodeABI()
setManualExecution(false) setManualExecution(false)
library.eth library.eth
@ -92,7 +157,13 @@ export const ManualExecutionButton = ({
}) })
.on('error', async (e: Error, receipt: TransactionReceipt) => { .on('error', async (e: Error, receipt: TransactionReceipt) => {
if (e.message.includes('Transaction has been reverted by the EVM')) { if (e.message.includes('Transaction has been reverted by the EVM')) {
const successExecutionData = await getSuccessExecutionData(bridge, 'RelayedMessage', library, messageId) const successExecutionData = await getSuccessExecutionData(
bridge,
'RelayedMessage',
library,
messageId,
FOREIGN_EXPLORER_API
)
if (successExecutionData) { if (successExecutionData) {
setExecutionData(successExecutionData) setExecutionData(successExecutionData)
setError(DOUBLE_EXECUTION_ATTEMPT_ERROR) setError(DOUBLE_EXECUTION_ATTEMPT_ERROR)
@ -123,17 +194,38 @@ export const ManualExecutionButton = ({
foreign.bridgeContract, foreign.bridgeContract,
setError, setError,
messageData, messageData,
signatureCollected,
setExecutionData, setExecutionData,
setPendingExecution setPendingExecution,
safeExecutionAvailable,
allowFailures,
foreign.web3,
validSignatures
] ]
) )
return ( return (
<div className="is-center"> <div>
<StyledButton className="button outline" onClick={() => setManualExecution(true)}> <div className="is-center">
Execute <ActionButton disabled={!ready} className="button outline" onClick={() => setManualExecution(true)}>
</StyledButton> {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> </div>
) )
} }

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks' import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size' import { useWindowWidth } from '@react-hook/window-size'
import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { RECENT_AGE, SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components' import styled from 'styled-components'
import { ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
@ -31,7 +31,9 @@ export const ValidatorsConfirmations = ({
const getValidatorStatusElement = (validatorStatus = '') => { const getValidatorStatusElement = (validatorStatus = '') => {
switch (validatorStatus) { switch (validatorStatus) {
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS: case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
return <SuccessLabel>{validatorStatus}</SuccessLabel> case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
case VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID:
return <SuccessLabel>{VALIDATOR_CONFIRMATION_STATUS.SUCCESS}</SuccessLabel>
case VALIDATOR_CONFIRMATION_STATUS.FAILED: case VALIDATOR_CONFIRMATION_STATUS.FAILED:
return <RedLabel>{validatorStatus}</RedLabel> return <RedLabel>{validatorStatus}</RedLabel>
case VALIDATOR_CONFIRMATION_STATUS.PENDING: case VALIDATOR_CONFIRMATION_STATUS.PENDING:
@ -58,26 +60,28 @@ export const ValidatorsConfirmations = ({
</tr> </tr>
</Thead> </Thead>
<tbody> <tbody>
{validatorList.map((validator, i) => { {confirmations.map((confirmation, i) => {
const filteredConfirmation = confirmations.filter(c => c.validator === validator) const displayedStatus = confirmation.status
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null const explorerLink = getExplorerTxUrl(confirmation.txHash, true)
const displayedStatus = confirmation && confirmation.status ? confirmation.status : '' let elementIfNoTimestamp: any = <SimpleLoading />
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : '' switch (displayedStatus) {
const elementIfNoTimestamp = case '':
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING && case VALIDATOR_CONFIRMATION_STATUS.UNDEFINED:
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? ( if (waitingBlocksResolved) {
(displayedStatus === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || displayedStatus === '') && elementIfNoTimestamp = SEARCHING_TX
waitingBlocksResolved ? ( }
SEARCHING_TX break
) : ( case VALIDATOR_CONFIRMATION_STATUS.WAITING:
<SimpleLoading /> case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
) elementIfNoTimestamp = ''
) : ( break
'' case VALIDATOR_CONFIRMATION_STATUS.MANUAL:
) elementIfNoTimestamp = RECENT_AGE
break
}
return ( return (
<tr key={i}> <tr key={i}>
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td> <td>{windowWidth < 850 ? formatTxHash(confirmation.validator) : confirmation.validator}</td>
<StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd> <StatusTd className="text-center">{getValidatorStatusElement(displayedStatus)}</StatusTd>
<AgeTd className="text-center"> <AgeTd className="text-center">
{confirmation && confirmation.timestamp > 0 ? ( {confirmation && confirmation.timestamp > 0 ? (
@ -94,7 +98,7 @@ export const ValidatorsConfirmations = ({
</tbody> </tbody>
</table> </table>
<RequiredConfirmations> <RequiredConfirmations>
{requiredSignatures} of {validatorList.length} confirmations required At least <strong>{requiredSignatures}</strong> of <strong>{validatorList.length}</strong> confirmations required
</RequiredConfirmations> </RequiredConfirmations>
</div> </div>
) )

View File

@ -33,7 +33,7 @@ export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: str
} }
return ( return (
<div className="row is-center"> <div className="row is-center">
<StyledErrorAlert className="col-10 is-vertical-align row"> <StyledErrorAlert className="col-12 is-vertical-align row">
<InfoIcon color="var(--failed-color)" /> <InfoIcon color="var(--failed-color)" />
<TextContainer className="col-10"> <TextContainer className="col-10">
{text} {text}

View File

@ -0,0 +1,34 @@
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>
)
}

View File

@ -54,14 +54,19 @@ export const CONFIRMATIONS_STATUS = {
} }
export const VALIDATOR_CONFIRMATION_STATUS = { export const VALIDATOR_CONFIRMATION_STATUS = {
SUCCESS: 'Success', SUCCESS: 'Confirmed',
MANUAL: 'Manual',
EXECUTION_SUCCESS: 'Executed',
FAILED: 'Failed', FAILED: 'Failed',
FAILED_VALID: 'Failed valid',
PENDING: 'Pending', PENDING: 'Pending',
WAITING: 'Waiting', WAITING: 'Waiting',
NOT_REQUIRED: 'Not required', NOT_REQUIRED: 'Not required',
UNDEFINED: 'UNDEFINED' UNDEFINED: 'UNDEFINED'
} }
export const RECENT_AGE = 'Recent'
export const SEARCHING_TX = 'Searching Transaction...' export const SEARCHING_TX = 'Searching Transaction...'
export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.` export const INCORRECT_CHAIN_ERROR = `Incorrect chain chosen. Switch to ${FOREIGN_NETWORK_NAME} in the wallet.`

View File

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

View File

@ -11,9 +11,6 @@ import {
VALIDATOR_CONFIRMATION_STATUS VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants' } from '../config/constants'
import { homeBlockNumberProvider, foreignBlockNumberProvider } from '../services/BlockNumberProvider' 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 { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
import { getFinalizationEvent } from '../utils/getFinalizationEvent' import { getFinalizationEvent } from '../utils/getFinalizationEvent'
import { import {
@ -32,17 +29,16 @@ export interface useMessageConfirmationsParams {
foreignStartBlock: Maybe<number> foreignStartBlock: Maybe<number>
requiredSignatures: number requiredSignatures: number
validatorList: string[] validatorList: string[]
targetValidatorList: string[]
blockConfirmations: number blockConfirmations: number
} }
export interface BasicConfirmationParam { export interface ConfirmationParam {
validator: string validator: string
status: string status: string
}
export interface ConfirmationParam extends BasicConfirmationParam {
txHash: string txHash: string
timestamp: number timestamp: number
signature?: string
} }
export interface ExecutionData { export interface ExecutionData {
@ -51,6 +47,7 @@ export interface ExecutionData {
txHash: string txHash: string
timestamp: number timestamp: number
executionResult: boolean executionResult: boolean
blockNumber: number
} }
export const useMessageConfirmations = ({ export const useMessageConfirmations = ({
@ -61,10 +58,11 @@ export const useMessageConfirmations = ({
foreignStartBlock, foreignStartBlock,
requiredSignatures, requiredSignatures,
validatorList, validatorList,
targetValidatorList,
blockConfirmations blockConfirmations
}: useMessageConfirmationsParams) => { }: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
const [confirmations, setConfirmations] = useState([]) const [confirmations, setConfirmations] = useState<ConfirmationParam[]>([])
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
const [waitingBlocks, setWaitingBlocks] = useState(false) const [waitingBlocks, setWaitingBlocks] = useState(false)
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false) const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
@ -76,7 +74,8 @@ export const useMessageConfirmations = ({
validator: '', validator: '',
txHash: '', txHash: '',
timestamp: 0, timestamp: 0,
executionResult: false executionResult: false,
blockNumber: 0
}) })
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false) const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false) const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
@ -85,12 +84,10 @@ export const useMessageConfirmations = ({
const [pendingConfirmations, setPendingConfirmations] = useState(false) const [pendingConfirmations, setPendingConfirmations] = useState(false)
const [pendingExecution, setPendingExecution] = useState(false) const [pendingExecution, setPendingExecution] = useState(false)
const existsConfirmation = (confirmationArray: ConfirmationParam[]) => { const existsConfirmation = (confirmationArray: ConfirmationParam[]) =>
const filteredList = confirmationArray.filter( confirmationArray.some(
c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING c => c.status !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED && c.status !== VALIDATOR_CONFIRMATION_STATUS.WAITING
) )
return filteredList.length > 0
}
// start watching blocks at the start // start watching blocks at the start
useEffect( useEffect(
@ -108,43 +105,38 @@ export const useMessageConfirmations = ({
() => { () => {
if (!receipt || !blockConfirmations || waitingBlocksResolved) return if (!receipt || !blockConfirmations || waitingBlocksResolved) return
const subscriptions: Array<number> = [] let timeoutId: number
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider
const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL
const targetBlock = receipt.blockNumber + blockConfirmations const targetBlock = receipt.blockNumber + blockConfirmations
const validatorsWaiting = validatorList.map(validator => ({
validator,
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
txHash: '',
timestamp: 0
}))
checkSignaturesWaitingForBLocks( const checkSignaturesWaitingForBLocks = () => {
targetBlock, const currentBlock = blockProvider.get()
setWaitingBlocks,
setWaitingBlocksResolved,
validatorList,
setConfirmations,
blockProvider,
interval,
subscriptions
)
return () => { if (currentBlock && currentBlock >= targetBlock) {
unsubscribe() setWaitingBlocksResolved(true)
setWaitingBlocks(false)
} else if (currentBlock) {
setWaitingBlocks(true)
setConfirmations(validatorsWaiting)
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, interval)
} else {
timeoutId = setTimeout(checkSignaturesWaitingForBLocks, 500)
}
} }
checkSignaturesWaitingForBLocks()
return () => clearTimeout(timeoutId)
}, },
[ [blockConfirmations, fromHome, receipt, validatorList, waitingBlocksResolved]
blockConfirmations,
foreign.web3,
fromHome,
validatorList,
home.web3,
receipt,
setConfirmations,
waitingBlocksResolved
]
) )
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
@ -152,32 +144,40 @@ export const useMessageConfirmations = ({
// This is executed if the message is in Home to Foreign direction only // This is executed if the message is in Home to Foreign direction only
useEffect( useEffect(
() => { () => {
if (!fromHome || !receipt || !home.web3 || !signatureCollected) return if (!fromHome || !receipt || !home.web3 || !home.bridgeContract || !signatureCollected) return
const subscriptions: Array<number> = [] let timeoutId: number
let isCancelled = false
const unsubscribe = () => { const messageHash = home.web3.utils.soliditySha3Raw(message.data)
subscriptions.forEach(s => { const contract = home.bridgeContract
clearTimeout(s)
}) 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 fromBlock = receipt.blockNumber getCollectedSignaturesEvent(receipt.blockNumber, receipt.blockNumber + BLOCK_RANGE)
const toBlock = fromBlock + BLOCK_RANGE
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
getCollectedSignaturesEvent(
home.web3,
home.bridgeContract,
fromBlock,
toBlock,
messageHash,
setCollectedSignaturesEvent,
subscriptions
)
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
isCancelled = true
} }
}, },
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected] [fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
@ -187,49 +187,99 @@ export const useMessageConfirmations = ({
// This is executed if the message is in Home to Foreign direction only // This is executed if the message is in Home to Foreign direction only
useEffect( useEffect(
() => { () => {
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return if (!fromHome || !home.web3 || !collectedSignaturesEvent || !blockConfirmations) return
if (waitingBlocksForExecutionResolved) return if (waitingBlocksForExecutionResolved) return
const subscriptions: Array<number> = [] let timeoutId: number
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
checkWaitingBlocksForExecution( const checkWaitingBlocksForExecution = () => {
homeBlockNumberProvider, const currentBlock = homeBlockNumberProvider.get()
HOME_RPC_POLLING_INTERVAL,
targetBlock,
collectedSignaturesEvent,
setWaitingBlocksForExecution,
setWaitingBlocksForExecutionResolved,
setExecutionData,
subscriptions
)
return () => { if (currentBlock && currentBlock >= targetBlock) {
unsubscribe() const undefinedExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? undefinedExecutionState
: data
)
setWaitingBlocksForExecutionResolved(true)
setWaitingBlocksForExecution(false)
} else if (currentBlock) {
setWaitingBlocksForExecution(true)
const waitingExecutionState = {
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
txHash: '',
timestamp: 0,
executionResult: false
}
setExecutionData(
(data: any) =>
data.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED ||
data.status === VALIDATOR_CONFIRMATION_STATUS.WAITING
? waitingExecutionState
: data
)
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), HOME_RPC_POLLING_INTERVAL)
} else {
timeoutId = setTimeout(() => checkWaitingBlocksForExecution(), 500)
}
} }
checkWaitingBlocksForExecution()
return () => clearTimeout(timeoutId)
}, },
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt, waitingBlocksForExecutionResolved] [collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, waitingBlocksForExecutionResolved]
) )
// Checks if validators verified the message // Checks if validators verified the message
// To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations // To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations
useEffect( useEffect(
() => { () => {
if (!waitingBlocksResolved || !homeStartBlock || !requiredSignatures) return if (!waitingBlocksResolved || !homeStartBlock || !requiredSignatures || !home.web3 || !home.bridgeContract) return
if (!validatorList || !validatorList.length) return
const subscriptions: Array<number> = [] let timeoutId: number
let isCancelled = false
const unsubscribe = () => { if (fromHome) {
subscriptions.forEach(s => { if (!targetValidatorList || !targetValidatorList.length) return
clearTimeout(s) 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)
} }
getConfirmationsForTx( getConfirmationsForTx(
@ -241,8 +291,8 @@ export const useMessageConfirmations = ({
setConfirmations, setConfirmations,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, id => (timeoutId = id),
subscriptions, () => isCancelled,
homeStartBlock, homeStartBlock,
getValidatorFailedTransactionsForMessage, getValidatorFailedTransactionsForMessage,
setFailedConfirmations, setFailedConfirmations,
@ -252,7 +302,8 @@ export const useMessageConfirmations = ({
) )
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
isCancelled = true
} }
}, },
[ [
@ -264,7 +315,7 @@ export const useMessageConfirmations = ({
requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
homeStartBlock, homeStartBlock,
setConfirmations targetValidatorList
] ]
) )
@ -274,32 +325,23 @@ export const useMessageConfirmations = ({
useEffect( useEffect(
() => { () => {
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
const startBlock = fromHome ? foreignStartBlock : homeStartBlock
if (!startBlock) return
const subscriptions: Array<number> = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const contractEvent = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract
const providedWeb3 = fromHome ? foreign.web3 : home.web3 const web3 = fromHome ? foreign.web3 : home.web3
const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL const startBlock = fromHome ? foreignStartBlock : homeStartBlock
if (!startBlock || !bridgeContract || !web3) return
let timeoutId: number
let isCancelled = false
getFinalizationEvent( getFinalizationEvent(
fromHome, fromHome,
bridgeContract, bridgeContract,
contractEvent, web3,
providedWeb3,
setExecutionData, setExecutionData,
waitingBlocksResolved,
message, message,
interval, id => (timeoutId = id),
subscriptions, () => isCancelled,
startBlock, startBlock,
collectedSignaturesEvent, collectedSignaturesEvent,
getExecutionFailedTransactionForMessage, getExecutionFailedTransactionForMessage,
@ -310,7 +352,8 @@ export const useMessageConfirmations = ({
) )
return () => { return () => {
unsubscribe() clearTimeout(timeoutId)
isCancelled = true
} }
}, },
[ [
@ -331,7 +374,10 @@ export const useMessageConfirmations = ({
// Sets the message status based in the collected information // Sets the message status based in the collected information
useEffect( useEffect(
() => { () => {
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS && existsConfirmation(confirmations)) { if (
executionData.status === VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS &&
existsConfirmation(confirmations)
) {
const newStatus = executionData.executionResult const newStatus = executionData.executionResult
? CONFIRMATIONS_STATUS.SUCCESS ? CONFIRMATIONS_STATUS.SUCCESS
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED : CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED

View File

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

View File

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

View File

@ -4,18 +4,13 @@ import Web3 from 'web3'
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract' import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
import { BRIDGE_VALIDATORS_ABI } from '../abis' import { BRIDGE_VALIDATORS_ABI } from '../abis'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { TransactionReceipt } from 'web3-eth'
import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider' import { foreignSnapshotProvider, homeSnapshotProvider, SnapshotProvider } from '../services/SnapshotProvider'
import { FOREIGN_EXPLORER_API, HOME_EXPLORER_API } from '../config/constants'
export interface useValidatorContractParams { export const useValidatorContract = (isHome: boolean, blockNumber: number | 'latest') => {
fromHome: boolean
receipt: Maybe<TransactionReceipt>
}
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null) const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
const [requiredSignatures, setRequiredSignatures] = useState(0) const [requiredSignatures, setRequiredSignatures] = useState(0)
const [validatorList, setValidatorList] = useState([]) const [validatorList, setValidatorList] = useState<string[]>([])
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
@ -28,45 +23,50 @@ export const useValidatorContract = ({ receipt, fromHome }: useValidatorContract
const callRequiredSignatures = async ( const callRequiredSignatures = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
receipt: TransactionReceipt, blockNumber: number | 'latest',
setResult: Function, setResult: Function,
snapshotProvider: SnapshotProvider snapshotProvider: SnapshotProvider,
web3: Web3,
api: string
) => { ) => {
if (!contract) return if (!contract) return
const result = await getRequiredSignatures(contract, receipt.blockNumber, snapshotProvider) const result = await getRequiredSignatures(contract, blockNumber, snapshotProvider, web3, api)
setResult(result) setResult(result)
} }
const callValidatorList = async ( const callValidatorList = async (
contract: Maybe<Contract>, contract: Maybe<Contract>,
receipt: TransactionReceipt, blockNumber: number | 'latest',
setResult: Function, setResult: Function,
snapshotProvider: SnapshotProvider snapshotProvider: SnapshotProvider,
web3: Web3,
api: string
) => { ) => {
if (!contract) return if (!contract) return
const result = await getValidatorList(contract, receipt.blockNumber, snapshotProvider) const result = await getValidatorList(contract, blockNumber, snapshotProvider, web3, api)
setResult(result) 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( useEffect(
() => { () => {
const web3 = fromHome ? home.web3 : foreign.web3
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
if (!web3 || !bridgeContract) return if (!web3 || !bridgeContract) return
callValidatorContract(bridgeContract, web3, setValidatorContract) callValidatorContract(bridgeContract, web3, setValidatorContract)
}, },
[home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome] [web3, bridgeContract]
) )
useEffect( useEffect(
() => { () => {
if (!receipt) return if (!web3 || !blockNumber) return
const snapshotProvider = fromHome ? homeSnapshotProvider : foreignSnapshotProvider callRequiredSignatures(validatorContract, blockNumber, setRequiredSignatures, snapshotProvider, web3, api)
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures, snapshotProvider) callValidatorList(validatorContract, blockNumber, setValidatorList, snapshotProvider, web3, api)
callValidatorList(validatorContract, receipt, setValidatorList, snapshotProvider)
}, },
[validatorContract, receipt, fromHome] [validatorContract, blockNumber, web3, snapshotProvider, api]
) )
return { return {

View File

@ -1,4 +1,4 @@
import React, { createContext, ReactNode, useState } from 'react' import React, { createContext, ReactNode } from 'react'
import { useNetwork } from '../hooks/useNetwork' import { useNetwork } from '../hooks/useNetwork'
import { import {
HOME_RPC_URL, HOME_RPC_URL,
@ -25,8 +25,6 @@ export interface StateContext {
home: BaseNetworkParams home: BaseNetworkParams
foreign: BaseNetworkParams foreign: BaseNetworkParams
loading: boolean loading: boolean
error: string
setError: Function
} }
const initialState = { const initialState = {
@ -44,9 +42,7 @@ const initialState = {
bridgeAddress: FOREIGN_BRIDGE_ADDRESS, bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
bridgeContract: null bridgeContract: null
}, },
loading: true, loading: true
error: '',
setError: () => {}
} }
const StateContext = createContext<StateContext>(initialState) const StateContext = createContext<StateContext>(initialState)
@ -58,7 +54,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
homeWeb3: homeNetwork.web3, homeWeb3: homeNetwork.web3,
foreignWeb3: foreignNetwork.web3 foreignWeb3: foreignNetwork.web3
}) })
const [error, setError] = useState('')
const value = { const value = {
home: { home: {
@ -73,9 +68,7 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
bridgeContract: foreignBridge, bridgeContract: foreignBridge,
...foreignNetwork ...foreignNetwork
}, },
loading: homeNetwork.loading || foreignNetwork.loading, loading: homeNetwork.loading || foreignNetwork.loading
error,
setError
} }
return <StateContext.Provider value={value}>{children}</StateContext.Provider> return <StateContext.Provider value={value}>{children}</StateContext.Provider>

View File

@ -28,5 +28,7 @@ export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor}; --not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
--failed-color: ${props => props.theme.failed.textColor}; --failed-color: ${props => props.theme.failed.textColor};
--failed-bg-color: ${props => props.theme.failed.backgroundColor}; --failed-bg-color: ${props => props.theme.failed.backgroundColor};
--warning-color: ${props => props.theme.warning.textColor};
--warning-bg-color: ${props => props.theme.warning.backgroundColor};
} }
` `

View File

@ -17,6 +17,10 @@ const theme = {
failed: { failed: {
textColor: '#de4437', textColor: '#de4437',
backgroundColor: 'rgba(222,68,55,.1)' backgroundColor: 'rgba(222,68,55,.1)'
},
warning: {
textColor: '#ffa758',
backgroundColor: 'rgba(222,68,55,.1)'
} }
} }
export default theme export default theme

View File

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

View File

@ -14,37 +14,40 @@ const messageData = '0x123456'
const OTHER_HASH = 'aabbccdd' const OTHER_HASH = 'aabbccdd'
const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560' const bridgeAddress = '0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560'
const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1' const otherAddress = '0xD4075FB57fCf038bFc702c915Ef9592534bED5c1'
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
describe('getFailedTransactions', () => { describe('getFailedTransactions', () => {
test('should only return failed transactions', async () => { test('should only return failed transactions', async () => {
const to = otherAddress const to = otherAddress
const transactions = [ const transactions = [
{ isError: '0', to }, { isError: '0', to, from: validator1 },
{ isError: '1', to }, { isError: '1', to, from: validator1 },
{ isError: '0', to }, { isError: '0', to, from: validator2 },
{ isError: '1', to }, { isError: '1', to, from: validator2 },
{ isError: '1', to } { isError: '1', to, from: validator3 }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getFailedTransactions('', to, 0, 1, '', fetchAccountTransactions) const result = await getFailedTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(3) expect(result.length).toEqual(1)
}) })
}) })
describe('getSuccessTransactions', () => { describe('getSuccessTransactions', () => {
test('should only return success transactions', async () => { test('should only return success transactions', async () => {
const to = otherAddress const to = otherAddress
const transactions = [ const transactions = [
{ isError: '0', to }, { isError: '0', to, from: validator1 },
{ isError: '1', to }, { isError: '1', to, from: validator1 },
{ isError: '0', to }, { isError: '0', to, from: validator2 },
{ isError: '1', to }, { isError: '1', to, from: validator2 },
{ isError: '1', to } { isError: '1', to, from: validator3 }
] ]
const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions) const fetchAccountTransactions = jest.fn().mockImplementation(() => transactions)
const result = await getSuccessTransactions('', to, 0, 1, '', fetchAccountTransactions) const result = await getSuccessTransactions(validator1, to, 0, 1, '', fetchAccountTransactions)
expect(result.length).toEqual(2) expect(result.length).toEqual(1)
}) })
}) })
describe('filterValidatorSignatureTransaction', () => { describe('filterValidatorSignatureTransaction', () => {

View File

@ -5,7 +5,7 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { APIPendingTransaction, APITransaction } from '../explorer' import { APIPendingTransaction, APITransaction } from '../explorer'
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
import { BasicConfirmationParam } from '../../hooks/useMessageConfirmations' import { ConfirmationParam } from '../../hooks/useMessageConfirmations'
jest.mock('../validatorConfirmationHelpers') jest.mock('../validatorConfirmationHelpers')
@ -18,6 +18,9 @@ const messageData = '0x111111111'
const web3 = { const web3 = {
utils: { utils: {
soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}` soliditySha3Raw: (data: string) => `0xaaaa${data.replace('0x', '')}`
},
eth: {
accounts: new Web3().eth.accounts
} }
} as Web3 } as Web3
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
@ -25,7 +28,7 @@ const validator2 = '0xAe8bFfc8BBc6AAa9E21ED1E4e4957fe798BEA25f'
const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244' const validator3 = '0x285A6eB779be4db94dA65e2F3518B1c5F0f71244'
const validatorList = [validator1, validator2, validator3] const validatorList = [validator1, validator2, validator3]
const signature = const signature =
'0x519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4519d704bceed17423daa79c20531cc34fc27a4be6e53fc5069a8023019188ca4' '0x6f5b74905669999f1abdb52e1e215506907e1849aac7b31854da458b33a5954e15b165007c3703cfd16e61ca46a96a56727ed11fa47be359d3834515accd016e1b'
const bridgeContract = { const bridgeContract = {
methods: { methods: {
signature: () => ({ signature: () => ({
@ -34,7 +37,7 @@ const bridgeContract = {
} }
} as Contract } as Contract
const requiredSignatures = 2 const requiredSignatures = 2
const waitingBlocksResolved = true const isCancelled = () => false
let subscriptions: Array<number> = [] let subscriptions: Array<number> = []
const timestamp = 1594045859 const timestamp = 1594045859
const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([]) const getFailedTransactions = (): Promise<APITransaction[]> => Promise.resolve([])
@ -61,19 +64,19 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -94,8 +97,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -110,9 +113,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(2) expect(setResult).toBeCalledTimes(2)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -135,7 +137,7 @@ describe('getConfirmationsForTx', () => {
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
]) ])
) )
}) })
@ -144,19 +146,19 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -177,8 +179,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -208,19 +210,19 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator !== validator3 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 ? '0x123' : '', txHash: validatorData.validator !== validator3 ? '0x123' : '',
timestamp: validatorData.validator !== validator3 ? 123 : 0 timestamp: validatorData.validator !== validator3 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
timestamp: 0 timestamp: 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -241,8 +243,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -257,9 +259,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(3) expect(setResult).toBeCalledTimes(3)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -281,16 +282,16 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, 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 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
]) ])
) )
}) })
@ -304,22 +305,22 @@ describe('getConfirmationsForTx', () => {
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '', txHash: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? '0x123' : '',
timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0 timestamp: validatorData.validator !== validator3 && validatorData.validator !== validator4 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.FAILED ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator3 ? '0x123' : '', txHash: validatorData.validator === validator3 ? '0x123' : '',
timestamp: validatorData.validator === validator3 ? 123 : 0 timestamp: validatorData.validator === validator3 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -340,8 +341,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -356,9 +357,8 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(4) expect(setResult).toBeCalledTimes(4)
expect(getValidatorConfirmation).toBeCalledTimes(1) expect(getValidatorConfirmation).toBeCalledTimes(1)
expect(getSuccessExecutionTransaction).toBeCalledTimes(1) expect(getSuccessExecutionTransaction).toBeCalledTimes(1)
expect(setSignatureCollected).toBeCalledTimes(2) expect(setSignatureCollected).toBeCalledTimes(1)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(true) expect(setSignatureCollected.mock.calls[0][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[1][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
@ -382,26 +382,26 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, 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, txHash: '0x123', timestamp: 123 }, { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED } { validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED, txHash: '', timestamp: 0 }
]) ])
) )
}) })
@ -414,22 +414,22 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x123' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator2 validatorData.validator === validator2
? VALIDATOR_CONFIRMATION_STATUS.FAILED ? VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator2 ? '0x123' : '', txHash: validatorData.validator === validator2 ? '0x123' : '',
timestamp: validatorData.validator === validator2 ? 123 : 0 timestamp: validatorData.validator === validator2 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
@ -453,8 +453,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -492,22 +492,22 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x123', timestamp: 123 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 }
]) ])
) )
@ -521,13 +521,13 @@ describe('getConfirmationsForTx', () => {
validator, validator,
status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED status: validator === validator1 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getSuccessExecutionTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x123' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 123 : 0
})) }))
getValidatorFailedTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorFailedTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator !== validator1 validatorData.validator !== validator1
@ -536,7 +536,7 @@ describe('getConfirmationsForTx', () => {
txHash: validatorData.validator !== validator1 ? '0x123' : '', txHash: validatorData.validator !== validator1 ? '0x123' : '',
timestamp: validatorData.validator !== validator1 ? 123 : 0 timestamp: validatorData.validator !== validator1 ? 123 : 0
})) }))
getValidatorPendingTransaction.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ getValidatorPendingTransaction.mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -557,8 +557,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -596,9 +596,9 @@ describe('getConfirmationsForTx', () => {
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
@ -610,9 +610,13 @@ describe('getConfirmationsForTx', () => {
) )
}) })
test('should remove pending state after transaction mined', async () => { test('should remove pending state after transaction mined', async () => {
// Validator1 success const validator4 = '0x9d2dC11C342F4eF3C5491A048D0f0eBCd2D8f7C3'
// Validator2 failed const validatorList = [validator1, validator2, validator3, validator4]
// Validator3 Pending
// Validator1 success (ts=100)
// Validator2 failed (ts=200)
// Validator3 Pending (ts=300)
// Validator4 Excess confirmation (Failed) (ts=400)
getValidatorConfirmation getValidatorConfirmation
.mockImplementationOnce(() => async (validator: string) => ({ .mockImplementationOnce(() => async (validator: string) => ({
@ -623,41 +627,57 @@ describe('getConfirmationsForTx', () => {
.mockImplementation(() => async (validator: string) => ({ .mockImplementation(() => async (validator: string) => ({
validator, validator,
status: status:
validator !== validator2 ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED validator === validator1 || validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.SUCCESS
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
})) }))
getSuccessExecutionTransaction getSuccessExecutionTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator === validator1 ? '0x123' : '', txHash: validatorData.validator === validator1 ? '0x100' : '',
timestamp: validatorData.validator === validator1 ? 123 : 0 timestamp: validatorData.validator === validator1 ? 100 : 0
})) }))
.mockImplementation(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementation(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
txHash: validatorData.validator !== validator2 ? '0x123' : '', txHash:
timestamp: validatorData.validator !== validator2 ? 123 : 0 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 : ''
})) }))
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 getValidatorPendingTransaction
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: status:
validatorData.validator === validator3 validatorData.validator === validator3
? VALIDATOR_CONFIRMATION_STATUS.PENDING ? VALIDATOR_CONFIRMATION_STATUS.PENDING
: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: validatorData.validator === validator3 ? '0x123' : '', txHash: validatorData.validator === validator3 ? '0x300' : '',
timestamp: validatorData.validator === validator3 ? 123 : 0 timestamp: validatorData.validator === validator3 ? 300 : 0
})) }))
.mockImplementationOnce(() => async (validatorData: BasicConfirmationParam) => ({ .mockImplementationOnce(() => async (validatorData: ConfirmationParam) => ({
validator: validatorData.validator, validator: validatorData.validator,
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
txHash: '', txHash: '',
@ -678,8 +698,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -698,7 +718,7 @@ describe('getConfirmationsForTx', () => {
expect(getValidatorFailedTransaction).toBeCalledTimes(1) expect(getValidatorFailedTransaction).toBeCalledTimes(1)
expect(setFailedConfirmations).toBeCalledTimes(1) expect(setFailedConfirmations).toBeCalledTimes(1)
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(getValidatorPendingTransaction).toBeCalledTimes(1)
expect(setPendingConfirmations).toBeCalledTimes(1) expect(setPendingConfirmations).toBeCalledTimes(1)
@ -712,28 +732,32 @@ describe('getConfirmationsForTx', () => {
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res2).toEqual( expect(res2).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res3).toEqual( expect(res3).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res4).toEqual( expect(res4).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.PENDING, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
@ -746,8 +770,8 @@ describe('getConfirmationsForTx', () => {
setResult, setResult,
requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
getFailedTransactions, getFailedTransactions,
setFailedConfirmations, setFailedConfirmations,
@ -761,14 +785,13 @@ describe('getConfirmationsForTx', () => {
expect(setResult).toBeCalledTimes(7) expect(setResult).toBeCalledTimes(7)
expect(getValidatorConfirmation).toBeCalledTimes(2) expect(getValidatorConfirmation).toBeCalledTimes(2)
expect(getSuccessExecutionTransaction).toBeCalledTimes(2) expect(getSuccessExecutionTransaction).toBeCalledTimes(2)
expect(setSignatureCollected).toBeCalledTimes(3) expect(setSignatureCollected).toBeCalledTimes(2)
expect(setSignatureCollected.mock.calls[0][0]).toEqual(false) expect(setSignatureCollected.mock.calls[0][0]).toEqual(false)
expect(setSignatureCollected.mock.calls[1][0]).toEqual(true) expect(setSignatureCollected.mock.calls[1][0]).toEqual(true)
expect(setSignatureCollected.mock.calls[2][0]).toEqual([signature, signature])
expect(getValidatorFailedTransaction).toBeCalledTimes(2) expect(getValidatorFailedTransaction).toBeCalledTimes(2)
expect(setFailedConfirmations).toBeCalledTimes(2) expect(setFailedConfirmations).toBeCalledTimes(2)
expect(setFailedConfirmations.mock.calls[0][0]).toEqual(false) expect(setFailedConfirmations.mock.calls[0][0]).toEqual(true)
expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false) expect(setFailedConfirmations.mock.calls[1][0]).toEqual(false)
expect(getValidatorPendingTransaction).toBeCalledTimes(1) expect(getValidatorPendingTransaction).toBeCalledTimes(1)
@ -781,23 +804,26 @@ describe('getConfirmationsForTx', () => {
const res7 = setResult.mock.calls[6][0](res6) const res7 = setResult.mock.calls[6][0](res6)
expect(res5).toEqual( expect(res5).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res6).toEqual( expect(res6).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED }
]) ])
) )
expect(res7).toEqual( expect(res7).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 }, { validator: validator1, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x100', timestamp: 100 },
{ validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x123', timestamp: 123 }, { validator: validator2, status: VALIDATOR_CONFIRMATION_STATUS.FAILED, txHash: '0x200', timestamp: 200 },
{ validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x123', timestamp: 123 } { validator: validator3, status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, txHash: '0x300', timestamp: 300 },
{ validator: validator4, status: VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID, txHash: '0x400', timestamp: 400 }
]) ])
) )
}) })

View File

@ -4,7 +4,6 @@ import Web3 from 'web3'
import { getFinalizationEvent } from '../getFinalizationEvent' import { getFinalizationEvent } from '../getFinalizationEvent'
import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants' import { VALIDATOR_CONFIRMATION_STATUS } from '../../config/constants'
const eventName = 'RelayedMessage'
const timestamp = 1594045859 const timestamp = 1594045859
const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908' const validator1 = '0x45b96809336A8b714BFbdAB3E4B5e0fe5d839908'
const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156' const txHash = '0xdab36c9210e7e45fb82af10ffe4960461e41661dce0c9cd36b2843adaa1df156'
@ -20,12 +19,11 @@ const web3 = ({
toChecksumAddress: (a: string) => a toChecksumAddress: (a: string) => a
} }
} as unknown) as Web3 } as unknown) as Web3
const waitingBlocksResolved = true
const message = { const message = {
id: '0x123', id: '0x123',
data: '0x123456789' data: '0x123456789'
} }
const interval = 10000 const isCancelled = () => false
let subscriptions: Array<number> = [] let subscriptions: Array<number> = []
const event = { const event = {
@ -50,7 +48,7 @@ beforeEach(() => {
describe('getFinalizationEvent', () => { describe('getFinalizationEvent', () => {
test('should get finalization event and not try to get failed or pending transactions', async () => { test('should get finalization event and not try to get failed or pending transactions', async () => {
const contract = ({ const contract = ({
getPastEvents: () => { getPastEvents: async () => {
return [event] return [event]
} }
} as unknown) as Contract } as unknown) as Contract
@ -66,13 +64,11 @@ describe('getFinalizationEvent', () => {
await getFinalizationEvent( await getFinalizationEvent(
true, true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
@ -88,10 +84,11 @@ describe('getFinalizationEvent', () => {
expect(setResult).toBeCalledTimes(1) expect(setResult).toBeCalledTimes(1)
expect(setResult.mock.calls[0][0]).toEqual({ expect(setResult.mock.calls[0][0]).toEqual({
validator: validator1, validator: validator1,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
txHash, txHash,
timestamp, timestamp,
executionResult: true executionResult: true,
blockNumber: 5523145
}) })
expect(getFailedExecution).toBeCalledTimes(0) expect(getFailedExecution).toBeCalledTimes(0)
@ -102,7 +99,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 () => { test('should retry to get finalization event and not try to get failed or pending transactions if foreign to home transaction', async () => {
const contract = ({ const contract = ({
getPastEvents: () => { getPastEvents: async () => {
return [] return []
} }
} as unknown) as Contract } as unknown) as Contract
@ -118,13 +115,11 @@ describe('getFinalizationEvent', () => {
await getFinalizationEvent( await getFinalizationEvent(
true, true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
@ -147,7 +142,7 @@ describe('getFinalizationEvent', () => {
}) })
test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => { test('should retry to get finalization event and try to get failed and pending transactions if home to foreign transaction', async () => {
const contract = ({ const contract = ({
getPastEvents: () => { getPastEvents: async () => {
return [] return []
}, },
options: { options: {
@ -170,13 +165,11 @@ describe('getFinalizationEvent', () => {
await getFinalizationEvent( await getFinalizationEvent(
true, true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
@ -199,7 +192,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 () => { 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 = ({ const contract = ({
getPastEvents: () => { getPastEvents: async () => {
return [] return []
}, },
options: { options: {
@ -222,13 +215,11 @@ describe('getFinalizationEvent', () => {
await getFinalizationEvent( await getFinalizationEvent(
true, true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
@ -247,7 +238,8 @@ describe('getFinalizationEvent', () => {
status: VALIDATOR_CONFIRMATION_STATUS.PENDING, status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
txHash, txHash,
timestamp: expect.any(Number), timestamp: expect.any(Number),
executionResult: false executionResult: false,
blockNumber: 0
}) })
expect(getFailedExecution).toBeCalledTimes(0) expect(getFailedExecution).toBeCalledTimes(0)
@ -258,7 +250,7 @@ describe('getFinalizationEvent', () => {
}) })
test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => { test('should retry to get finalization event even if failed transaction found if home to foreign transaction', async () => {
const contract = ({ const contract = ({
getPastEvents: () => { getPastEvents: async () => {
return [] return []
}, },
options: { options: {
@ -281,13 +273,11 @@ describe('getFinalizationEvent', () => {
await getFinalizationEvent( await getFinalizationEvent(
true, true,
contract, contract,
eventName,
web3, web3,
setResult, setResult,
waitingBlocksResolved,
message, message,
interval, subscriptions.push.bind(subscriptions),
subscriptions, isCancelled,
timestamp, timestamp,
collectedSignaturesEvent, collectedSignaturesEvent,
getFailedExecution, getFailedExecution,
@ -306,7 +296,8 @@ describe('getFinalizationEvent', () => {
status: VALIDATOR_CONFIRMATION_STATUS.FAILED, status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
txHash, txHash,
timestamp: expect.any(Number), timestamp: expect.any(Number),
executionResult: false executionResult: false,
blockNumber: expect.any(Number)
}) })
expect(getFailedExecution).toBeCalledTimes(1) expect(getFailedExecution).toBeCalledTimes(1)

View File

@ -1,18 +1,43 @@
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { EventData } from 'web3-eth-contract' import { EventData } from 'web3-eth-contract'
import { SnapshotProvider } from '../services/SnapshotProvider' 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 ( export const getRequiredBlockConfirmations = async (
contract: Contract, contract: Contract,
blockNumber: number, blockNumber: number,
snapshotProvider: SnapshotProvider snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => { ) => {
let blockConfirmations
try {
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
} catch {}
if (blockConfirmations) {
return parseInt(blockConfirmations)
}
const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber) const eventsFromSnapshot = snapshotProvider.requiredBlockConfirmationEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
let contractEvents: EventData[] = [] let contractEvents: EventData[] = []
if (blockNumber > snapshotBlockNumber) { if (blockNumber > snapshotBlockNumber) {
contractEvents = await contract.getPastEvents('RequiredBlockConfirmationChanged', { contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredBlockConfirmationChanged', {
fromBlock: snapshotBlockNumber + 1, fromBlock: snapshotBlockNumber + 1,
toBlock: blockNumber toBlock: blockNumber
}) })
@ -20,16 +45,10 @@ export const getRequiredBlockConfirmations = async (
const events = [...eventsFromSnapshot, ...contractEvents] const events = [...eventsFromSnapshot, ...contractEvents]
let blockConfirmations // Use the value from last event before the transaction
if (events.length > 0) { const event = events[events.length - 1]
// Use the value from last event before the transaction blockConfirmations = event.returnValues.requiredBlockConfirmations
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) return parseInt(blockConfirmations)
} }
@ -37,15 +56,31 @@ export const getValidatorAddress = (contract: Contract) => contract.methods.vali
export const getRequiredSignatures = async ( export const getRequiredSignatures = async (
contract: Contract, contract: Contract,
blockNumber: number, blockNumber: number | 'latest',
snapshotProvider: SnapshotProvider snapshotProvider: SnapshotProvider,
web3: Web3 | null = null,
api: string = ''
) => { ) => {
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 eventsFromSnapshot = snapshotProvider.requiredSignaturesEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
let contractEvents: EventData[] = [] let contractEvents: EventData[] = []
if (blockNumber > snapshotBlockNumber) { if (blockNumber > snapshotBlockNumber) {
contractEvents = await contract.getPastEvents('RequiredSignaturesChanged', { contractEvents = await getPastEventsWithFallback(api, web3, contract, 'RequiredSignaturesChanged', {
fromBlock: snapshotBlockNumber + 1, fromBlock: snapshotBlockNumber + 1,
toBlock: blockNumber toBlock: blockNumber
}) })
@ -55,11 +90,25 @@ export const getRequiredSignatures = async (
// Use the value form last event before the transaction // Use the value form last event before the transaction
const event = events[events.length - 1] const event = events[events.length - 1]
const { requiredSignatures } = event.returnValues ;({ requiredSignatures } = event.returnValues)
return parseInt(requiredSignatures) return parseInt(requiredSignatures)
} }
export const getValidatorList = async (contract: Contract, blockNumber: number, snapshotProvider: SnapshotProvider) => { 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 {}
const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber) const addedEventsFromSnapshot = snapshotProvider.validatorAddedEvents(blockNumber)
const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber) const removedEventsFromSnapshot = snapshotProvider.validatorRemovedEvents(blockNumber)
const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber() const snapshotBlockNumber = snapshotProvider.snapshotBlockNumber()
@ -67,10 +116,10 @@ export const getValidatorList = async (contract: Contract, blockNumber: number,
const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber const fromBlock = snapshotBlockNumber > blockNumber ? snapshotBlockNumber + 1 : blockNumber
const [currentList, added, removed] = await Promise.all([ const [currentList, added, removed] = await Promise.all([
contract.methods.validatorList().call(), contract.methods.validatorList().call(),
contract.getPastEvents('ValidatorAdded', { getPastEventsWithFallback(api, web3, contract, 'ValidatorAdded', {
fromBlock fromBlock
}), }),
contract.getPastEvents('ValidatorRemoved', { getPastEventsWithFallback(api, web3, contract, 'ValidatorRemoved', {
fromBlock fromBlock
}) })
]) ])

View File

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

View File

@ -7,8 +7,12 @@ import {
MAX_TX_SEARCH_BLOCK_RANGE, MAX_TX_SEARCH_BLOCK_RANGE,
SUBMIT_SIGNATURE_HASH SUBMIT_SIGNATURE_HASH
} from '../config/constants' } from '../config/constants'
import { AbiItem } from 'web3-utils'
import Web3 from 'web3'
import { Contract } from 'web3-eth-contract'
export interface APITransaction { export interface APITransaction {
from: string
timeStamp: string timeStamp: string
isError: string isError: string
input: string input: string
@ -47,16 +51,21 @@ export interface GetTransactionParams extends GetPendingTransactionParams {
} }
export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => { export const fetchAccountTransactions = async ({ account, startBlock, endBlock, api }: AccountTransactionsParams) => {
const params = `module=account&action=txlist&address=${account}&filterby=from&startblock=${startBlock}&endblock=${endBlock}` const url = new URL(api)
const url = api.includes('blockscout') ? `${api}?${params}` : `${api}&${params}` 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())
const result = await fetch(url).then(res => res.json()) const result = await fetch(url.toString()).then(res => res.json())
if (result.message === 'No transactions found') { if (result.message === 'No transactions found') {
return [] return []
} }
return result.result return result.result || []
} }
export const fetchPendingTransactions = async ({ export const fetchPendingTransactions = async ({
@ -66,10 +75,13 @@ export const fetchPendingTransactions = async ({
if (!api.includes('blockscout')) { if (!api.includes('blockscout')) {
return [] return []
} }
const url = `${api}?module=account&action=pendingtxlist&address=${account}` const url = new URL(api)
url.searchParams.append('module', 'account')
url.searchParams.append('action', 'pendingtxlist')
url.searchParams.append('address', account)
try { try {
const result = await fetch(url).then(res => res.json()) const result = await fetch(url.toString()).then(res => res.json())
if (result.status === '0') { if (result.status === '0') {
return [] return []
} }
@ -85,9 +97,13 @@ export const getClosestBlockByTimestamp = async (api: string, timestamp: number)
throw new Error('Blockscout does not support getblocknobytime') throw new Error('Blockscout does not support getblocknobytime')
} }
const url = `${api}&module=block&action=getblocknobytime&timestamp=${timestamp}&closest=before` 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).then(res => res.json()) const blockNumber = await fetch(url.toString()).then(res => res.json())
return parseInt(blockNumber.result) return parseInt(blockNumber.result)
} }
@ -144,7 +160,44 @@ export const getAccountTransactions = async ({
return transactionsCache[key].transactions return transactionsCache[key].transactions
} }
const filterReceiver = (to: string) => (tx: APITransaction) => tx.to.toLowerCase() === to.toLowerCase() 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 ( export const getFailedTransactions = async (
account: string, account: string,
@ -154,9 +207,9 @@ export const getFailedTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api }) const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
return transactions.filter(t => t.isError !== '0').filter(filterReceiver(to)) return transactions.filter(t => t.isError !== '0').filter(filterSender(account))
} }
export const getSuccessTransactions = async ( export const getSuccessTransactions = async (
@ -167,9 +220,9 @@ export const getSuccessTransactions = async (
api: string, api: string,
getAccountTransactionsMethod = getAccountTransactions getAccountTransactionsMethod = getAccountTransactions
): Promise<APITransaction[]> => { ): Promise<APITransaction[]> => {
const transactions = await getAccountTransactionsMethod({ account, startBlock, endBlock, api }) const transactions = await getAccountTransactionsMethod({ account: to, startBlock, endBlock, api })
return transactions.filter(t => t.isError === '0').filter(filterReceiver(to)) return transactions.filter(t => t.isError === '0').filter(filterSender(account))
} }
export const filterValidatorSignatureTransaction = ( export const filterValidatorSignatureTransaction = (

View File

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

View File

@ -2,26 +2,37 @@ import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { HOME_RPC_POLLING_INTERVAL, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer' import { GetTransactionParams, APITransaction, APIPendingTransaction, GetPendingTransactionParams } from './explorer'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
import { import {
getValidatorConfirmation, getValidatorConfirmation,
getValidatorFailedTransaction, getValidatorFailedTransaction,
getValidatorPendingTransaction, getValidatorPendingTransaction,
getSuccessExecutionTransaction getSuccessExecutionTransaction
} from './validatorConfirmationHelpers' } from './validatorConfirmationHelpers'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import { signatureToVRS } from './signatures'
const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfirmations: BasicConfirmationParam[]) => { const mergeConfirmations = (oldConfirmations: ConfirmationParam[], newConfirmations: ConfirmationParam[]) => {
const confirmations = [...oldConfirmations] const confirmations = [...oldConfirmations]
newConfirmations.forEach(validatorData => { newConfirmations.forEach(validatorData => {
const index = confirmations.findIndex(e => e.validator === validatorData.validator) const index = confirmations.findIndex(e => e.validator === validatorData.validator)
if (index === -1) {
confirmations.push(validatorData)
return
}
const currentStatus = confirmations[index].status const currentStatus = confirmations[index].status
const newStatus = validatorData.status const newStatus = validatorData.status
if ( if (
(validatorData as ConfirmationParam).txHash || validatorData.txHash ||
!!validatorData.signature ||
(newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED) (newStatus !== currentStatus && newStatus !== VALIDATOR_CONFIRMATION_STATUS.UNDEFINED)
) { ) {
confirmations[index] = validatorData 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 return confirmations
@ -29,15 +40,15 @@ const mergeConfirmations = (oldConfirmations: BasicConfirmationParam[], newConfi
export const getConfirmationsForTx = async ( export const getConfirmationsForTx = async (
messageData: string, messageData: string,
web3: Maybe<Web3>, web3: Web3,
validatorList: string[], validatorList: string[],
bridgeContract: Maybe<Contract>, bridgeContract: Contract,
fromHome: boolean, fromHome: boolean,
setResult: Function, setResult: Function,
requiredSignatures: number, requiredSignatures: number,
setSignatureCollected: Function, setSignatureCollected: Function,
waitingBlocksResolved: boolean, setTimeoutId: (timeoutId: number) => void,
subscriptions: number[], isCancelled: () => boolean,
startBlock: number, startBlock: number,
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>, getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>,
setFailedConfirmations: Function, setFailedConfirmations: Function,
@ -45,21 +56,17 @@ export const getConfirmationsForTx = async (
setPendingConfirmations: Function, setPendingConfirmations: Function,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => { ) => {
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const hashMsg = web3.utils.soliditySha3Raw(messageData) const hashMsg = web3.utils.soliditySha3Raw(messageData)
let validatorConfirmations = await Promise.all( let validatorConfirmations = await Promise.all(
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod)) validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, fromHome))
) )
const updateConfirmations = (confirmations: BasicConfirmationParam[]) => { const updateConfirmations = (confirmations: ConfirmationParam[]) => {
if (confirmations.length === 0) { if (confirmations.length === 0) {
return return
} }
validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations) validatorConfirmations = mergeConfirmations(validatorConfirmations, confirmations)
setResult((currentConfirmations: BasicConfirmationParam[]) => { setResult((currentConfirmations: ConfirmationParam[]) => {
if (currentConfirmations && currentConfirmations.length) { if (currentConfirmations && currentConfirmations.length) {
return mergeConfirmations(currentConfirmations, confirmations) return mergeConfirmations(currentConfirmations, confirmations)
} }
@ -69,62 +76,24 @@ export const getConfirmationsForTx = async (
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
const notSuccessConfirmations = 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 const hasEnoughSignatures = successConfirmations.length >= requiredSignatures
updateConfirmations(validatorConfirmations) updateConfirmations(validatorConfirmations)
setSignatureCollected(hasEnoughSignatures) setSignatureCollected(hasEnoughSignatures)
// If signatures not collected, look for pending transactions
if (!hasEnoughSignatures) {
// 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)
} else {
setPendingConfirmations(false)
}
const undefinedConfirmations = validatorConfirmations.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
)
// Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map(
getValidatorFailedTransaction(bridgeContract, messageData, startBlock, getFailedTransactions)
)
)
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
)
setFailedConfirmations(validatorFailedConfirmations.length > validatorList.length - requiredSignatures)
updateConfirmations(validatorFailedConfirmations)
const missingConfirmations = validatorConfirmations.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
)
if (hasEnoughSignatures) { if (hasEnoughSignatures) {
// If signatures collected, it should set other signatures not found as not required setPendingConfirmations(false)
const notRequiredConfirmations = missingConfirmations.map(c => ({
validator: c.validator,
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
}))
updateConfirmations(notRequiredConfirmations)
if (fromHome) { if (fromHome) {
// fetch collected signatures for possible manual processing // fetch collected signatures for possible manual processing
setSignatureCollected( const signatures = await Promise.all(
await Promise.all( Array.from(Array(requiredSignatures).keys()).map(i => bridgeContract.methods.signature(hashMsg, i).call())
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 }))
})
updateConfirmations(confirmations)
} }
} }
@ -138,34 +107,95 @@ export const getConfirmationsForTx = async (
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '') const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
updateConfirmations(successConfirmationWithTxFound) updateConfirmations(successConfirmationWithTxFound)
// If signatures not collected, look for pending transactions
if (!hasEnoughSignatures) {
// 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)
}
const undefinedConfirmations = validatorConfirmations.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
)
// Check if confirmation failed
const validatorFailedConfirmationsChecks = await Promise.all(
undefinedConfirmations.map(
getValidatorFailedTransaction(web3, bridgeContract, messageData, startBlock, getFailedTransactions)
)
)
let validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED || c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED_VALID
)
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
}
)
}
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 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: ''
}))
updateConfirmations(notRequiredConfirmations)
}
// retry if not all signatures are collected and some confirmations are still missing // retry if not all signatures are collected and some confirmations are still missing
// or some success transactions were not fetched successfully // or some success transactions were not fetched successfully
if ( if (
(!hasEnoughSignatures && missingConfirmations.length > 0) || (!hasEnoughSignatures && missingConfirmations.length > 0) ||
successConfirmationWithTxFound.length < successConfirmationWithData.length successConfirmationWithTxFound.length < successConfirmationWithData.length
) { ) {
const timeoutId = setTimeout( if (!isCancelled()) {
() => const timeoutId = setTimeout(
getConfirmationsForTx( () =>
messageData, getConfirmationsForTx(
web3, messageData,
validatorList, web3,
bridgeContract, validatorList,
fromHome, bridgeContract,
setResult, fromHome,
requiredSignatures, setResult,
setSignatureCollected, requiredSignatures,
waitingBlocksResolved, setSignatureCollected,
subscriptions, setTimeoutId,
startBlock, isCancelled,
getFailedTransactions, startBlock,
setFailedConfirmations, getFailedTransactions,
getPendingTransactions, setFailedConfirmations,
setPendingConfirmations, getPendingTransactions,
getSuccessTransactions setPendingConfirmations,
), getSuccessTransactions
HOME_RPC_POLLING_INTERVAL ),
) HOME_RPC_POLLING_INTERVAL
subscriptions.push(timeoutId) )
setTimeoutId(timeoutId)
}
} }
} }

View File

@ -1,16 +1,47 @@
import { Contract, EventData } from 'web3-eth-contract' import { Contract, EventData } from 'web3-eth-contract'
import Web3 from 'web3' import Web3 from 'web3'
import { CACHE_KEY_EXECUTION_FAILED, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import {
CACHE_KEY_EXECUTION_FAILED,
FOREIGN_EXPLORER_API,
FOREIGN_RPC_POLLING_INTERVAL,
HOME_EXPLORER_API,
HOME_RPC_POLLING_INTERVAL,
VALIDATOR_CONFIRMATION_STATUS
} from '../config/constants'
import { ExecutionData } from '../hooks/useMessageConfirmations' import { ExecutionData } from '../hooks/useMessageConfirmations'
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer' import {
APIPendingTransaction,
APITransaction,
GetTransactionParams,
GetPendingTransactionParams,
getLogs
} from './explorer'
import { getBlock, MessageObject } from './web3' import { getBlock, MessageObject } from './web3'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider' import { foreignBlockNumberProvider, homeBlockNumberProvider } from '../services/BlockNumberProvider'
export const getSuccessExecutionData = async (contract: Contract, eventName: string, web3: Web3, messageId: string) => { const getPastEventsWithFallback = (api: string, web3: Web3, contract: Contract, eventName: string, options: any) =>
contract.getPastEvents(eventName, options).catch(
() =>
api
? getLogs(api, web3, contract, eventName, {
fromBlock: options.fromBlock,
toBlock: options.toBlock,
topics: [null, null, options.filter.messageId]
})
: []
)
export const getSuccessExecutionData = async (
contract: Contract,
eventName: string,
web3: Web3,
messageId: string,
api: string = ''
) => {
// Since it filters by the message id, only one event will be fetched // 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 // so there is no need to limit the range of the block to reduce the network traffic
const events: EventData[] = await contract.getPastEvents(eventName, { const events: EventData[] = await getPastEventsWithFallback(api, web3, contract, eventName, {
fromBlock: 0, fromBlock: 0,
toBlock: 'latest', toBlock: 'latest',
filter: { filter: {
@ -28,11 +59,12 @@ export const getSuccessExecutionData = async (contract: Contract, eventName: str
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from) const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS, status: VALIDATOR_CONFIRMATION_STATUS.EXECUTION_SUCCESS,
validator: validatorAddress, validator: validatorAddress,
txHash: event.transactionHash, txHash: event.transactionHash,
timestamp: blockTimestamp, timestamp: blockTimestamp,
executionResult: event.returnValues.status executionResult: event.returnValues.status,
blockNumber: event.blockNumber
} }
} }
return null return null
@ -40,14 +72,12 @@ export const getSuccessExecutionData = async (contract: Contract, eventName: str
export const getFinalizationEvent = async ( export const getFinalizationEvent = async (
fromHome: boolean, fromHome: boolean,
contract: Maybe<Contract>, contract: Contract,
eventName: string, web3: Web3,
web3: Maybe<Web3>,
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>, setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
waitingBlocksResolved: boolean,
message: MessageObject, message: MessageObject,
interval: number, setTimeoutId: (timeoutId: number) => void,
subscriptions: number[], isCancelled: () => boolean,
startBlock: number, startBlock: number,
collectedSignaturesEvent: Maybe<EventData>, collectedSignaturesEvent: Maybe<EventData>,
getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>, getFailedExecution: (args: GetTransactionParams) => Promise<APITransaction[]>,
@ -56,8 +86,11 @@ export const getFinalizationEvent = async (
setPendingExecution: Function, setPendingExecution: Function,
setExecutionEventsFetched: Function setExecutionEventsFetched: Function
) => { ) => {
if (!contract || !web3 || !waitingBlocksResolved) return const eventName = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id) const api = fromHome ? FOREIGN_EXPLORER_API : HOME_EXPLORER_API
const successExecutionData = await getSuccessExecutionData(contract, eventName, web3, message.id, api)
if (successExecutionData) { if (successExecutionData) {
setResult(successExecutionData) setResult(successExecutionData)
} else { } else {
@ -83,7 +116,8 @@ export const getFinalizationEvent = async (
validator: validator, validator: validator,
txHash: pendingTx.hash, txHash: pendingTx.hash,
timestamp: nowTimestamp, timestamp: nowTimestamp,
executionResult: false executionResult: false,
blockNumber: 0
}) })
setPendingExecution(true) setPendingExecution(true)
} else { } else {
@ -112,7 +146,8 @@ export const getFinalizationEvent = async (
validator: validator, validator: validator,
txHash: failedTx.hash, txHash: failedTx.hash,
timestamp, timestamp,
executionResult: false executionResult: false,
blockNumber: parseInt(failedTx.blockNumber)
}) })
setFailedExecution(true) setFailedExecution(true)
} }
@ -120,28 +155,28 @@ export const getFinalizationEvent = async (
} }
} }
const timeoutId = setTimeout( if (!isCancelled()) {
() => const timeoutId = setTimeout(
getFinalizationEvent( () =>
fromHome, getFinalizationEvent(
contract, fromHome,
eventName, contract,
web3, web3,
setResult, setResult,
waitingBlocksResolved, message,
message, setTimeoutId,
interval, isCancelled,
subscriptions, startBlock,
startBlock, collectedSignaturesEvent,
collectedSignaturesEvent, getFailedExecution,
getFailedExecution, setFailedExecution,
setFailedExecution, getPendingExecution,
getPendingExecution, setPendingExecution,
setPendingExecution, setExecutionEventsFetched
setExecutionEventsFetched ),
), fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
interval )
) setTimeoutId(timeoutId)
subscriptions.push(timeoutId) }
} }
} }

View File

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

View File

@ -1,38 +1,45 @@
import Web3 from 'web3' import Web3 from 'web3'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations' import { ConfirmationParam } from '../hooks/useMessageConfirmations'
import validatorsCache from '../services/ValidatorsCache' import validatorsCache from '../services/ValidatorsCache'
import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { CACHE_KEY_FAILED, CACHE_KEY_SUCCESS, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer' import { APIPendingTransaction, APITransaction, GetTransactionParams, GetPendingTransactionParams } from './explorer'
import { homeBlockNumberProvider } from '../services/BlockNumberProvider' import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
import { getAffirmationsSigned, getMessagesSigned } from './contract'
export const getValidatorConfirmation = ( export const getValidatorConfirmation = (
web3: Web3, web3: Web3,
hashMsg: string, hashMsg: string,
bridgeContract: Contract, bridgeContract: Contract,
confirmationContractMethod: Function fromHome: boolean
) => async (validator: string): Promise<BasicConfirmationParam> => { ) => async (validator: string): Promise<ConfirmationParam> => {
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg) const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
const signatureFromCache = validatorsCache.get(hashSenderMsg) const fromCache = validatorsCache.getData(hashSenderMsg)
if (signatureFromCache) { if (fromCache) {
return { return fromCache
validator,
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
}
} }
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg) 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 validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
if (confirmed) { if (confirmed) {
validatorsCache.set(hashSenderMsg, confirmed) const confirmation: ConfirmationParam = {
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
validator,
timestamp: 0,
txHash: ''
}
validatorsCache.setData(hashSenderMsg, confirmation)
return confirmation
} }
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator, validator,
status timestamp: 0,
txHash: ''
} }
} }
@ -43,7 +50,7 @@ export const getSuccessExecutionTransaction = (
messageData: string, messageData: string,
startBlock: number, startBlock: number,
getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getSuccessTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
const fromCache = validatorsCache.getData(validatorCacheKey) const fromCache = validatorsCache.getData(validatorCacheKey)
@ -87,11 +94,12 @@ export const getSuccessExecutionTransaction = (
} }
export const getValidatorFailedTransaction = ( export const getValidatorFailedTransaction = (
web3: Web3,
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
startBlock: number, startBlock: number,
getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]> getFailedTransactions: (args: GetTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}` const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
const failedFromCache = validatorsCache.getData(validatorCacheKey) const failedFromCache = validatorsCache.getData(validatorCacheKey)
@ -106,30 +114,33 @@ export const getValidatorFailedTransaction = (
startBlock, startBlock,
endBlock: homeBlockNumberProvider.get() || 0 endBlock: homeBlockNumberProvider.get() || 0
}) })
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 validator signature failed, we cache the result to avoid doing future requests for a result that won't change
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
const failedTx = failedTransactions[0] const failedTx = failedTransactions[0]
txHashTimestamp = parseInt(failedTx.timeStamp) const confirmation: ConfirmationParam = {
txHash = failedTx.hash status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
validatorsCache.setData(validatorCacheKey, {
validator: validatorData.validator, validator: validatorData.validator,
status: newStatus, txHash: failedTx.hash,
txHash, timestamp: parseInt(failedTx.timeStamp)
timestamp: txHashTimestamp }
})
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
} }
return { return {
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
validator: validatorData.validator, validator: validatorData.validator,
status: newStatus, txHash: '',
txHash, timestamp: 0
timestamp: txHashTimestamp
} }
} }
@ -137,7 +148,7 @@ export const getValidatorPendingTransaction = (
bridgeContract: Contract, bridgeContract: Contract,
messageData: string, messageData: string,
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]> getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => { ) => async (validatorData: ConfirmationParam): Promise<ConfirmationParam> => {
const failedTransactions = await getPendingTransactions({ const failedTransactions = await getPendingTransactions({
account: validatorData.validator, account: validatorData.validator,
to: bridgeContract.options.address, to: bridgeContract.options.address,

View File

@ -10,6 +10,37 @@ import { SnapshotProvider } from '../services/SnapshotProvider'
export interface MessageObject { export interface MessageObject {
id: string id: string
data: 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)) const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
@ -26,15 +57,33 @@ export const filterEventsByAbi = (
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi) const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash) 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 => { return events.map(e => {
let decodedLogs: { [p: string]: string } = { const { messageId, encodedData } = web3.eth.abi.decodeLog(inputs, e.data, [e.topics[1]])
messageId: '', let sender, executor, obToken, obReceiver
encodedData: '' 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)}`
}
} }
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) { return {
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]]) id: messageId || '',
data: encodedData || '',
sender,
executor,
obToken,
obReceiver
} }
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
}) })
} }

View File

@ -1,13 +1,7 @@
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 HOME_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/HomeBridgeErcToNative').abi
const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/ForeignBridgeErcToNative').abi const FOREIGN_ERC_TO_NATIVE_ABI = require('../contracts/build/contracts/XDaiForeignBridge.json').abi
const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi const ERC20_ABI = require('../contracts/build/contracts/ERC20').abi
const ERC677_ABI = require('../contracts/build/contracts/ERC677').abi const BLOCK_REWARD_ABI = require('../contracts/build/contracts/BlockRewardMock').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 BRIDGE_VALIDATORS_ABI = require('../contracts/build/contracts/BridgeValidators').abi
const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/RewardableValidators').abi const REWARDABLE_VALIDATORS_ABI = require('../contracts/build/contracts/RewardableValidators').abi
const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi const HOME_AMB_ABI = require('../contracts/build/contracts/HomeAMB').abi
@ -15,43 +9,9 @@ const FOREIGN_AMB_ABI = require('../contracts/build/contracts/ForeignAMB').abi
const BOX_ABI = require('../contracts/build/contracts/Box').abi const BOX_ABI = require('../contracts/build/contracts/Box').abi
const HOME_AMB_ERC_TO_ERC_ABI = require('../contracts/build/contracts/HomeAMBErc677ToErc677').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 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 { 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 = [ const OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI = [
{ {
anonymous: false, anonymous: false,
@ -85,27 +45,15 @@ const OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI = [
function getBridgeABIs(bridgeMode) { function getBridgeABIs(bridgeMode) {
let HOME_ABI = null let HOME_ABI = null
let FOREIGN_ABI = null let FOREIGN_ABI = null
if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) { if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) {
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 HOME_ABI = HOME_ERC_TO_NATIVE_ABI
FOREIGN_ABI = FOREIGN_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) { } else if (bridgeMode === BRIDGE_MODES.ARBITRARY_MESSAGE) {
HOME_ABI = HOME_AMB_ABI HOME_ABI = HOME_AMB_ABI
FOREIGN_ABI = FOREIGN_AMB_ABI FOREIGN_ABI = FOREIGN_AMB_ABI
} else if (bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC) { } else if (bridgeMode === BRIDGE_MODES.AMB_ERC_TO_ERC) {
HOME_ABI = HOME_AMB_ERC_TO_ERC_ABI HOME_ABI = HOME_AMB_ERC_TO_ERC_ABI
FOREIGN_ABI = FOREIGN_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 { } else {
throw new Error(`Unrecognized bridge mode: ${bridgeMode}`) throw new Error(`Unrecognized bridge mode: ${bridgeMode}`)
} }
@ -115,26 +63,15 @@ function getBridgeABIs(bridgeMode) {
module.exports = { module.exports = {
getBridgeABIs, 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, HOME_ERC_TO_NATIVE_ABI,
FOREIGN_ERC_TO_NATIVE_ABI, FOREIGN_ERC_TO_NATIVE_ABI,
ERC20_ABI, ERC20_ABI,
ERC677_ABI,
ERC677_BRIDGE_TOKEN_ABI,
BLOCK_REWARD_ABI, BLOCK_REWARD_ABI,
BRIDGE_VALIDATORS_ABI, BRIDGE_VALIDATORS_ABI,
REWARDABLE_VALIDATORS_ABI, REWARDABLE_VALIDATORS_ABI,
HOME_V1_ABI,
FOREIGN_V1_ABI,
ERC20_BYTES32_ABI,
HOME_AMB_ABI, HOME_AMB_ABI,
FOREIGN_AMB_ABI, FOREIGN_AMB_ABI,
OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI, OLD_AMB_USER_REQUEST_FOR_AFFIRMATION_ABI,
OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI, OLD_AMB_USER_REQUEST_FOR_SIGNATURE_ABI,
BOX_ABI, BOX_ABI
HOME_STAKE_ERC_TO_ERC_ABI,
FOREIGN_STAKE_ERC_TO_ERC_ABI
} }

View File

@ -1,30 +1,12 @@
const BRIDGE_MODES = { const BRIDGE_MODES = {
NATIVE_TO_ERC: 'NATIVE_TO_ERC',
ERC_TO_ERC: 'ERC_TO_ERC',
ERC_TO_NATIVE: 'ERC_TO_NATIVE', ERC_TO_NATIVE: 'ERC_TO_NATIVE',
NATIVE_TO_ERC_V1: 'NATIVE_TO_ERC_V1',
ARBITRARY_MESSAGE: 'ARBITRARY_MESSAGE', 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' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
module.exports = { module.exports = {
BRIDGE_MODES, BRIDGE_MODES,
ERC_TYPES,
FEE_MANAGER_MODE,
ZERO_ADDRESS ZERO_ADDRESS
} }

View File

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

View File

@ -8,8 +8,10 @@
"test": "NODE_ENV=test mocha" "test": "NODE_ENV=test mocha"
}, },
"dependencies": { "dependencies": {
"@mycrypto/gas-estimation": "^1.1.0",
"gas-price-oracle": "^0.1.5", "gas-price-oracle": "^0.1.5",
"web3-utils": "1.0.0-beta.34" "web3-utils": "^1.3.0",
"node-fetch": "^2.1.2"
}, },
"devDependencies": { "devDependencies": {
"bn-chai": "^1.0.1", "bn-chai": "^1.0.1",

View File

@ -1,12 +1,8 @@
const { expect } = require('chai') const { expect } = require('chai')
const { BRIDGE_MODES, ERC_TYPES } = require('../constants') const { BRIDGE_MODES } = require('../constants')
describe('constants', () => { describe('constants', () => {
it('should contain correct number of bridge types', () => { it('should contain correct number of bridge types', () => {
expect(Object.keys(BRIDGE_MODES).length).to.be.equal(7) expect(Object.keys(BRIDGE_MODES).length).to.be.equal(3)
})
it('should contain correct number of erc types', () => {
expect(Object.keys(ERC_TYPES).length).to.be.equal(2)
}) })
}) })

View File

@ -1,159 +0,0 @@
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)
})
})

View File

@ -1,22 +1,18 @@
const { toWei, toBN } = require('web3-utils') const { toWei, toBN, BN } = require('web3-utils')
const { GasPriceOracle } = require('gas-price-oracle') const { GasPriceOracle } = require('gas-price-oracle')
const { BRIDGE_MODES, FEE_MANAGER_MODE, ERC_TYPES } = require('./constants') const { estimateFees } = require('@mycrypto/gas-estimation')
const fetch = require('node-fetch')
const { BRIDGE_MODES } = require('./constants')
const { REWARDABLE_VALIDATORS_ABI } = require('./abis') const { REWARDABLE_VALIDATORS_ABI } = require('./abis')
const gasPriceOracle = new GasPriceOracle() const gasPriceOracle = new GasPriceOracle()
function decodeBridgeMode(bridgeModeHash) { function decodeBridgeMode(bridgeModeHash) {
switch (bridgeModeHash) { switch (bridgeModeHash) {
case '0x92a8d7fe':
return BRIDGE_MODES.NATIVE_TO_ERC
case '0xba4690f5':
return BRIDGE_MODES.ERC_TO_ERC
case '0x18762d46': case '0x18762d46':
return BRIDGE_MODES.ERC_TO_NATIVE return BRIDGE_MODES.ERC_TO_NATIVE
case '0x2544fbb9': case '0x2544fbb9':
return BRIDGE_MODES.ARBITRARY_MESSAGE return BRIDGE_MODES.ARBITRARY_MESSAGE
case '0x16ea01e9':
return BRIDGE_MODES.STAKE_AMB_ERC_TO_ERC
case '0x76595b56': case '0x76595b56':
return BRIDGE_MODES.AMB_ERC_TO_ERC return BRIDGE_MODES.AMB_ERC_TO_ERC
default: default:
@ -24,80 +20,9 @@ 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) { async function getBridgeMode(contract) {
try { const bridgeModeHash = await contract.methods.getBridgeMode().call()
const bridgeModeHash = await contract.methods.getBridgeMode().call() return decodeBridgeMode(bridgeModeHash)
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 => { const parseValidatorEvent = event => {
@ -150,11 +75,8 @@ const tryCall = async (method, fallbackValue) => {
const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0) const getDeployedAtBlock = async contract => tryCall(contract.methods.deployedAtBlock(), 0)
const getPastEvents = async ( const getPastEventsOrSplit = async (contract, { event, fromBlock, toBlock, options }) => {
contract, let events = []
{ event = 'allEvents', fromBlock = toBN(0), toBlock = 'latest', options = {} }
) => {
let events
try { try {
events = await contract.getPastEvents(event, { events = await contract.getPastEvents(event, {
...options, ...options,
@ -162,19 +84,19 @@ const getPastEvents = async (
toBlock toBlock
}) })
} catch (e) { } catch (e) {
if (e.message.includes('query returned more than') && toBlock !== 'latest') { if (e.message.includes('query returned more than') || e.message.toLowerCase().includes('timeout')) {
const middle = toBN(fromBlock) const middle = toBN(fromBlock)
.add(toBN(toBlock)) .add(toBN(toBlock))
.divRound(toBN(2)) .divRound(toBN(2))
const middlePlusOne = middle.add(toBN(1)) const middlePlusOne = middle.add(toBN(1))
const firstHalfEvents = await getPastEvents(contract, { const firstHalfEvents = await getPastEventsOrSplit(contract, {
options, options,
event, event,
fromBlock, fromBlock,
toBlock: middle toBlock: middle
}) })
const secondHalfEvents = await getPastEvents(contract, { const secondHalfEvents = await getPastEventsOrSplit(contract, {
options, options,
event, event,
fromBlock: middlePlusOne, fromBlock: middlePlusOne,
@ -188,6 +110,31 @@ const getPastEvents = async (
return events 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) => { const getValidatorList = async (address, eth, options) => {
options.logger && options.logger.debug && options.logger.debug('getting validatorList') options.logger && options.logger.debug && options.logger.debug('getting validatorList')
@ -230,20 +177,27 @@ const gasPriceWithinLimits = (gasPrice, limits) => {
const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => { const normalizeGasPrice = (oracleGasPrice, factor, limits = null) => {
let gasPrice = oracleGasPrice * factor let gasPrice = oracleGasPrice * factor
gasPrice = gasPriceWithinLimits(gasPrice, limits) gasPrice = gasPriceWithinLimits(gasPrice, limits)
return toBN(toWei(gasPrice.toFixed(2).toString(), 'gwei')) return toWei(gasPrice.toFixed(2).toString(), 'gwei')
} }
// fetchFn has to be supplied (instead of just url to oracle), const gasPriceFromSupplier = async (web3, url, options = {}) => {
// because this utility function is shared between Browser and Node,
// we use built-in 'fetch' on browser side, and `node-fetch` package in Node.
const gasPriceFromSupplier = async (fetchFn, options = {}) => {
try { try {
let json let json
if (fetchFn) { if (url === 'eip1559-gas-estimation') {
const response = await fetchFn() 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 })
json = await response.json() json = await response.json()
} else { } else {
json = await gasPriceOracle.fetchGasPricesOffChain() return null
} }
const oracleGasPrice = json[options.speedType] const oracleGasPrice = json[options.speedType]
@ -260,7 +214,7 @@ const gasPriceFromSupplier = async (fetchFn, options = {}) => {
options.logger.debug && options.logger.debug &&
options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API') options.logger.debug({ oracleGasPrice, normalizedGasPrice }, 'Gas price updated using the API')
return normalizedGasPrice return { gasPrice: normalizedGasPrice }
} catch (e) { } catch (e) {
options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`) options.logger && options.logger.error && options.logger.error(`Gas Price API is not available. ${e.message}`)
} }
@ -269,11 +223,11 @@ const gasPriceFromSupplier = async (fetchFn, options = {}) => {
const gasPriceFromContract = async (bridgeContract, options = {}) => { const gasPriceFromContract = async (bridgeContract, options = {}) => {
try { try {
const gasPrice = await bridgeContract.methods.gasPrice().call() const gasPrice = (await bridgeContract.methods.gasPrice().call()).toString()
options.logger && options.logger &&
options.logger.debug && options.logger.debug &&
options.logger.debug({ gasPrice }, 'Gas price updated using the contracts') options.logger.debug({ gasPrice }, 'Gas price updated using the contracts')
return gasPrice return { gasPrice }
} catch (e) { } catch (e) {
options.logger && options.logger &&
options.logger.error && options.logger.error &&
@ -284,10 +238,7 @@ const gasPriceFromContract = async (bridgeContract, options = {}) => {
module.exports = { module.exports = {
decodeBridgeMode, decodeBridgeMode,
decodeFeeManagerMode,
getBridgeMode, getBridgeMode,
getTokenType,
getUnit,
parseValidatorEvent, parseValidatorEvent,
processValidatorsEvents, processValidatorsEvents,
getValidatorList, getValidatorList,
@ -296,7 +247,5 @@ module.exports = {
normalizeGasPrice, normalizeGasPrice,
gasPriceFromSupplier, gasPriceFromSupplier,
gasPriceFromContract, gasPriceFromContract,
gasPriceWithinLimits, gasPriceWithinLimits
isErcToErcMode,
isMediatorMode
} }

View File

@ -1,191 +0,0 @@
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'
}
]
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'
}
]
module.exports = {
HOME_V1_ABI: homeV1Abi,
FOREIGN_V1_ABI: foreignViAbi
}

@ -1 +1 @@
Subproject commit 835742dfd8f1c869d4e7b61582155d250d6cf094 Subproject commit 908a48107919d4ab127f9af07d44d47eac91547e

View File

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

View File

@ -16,7 +16,6 @@ platforms:
children: children:
- oracle - oracle
- monitor - monitor
- ui
image: ubuntu:16.04 image: ubuntu:16.04
privileged: true privileged: true
network_mode: host network_mode: host
@ -35,7 +34,7 @@ provisioner:
inventory: inventory:
host_vars: host_vars:
multiple-host: multiple-host:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "6c48435bd464a53ed66ed62127c4dba8af75cf1a99a8ebe2680599948fbfbc6d"
MONITOR_PORT: 3003 MONITOR_PORT: 3003
syslog_server_port: "udp://127.0.0.1:514" syslog_server_port: "udp://127.0.0.1:514"
verifier: verifier:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,12 +9,15 @@
- oracle_net_db_bridge_request - oracle_net_db_bridge_request
- oracle_net_db_bridge_collected - oracle_net_db_bridge_collected
- oracle_net_db_bridge_affirmation - oracle_net_db_bridge_affirmation
- oracle_net_db_bridge_information
- oracle_net_db_bridge_transfer - oracle_net_db_bridge_transfer
- oracle_net_db_bridge_senderhome - oracle_net_db_bridge_senderhome
- oracle_net_db_bridge_senderforeign - oracle_net_db_bridge_senderforeign
- oracle_net_db_bridge_shutdown
- oracle_net_rabbit_bridge_request - oracle_net_rabbit_bridge_request
- oracle_net_rabbit_bridge_collected - oracle_net_rabbit_bridge_collected
- oracle_net_rabbit_bridge_affirmation - oracle_net_rabbit_bridge_affirmation
- oracle_net_rabbit_bridge_information
- oracle_net_rabbit_bridge_transfer - oracle_net_rabbit_bridge_transfer
- oracle_net_rabbit_bridge_senderhome - oracle_net_rabbit_bridge_senderhome
- oracle_net_rabbit_bridge_senderforeign - oracle_net_rabbit_bridge_senderforeign

View File

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

View File

@ -1,52 +0,0 @@
---
driver:
name: docker
platforms:
- name: oracle-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:
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

View File

@ -13,17 +13,6 @@ platforms:
network_mode: host network_mode: host
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /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: provisioner:
name: ansible name: ansible
playbooks: playbooks:
@ -36,9 +25,6 @@ provisioner:
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9"
ORACLE_HOME_START_BLOCK: 1 ORACLE_HOME_START_BLOCK: 1
ORACLE_FOREIGN_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: verifier:
name: testinfra name: testinfra
lint: lint:

View File

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

View File

@ -1,52 +0,0 @@
---
driver:
name: docker
platforms:
- name: oracle-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:
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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
--- ---
ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE" ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE"
COMMON_HOME_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0" COMMON_HOME_BRIDGE_ADDRESS: "0x8397be90BCF57b0B71219f555Fe121b22e5a994C"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x0AEe1FCD12dDFab6265F7f8956e6E012A9Fe4Aa0" COMMON_FOREIGN_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
MONITOR_PORT: 3013 MONITOR_PORT: 3013

View File

@ -1,19 +1,14 @@
--- ---
## General settings ## General settings
ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE" ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE"
UI_NATIVE_TOKEN_DISPLAY_NAME: "xDai"
## Home contract ## Home contract
COMMON_HOME_RPC_URL: "https://dai.poa.network" 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" COMMON_HOME_BRIDGE_ADDRESS: "0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000 ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract ## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://mainnet.infura.io" 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" COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000 ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
@ -30,20 +25,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 10000000000
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000 ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1 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
MONITOR_BRIDGE_NAME: "xdai" MONITOR_BRIDGE_NAME: "xdai"
MONITOR_PORT: 3003 MONITOR_PORT: 3003

View File

@ -1,6 +0,0 @@
---
ORACLE_BRIDGE_MODE: "ERC_TO_ERC"
COMMON_HOME_BRIDGE_ADDRESS: "0x1feB40aD9420b186F019A717c37f5546165d411E"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127"
UI_PORT: 3001
MONITOR_PORT: 3011

View File

@ -1,6 +1,5 @@
--- ---
ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE" ORACLE_BRIDGE_MODE: "ERC_TO_NATIVE"
COMMON_HOME_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda" COMMON_HOME_BRIDGE_ADDRESS: "0x5118AC62AE912Dd5B51EEfF7338c4fcb0248Ba8c"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x488Af810997eD1730cB3a3918cD83b3216E6eAda" COMMON_FOREIGN_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
UI_PORT: 3002
MONITOR_PORT: 3012 MONITOR_PORT: 3012

View File

@ -1,22 +1,17 @@
--- ---
## General settings ## General settings
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC" ORACLE_BRIDGE_MODE: "ARBITRARY_MESSAGE"
ORACLE_LOG_LEVEL: debug ORACLE_LOG_LEVEL: debug
UI_NATIVE_TOKEN_DISPLAY_NAME: "POA"
## Home contract ## Home contract
COMMON_HOME_RPC_URL: "https://sokol.poa.network" COMMON_HOME_RPC_URL: "https://sokol.poa.network"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol" COMMON_HOME_BRIDGE_ADDRESS: "0x59ba90A588ce732AB33FD32Aab1b58c21400A0f6"
UI_HOME_WITHOUT_EVENTS: false
COMMON_HOME_BRIDGE_ADDRESS: "0x98aFdE294f1C46aA0a27Cc4049ED337F879d8976"
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000 ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract ## Foreign contract
COMMON_FOREIGN_RPC_URL: "https://sokol.poa.network" COMMON_FOREIGN_RPC_URL: "https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan" COMMON_FOREIGN_BRIDGE_ADDRESS: "0xdA4a49a00F4fF4A5988b9AceE95f99e3b2c208b6"
UI_FOREIGN_WITHOUT_EVENTS: false ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 5000
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x5a584f4C30B36f282848dAc9a2b20E7BEF481981"
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
## Home Gasprice ## Home Gasprice
COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/" COMMON_HOME_GAS_PRICE_SUPPLIER_URL: "https://gasprice.poa.network/"
@ -32,26 +27,12 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 1000000000 # in wei
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1 COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000 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
MONITOR_BRIDGE_NAME: "bridge" MONITOR_BRIDGE_NAME: "bridge"
MONITOR_PORT: 3003 MONITOR_PORT: 3003
MONITOR_CACHE_EVENTS: "false" MONITOR_CACHE_EVENTS: "false"
MONITOR_HOME_START_BLOCK: 0 MONITOR_HOME_START_BLOCK: 20821049
MONITOR_FOREIGN_START_BLOCK: 0 MONITOR_FOREIGN_START_BLOCK: 24773297
MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000 MONITOR_VALIDATOR_HOME_TX_LIMIT: 300000
MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000 MONITOR_VALIDATOR_FOREIGN_TX_LIMIT: 300000
MONITOR_TX_NUMBER_THRESHOLD: 100 MONITOR_TX_NUMBER_THRESHOLD: 100

View File

@ -1,6 +0,0 @@
---
ORACLE_BRIDGE_MODE: "NATIVE_TO_ERC"
COMMON_HOME_BRIDGE_ADDRESS: "0x32198D570fffC7033641F8A9094FFDCaAEF42624"
COMMON_FOREIGN_BRIDGE_ADDRESS: "0x2B6871b9B02F73fa24F4864322CdC78604207769"
UI_PORT: 3000
MONITOR_PORT: 3010

View File

@ -1,19 +1,14 @@
--- ---
## General settings ## General settings
UI_NATIVE_TOKEN_DISPLAY_NAME: "POA"
ORACLE_ALLOW_HTTP_FOR_RPC: yes ORACLE_ALLOW_HTTP_FOR_RPC: yes
ORACLE_LOG_LEVEL: debug ORACLE_LOG_LEVEL: debug
## Home contract ## Home contract
COMMON_HOME_RPC_URL: "http://parity1:8545" COMMON_HOME_RPC_URL: "http://parity1:8545"
UI_HOME_NETWORK_DISPLAY_NAME: "POA Sokol"
UI_HOME_WITHOUT_EVENTS: false
ORACLE_HOME_RPC_POLLING_INTERVAL: 5000 ORACLE_HOME_RPC_POLLING_INTERVAL: 5000
## Foreign contract ## Foreign contract
COMMON_FOREIGN_RPC_URL: "http://parity2:8545" COMMON_FOREIGN_RPC_URL: "http://parity2:8545"
UI_FOREIGN_NETWORK_DISPLAY_NAME: "Kovan"
UI_FOREIGN_WITHOUT_EVENTS: false
ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000 ORACLE_FOREIGN_RPC_POLLING_INTERVAL: 1000
## Home Gasprice ## Home Gasprice
@ -30,20 +25,6 @@ COMMON_FOREIGN_GAS_PRICE_FALLBACK: 1000000000 # in wei
COMMON_FOREIGN_GAS_PRICE_FACTOR: 1 COMMON_FOREIGN_GAS_PRICE_FACTOR: 1
ORACLE_FOREIGN_GAS_PRICE_UPDATE_INTERVAL: 600000 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
MONITOR_BRIDGE_NAME: "bridge" MONITOR_BRIDGE_NAME: "bridge"
MONITOR_CACHE_EVENTS: "true" MONITOR_CACHE_EVENTS: "true"

View File

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

View File

@ -7,11 +7,6 @@ sokol-kovan:
ansible_user: ubuntu ansible_user: ubuntu
ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ORACLE_VALIDATOR_ADDRESS_PRIVATE_KEY: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
#syslog_server_port: "udp://127.0.0.1:514" #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: monitor:
hosts: hosts:
127.0.0.1: 127.0.0.1:

View File

@ -1,6 +1,6 @@
/var/log/docker/*/docker.log { /var/log/docker/*/docker.log {
rotate 5 rotate 5
size 1G size 100M
compress compress
missingok missingok
delaycompress delaycompress
@ -8,7 +8,7 @@
} }
/var/log/docker/*.log { /var/log/docker/*.log {
rotate 5 rotate 5
size 1G size 100M
compress compress
missingok missingok
delaycompress delaycompress

View File

@ -3,11 +3,11 @@
with_items: with_items:
- docker-compose - docker-compose
- docker-compose-transfer - docker-compose-transfer
- docker-compose-erc-native - docker-compose-amb
loop_control: loop_control:
loop_var: file loop_var: file
- name: Set the local container logs configuration file - name: Set the oracle's containers local logs configuration file
template: template:
src: 31-oracle-docker.conf.j2 src: 31-oracle-docker.conf.j2
dest: /etc/rsyslog.d/31-oracle-docker.conf dest: /etc/rsyslog.d/31-oracle-docker.conf
@ -15,6 +15,22 @@
group: root group: root
mode: 0644 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 - name: Set the log configuration file to send container logs to remote server
template: template:
src: 36-oracle-remote-logging.conf.j2 src: 36-oracle-remote-logging.conf.j2

View File

@ -27,25 +27,14 @@
set_fact: set_fact:
ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}" ORACLE_VALIDATOR_ADDRESS: "{{ VADDRESS.stdout }}"
- name: Get foreign erc type
become_user: "{{ compose_service_user }}"
shell: docker-compose run --rm --entrypoint "node scripts/initialChecks.js" bridge_affirmation
args:
chdir: "{{ bridge_path }}/oracle"
register: ERCTYPE
- name: Set FOREIGN_ERC_TYPE variable
set_fact:
FOREIGN_ERC_TYPE: "{{ (ERCTYPE.stdout).foreignERC | default('') }}"
- name: Extend docker compose file
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 - name: Extend docker compose file for erc to native
set_fact: composefileoverride="-f docker-compose-erc-native.yml" set_fact: composefileoverride="-f docker-compose-transfer.yml"
when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE" when: ORACLE_BRIDGE_MODE == "ERC_TO_NATIVE"
- name: Extend docker compose file for amb
set_fact: composefileoverride="-f docker-compose-amb.yml"
when: ORACLE_BRIDGE_MODE == "ARBITRARY_MESSAGE"
- name: Install .key config - name: Install .key config
template: template:
src: key.j2 src: key.j2

View File

@ -20,4 +20,4 @@
with_items: with_items:
- docker-compose.yml - docker-compose.yml
- docker-compose-transfer.yml - docker-compose-transfer.yml
- docker-compose-erc-native.yml - docker-compose-amb.yml

View File

@ -11,9 +11,16 @@ ORACLE_HOME_RPC_POLLING_INTERVAL={{ ORACLE_HOME_RPC_POLLING_INTERVAL }}
## Foreign contract ## Foreign contract
COMMON_FOREIGN_RPC_URL={{ COMMON_FOREIGN_RPC_URL }} 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 }} COMMON_FOREIGN_BRIDGE_ADDRESS={{ COMMON_FOREIGN_BRIDGE_ADDRESS }}
ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }} ORACLE_FOREIGN_RPC_POLLING_INTERVAL={{ ORACLE_FOREIGN_RPC_POLLING_INTERVAL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }}
{% endif %}
## Gasprice ## Gasprice
{% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %} {% if COMMON_HOME_GAS_PRICE_SUPPLIER_URL | default('') != '' %}
COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }} COMMON_HOME_GAS_PRICE_SUPPLIER_URL={{ COMMON_HOME_GAS_PRICE_SUPPLIER_URL }}
@ -47,8 +54,28 @@ 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_ALLOW_HTTP_FOR_RPC={{ "yes" if ORACLE_ALLOW_HTTP_FOR_RPC else "no" }}
ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }} ORACLE_QUEUE_URL={{ ORACLE_QUEUE_URL }}
ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }} ORACLE_REDIS_URL={{ ORACLE_REDIS_URL }}
{% if ORACLE_TX_REDUNDANCY | default('') != '' %} {% if ORACLE_FOREIGN_TX_RESEND_INTERVAL | default('') != '' %}
ORACLE_TX_REDUNDANCY={{ ORACLE_TX_REDUNDANCY }} 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 }}
{% endif %} {% endif %}
{% if ORACLE_HOME_START_BLOCK | default('') != '' %} {% if ORACLE_HOME_START_BLOCK | default('') != '' %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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